/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#import <XCTest/XCTest.h>

#import <IGListKit/IGListCollectionView.h>

#import "IGLayoutTestDataSource.h"
#import "IGLayoutTestItem.h"
#import "IGLayoutTestSection.h"
#import "IGListTestHelpers.h"

@interface IGListCollectionViewTests : XCTestCase

@property (nonatomic, strong) UIWindow *window;
@property (nonatomic, strong) IGListCollectionView *collectionView;
@property (nonatomic, strong) IGLayoutTestDataSource *dataSource;

@end

@implementation IGListCollectionViewTests

- (void)setUp {
    [super setUp];
    const CGRect frame = CGRectMake(0, 0, 100, 100);
    self.window = [[UIWindow alloc] initWithFrame:frame];
    self.dataSource = [IGLayoutTestDataSource new];
    self.collectionView = [[IGListCollectionView alloc] initWithFrame:frame];
    self.collectionView.dataSource = self.dataSource;
    self.collectionView.delegate = self.dataSource;
    [self.window addSubview:self.collectionView];
    [self.dataSource configCollectionView:self.collectionView];
}

#pragma mark - Reload All

- (void)test_whenReloadData_thatEntireLayoutUpdates {
    self.dataSource.sections = @[
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(10, 10))])
                                 ];
    [self.collectionView reloadData];
    [self.collectionView layoutIfNeeded];

    self.dataSource.sections = @[
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(20, 20))]),
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(10, 10))])
                                 ];
    [self.collectionView reloadData];
    [self.collectionView layoutIfNeeded];

    IGAssertEqualFrame([self cellForSection:0 item:0].frame, 0, 0, 20, 20);
    IGAssertEqualFrame([self cellForSection:1 item:0].frame, 20, 0, 10, 10);
}

#pragma mark - Insert/Delete/Reload/Move

- (void)test_whenInsertingSection_thatLayoutUpdates {
    self.dataSource.sections = @[
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(10, 10))])
                                 ];
    [self.collectionView reloadData];
    [self.collectionView layoutIfNeeded];

    self.dataSource.sections = @[
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(20, 20))]),
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(10, 10))])
                                 ];
    [self.collectionView insertSections:[NSIndexSet indexSetWithIndex:1]];

    // check that section 0 was updated
    IGAssertEqualFrame([self cellForSection:0 item:0].frame, 0, 0, 20, 20);
    // check that section 1 was updated
    IGAssertEqualFrame([self cellForSection:1 item:0].frame, 20, 0, 10, 10);
}

- (void)test_whenDeletingSection_thatLayoutUpdates {
    self.dataSource.sections = @[
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(10, 10))]),
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(10, 10))])
                                 ];
    [self.collectionView reloadData];
    [self.collectionView layoutIfNeeded];

    self.dataSource.sections = @[
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(20, 20))]),
                                 ];
    [self.collectionView deleteSections:[NSIndexSet indexSetWithIndex:1]];

    // check that section 0 wasn't updated
    IGAssertEqualFrame([self cellForSection:0 item:0].frame, 0, 0, 20, 20);
}

- (void)test_whenReloadingSection_thatLayoutUpdates {
    self.dataSource.sections = @[
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(10, 10))]),
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(10, 10))])
                                 ];
    [self.collectionView reloadData];
    [self.collectionView layoutIfNeeded];

    self.dataSource.sections = @[
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(20, 20))]),
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(20, 20))]),
                                 ];
    [self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:1]];

    // check that section 0 was updated
    IGAssertEqualFrame([self cellForSection:0 item:0].frame, 0, 0, 20, 20);
    // check that section 1 was updated
    IGAssertEqualFrame([self cellForSection:1 item:0].frame, 20, 0, 20, 20);
}

- (void)test_whenMoveSection_thatLayoutUpdates {
    self.dataSource.sections = @[
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(10, 10))]),
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(20, 20))]),
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(30, 30))])
                                 ];
    [self.collectionView reloadData];
    [self.collectionView layoutIfNeeded];

    self.dataSource.sections = @[
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(40, 40))]),
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(30, 30))]),
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(20, 20))]),
                                 ];
    [self.collectionView moveSection:1 toSection:2];

    // check that section 0 was updated
    IGAssertEqualFrame([self cellForSection:0 item:0].frame, 0, 0, 40, 40);
    // check that section 1 was updated
    IGAssertEqualFrame([self cellForSection:1 item:0].frame, 40, 0, 30, 30);
    // check that section 2 was updated
    IGAssertEqualFrame([self cellForSection:2 item:0].frame, 70, 0, 20, 20);
}

- (void)test_whenMoveItem_thatLayoutPartiallyUpdates {
    self.dataSource.sections = @[
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(10, 10))]),
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(20, 20))]),
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(30, 30))])
                                 ];
    [self.collectionView reloadData];
    [self.collectionView layoutIfNeeded];

    NSArray *sections = @[genLayoutTestItem(CGSizeMake(10, 10)), genLayoutTestItem(CGSizeMake(20, 20))];
    self.dataSource.sections = @[
                                 genLayoutTestSection(sections),
                                 genLayoutTestSection(@[]),
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(20, 20))]),
                                 ];

    [self.collectionView moveItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]
                                 toIndexPath:[NSIndexPath indexPathForItem:1 inSection:0]];

    // check that section 0 wasn't updated
    IGAssertEqualFrame([self cellForSection:0 item:0].frame, 0, 0, 10, 10);
    // check that section 1 was updated
    IGAssertEqualFrame([self cellForSection:0 item:1].frame, 10, 0, 20, 20);
    // check that section 2 was updated
    IGAssertEqualFrame([self cellForSection:2 item:0].frame, 30, 0, 20, 20);
}

#pragma mark - Batch

- (void)test_whenInsertDeleteMoveSection_thatLayoutUpdates {
    self.dataSource.sections = @[
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(1, 1))]),
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(2, 2))]),
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(3, 3))]),
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(4, 4))]),
                                 ];
    [self.collectionView reloadData];
    [self.collectionView layoutIfNeeded];

    self.dataSource.sections = @[
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeZero)]),
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(4, 4))]),
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(3, 3))]),
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(5, 5))]),
                                 ];

    XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];

    [self.collectionView performBatchUpdates:^{
        [self.collectionView deleteSections:[NSIndexSet indexSetWithIndex:1]]; // deleted (2, 2)
        [self.collectionView moveSection:3 toSection:1]; // move (4, 4)
        [self.collectionView insertSections:[NSIndexSet indexSetWithIndex:3]]; // inserted (5, 5)
    } completion:^(BOOL finished) {
        [self.collectionView layoutIfNeeded];
        [expectation fulfill];

        // check that section 0 was updated
        IGAssertEqualFrame([self cellForSection:0 item:0].frame, 0, 0, 0, 0);
        // check that section 1 was updated
        IGAssertEqualFrame([self cellForSection:1 item:0].frame, 0, 0, 4, 4);
        // check that section 2 was updated
        IGAssertEqualFrame([self cellForSection:2 item:0].frame, 4, 0, 3, 3);
        // check that section 3 was updated
        IGAssertEqualFrame([self cellForSection:3 item:0].frame, 7, 0, 5, 5);
    }];

    [self waitForExpectationsWithTimeout:30 handler:^(NSError * _Nullable error) {
        XCTAssertNil(error);
    }];
}

- (void)test_whenInsertingNilSection_thatExecutionCompletesCleanly {
    self.dataSource.sections = @[
                                 genLayoutTestSection(@[genLayoutTestItem(CGSizeMake(10, 10))])
                                 ];
    [self.collectionView reloadData];
    [self.collectionView layoutIfNeeded];

    [self.collectionView insertSections:[NSIndexSet indexSet]];

    // check that section 0 wasn't updated
    IGAssertEqualFrame([self cellForSection:0 item:0].frame, 0, 0, 10, 10);
}

#pragma mark - Helpers

- (UICollectionViewCell *)cellForSection:(NSInteger)section item:(NSInteger)item {
    return [self.collectionView cellForItemAtIndexPath:genIndexPath(section, item)];
}

@end
