/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#import "WXLog.h"
#import "WXUtility.h"
#import "WXComponent_internal.h"
#import "WXComponentManager.h"
#import "WXSDKInstance_private.h"

#import "WXCellSlotComponent.h"
#import "WXRecycleListLayout.h"
#import "WXRecycleListComponent.h"
#import "WXRecycleListDataManager.h"
#import "WXRecycleListTemplateManager.h"
#import "WXRecycleListUpdateManager.h"
#import "WXBridgeManager.h"
#import "WXSDKManager.h"
#import "WXComponent+DataBinding.h"
#import "WXComponent+Layout.h"
#import "WXModuleProtocol.h"

@interface WXRecycleListComponentView:UICollectionView
@end

@implementation WXRecycleListComponentView
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    if ([(id <WXScrollerProtocol>) self.wx_component respondsToSelector:@selector(requestGestureShouldStopPropagation:shouldReceiveTouch:)]) {
        return [(id <WXScrollerProtocol>) self.wx_component requestGestureShouldStopPropagation:gestureRecognizer shouldReceiveTouch:touch];
    }
    else{
        return YES;
    }
}

- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
{
    [super setContentOffset:contentOffset animated:animated];
    BOOL scrollStartEvent = [[self.wx_component valueForKey:@"_scrollStartEvent"] boolValue];
    id scrollEventListener = [self.wx_component valueForKey:@"_scrollEventListener"];
    
    if (animated && (scrollStartEvent ||scrollEventListener)  && !WXPointEqualToPoint(contentOffset, self.contentOffset)) {
        CGFloat scaleFactor = self.wx_component.weexInstance.pixelScaleFactor;
        NSDictionary *contentSizeData = @{@"width":@(self.contentSize.width / scaleFactor),
                                          @"height":@(self.contentSize.height / scaleFactor)};
        NSDictionary *contentOffsetData = @{@"x":@(-self.contentOffset.x / scaleFactor),
                                            @"y":@(-self.contentOffset.y / scaleFactor)};
        if (scrollStartEvent) {
            [self.wx_component fireEvent:@"scrollstart" params:@{@"contentSize":contentSizeData, @"contentOffset":contentOffsetData} domChanges:nil];
        }
        if (scrollEventListener) {
            WXScrollerComponent *component = (WXScrollerComponent *)self.wx_component;
            component.scrollEventListener(component, @"scrollstart", @{@"contentSize":contentSizeData, @"contentOffset":contentOffsetData});
        }
    }
}

@end

@interface WXRecycleListComponent () <WXRecycleListLayoutDelegate, WXRecycleListUpdateDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource>

@end

@implementation WXRecycleListComponent
{
    NSString *_templateSwitchKey;
    NSString *_aliasKey;
    NSString *_indexKey;
    __weak UICollectionView *_collectionView;
    
    NSMutableDictionary *_sizeCache;
    NSMutableDictionary *_stickyCache;
    
    NSUInteger _previousLoadMoreCellNumber;
}

WX_EXPORT_METHOD(@selector(appendData:))
WX_EXPORT_METHOD(@selector(appendRange:))
WX_EXPORT_METHOD(@selector(insertData:data:))
WX_EXPORT_METHOD(@selector(updateData:data:))
WX_EXPORT_METHOD(@selector(removeData:count:))
WX_EXPORT_METHOD(@selector(moveData:toIndex:))
WX_EXPORT_METHOD(@selector(insertRange:range:))
WX_EXPORT_METHOD(@selector(setListData:))
WX_EXPORT_METHOD(@selector(scrollTo:options:))
WX_EXPORT_METHOD(@selector(scrollToElement:options:))
WX_EXPORT_METHOD(@selector(queryElement:cssSelector:callback:))
WX_EXPORT_METHOD(@selector(queryElementAll:cssSelector:callback:))
WX_EXPORT_METHOD(@selector(closest:cssSelector:callback:))

- (instancetype)initWithRef:(NSString *)ref
                       type:(NSString *)type
                     styles:(NSDictionary *)styles
                 attributes:(NSDictionary *)attributes
                     events:(NSArray *)events
               weexInstance:(WXSDKInstance *)weexInstance
{
    if (self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]) {
        _dataManager = attributes[@"listData"]? [[WXRecycleListDataManager alloc] initWithData:attributes[@"listData"]] : [WXRecycleListDataManager new];
        _templateManager = [WXRecycleListTemplateManager new];
        _updateManager = [WXRecycleListUpdateManager new];
        _updateManager.delegate = self;
        _templateSwitchKey = [WXConvert NSString:attributes[@"switch"]];
        _aliasKey = [WXConvert NSString:attributes[@"alias"]];
        _indexKey = [WXConvert NSString:attributes[@"index"]];
        _sizeCache = [NSMutableDictionary dictionary];
        _stickyCache = [NSMutableDictionary dictionary];
    }
    
    return self;
}

#pragma mark - WXComponent Methods

- (UIView *)loadView
{
    WXRecycleListLayout *layout = [self recycleListLayout];
    return [[WXRecycleListComponentView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    _collectionView = (UICollectionView *)self.view;
    _collectionView.allowsSelection = NO;
    _collectionView.allowsMultipleSelection = NO;
    _collectionView.dataSource = self;
    _collectionView.delegate = self;
    
    _templateManager.collectionView = _collectionView;
    _updateManager.collectionView = _collectionView;
}

- (void)viewWillUnload
{
    [super viewWillUnload];
    
    _collectionView.dataSource = nil;
    _collectionView.delegate = nil;
}

- (void)updateAttributes:(NSDictionary *)attributes
{
    [super updateAttributes:attributes];
    
    if (attributes[@"listData"]) {
        NSArray *listData = attributes[@"listData"];
        [self _updateListData:listData withCompletion:nil animation:NO];
    }
    if (attributes[@"switch"]) {
        _templateSwitchKey = [WXConvert NSString:attributes[@"switch"]];
    }
    if (attributes[@"alias"]) {
        _aliasKey = [WXConvert NSString:attributes[@"alias"]];
    }
    if (attributes[@"index"]) {
        _indexKey = [WXConvert NSString:attributes[@"index"]];
    }
    if (attributes[@"scrollDirection"]) {
        WXScrollDirection newScrollDirection = attributes[@"scrollDirection"] ? [WXConvert WXScrollDirection:attributes[@"scrollDirection"]] : WXScrollDirectionVertical;
        [self _updateScrollDirection:newScrollDirection];
    }
}

- (CGPoint)absolutePositionForComponent:(WXComponent *)component
{
    CGPoint position = CGPointZero;
    UIView *view = component->_view;
    while (view) {
        if ([view isKindOfClass:[UICollectionViewCell class]]) {
            NSIndexPath *indexPath = [_collectionView indexPathForCell:(UICollectionViewCell *)view];
            if (!indexPath) {
                return CGPointMake(NAN, NAN);
            }
            UICollectionViewLayoutAttributes *attributes = [_collectionView layoutAttributesForItemAtIndexPath:indexPath];
            CGPoint cellOrigin = attributes.frame.origin;
            position = CGPointMake(position.x + cellOrigin.x,
                                   position.y + cellOrigin.y);
            break;
        }
        position = CGPointMake(position.x + view.frame.origin.x,
                               position.y + view.frame.origin.y);
        view = view.superview;
    }
    
    return position;
}

- (void)setContentSize:(CGSize)contentSize
{
    // Do Nothing
}

- (void)adjustSticky
{
    // Do Nothing, sticky is adjusted by layout
}

#pragma mark - Load More Event

- (void)loadMore
{
    [super loadMore];
    _previousLoadMoreCellNumber = [_collectionView numberOfItemsInSection:0];
}

- (BOOL)isNeedLoadMore
{
    BOOL superNeedLoadMore = [super isNeedLoadMore];
    return superNeedLoadMore && _previousLoadMoreCellNumber != [_collectionView numberOfItemsInSection:0];
}

- (void)resetLoadmore
{
    [super resetLoadmore];
    _previousLoadMoreCellNumber = 0;
}

#pragma mark - Exported Component Methods

- (void)appendData:(id)appendingData
{
    if (!appendingData){
        return;
    }
    NSMutableArray * newListData = [[_dataManager data] mutableCopy];
    [newListData addObject:appendingData];
}

- (void)appendRange:(NSArray*)data
{
    if (![data isKindOfClass:[NSArray class]]) {
        WXLogError(@"wrong format of appending data:%@", data);
        return;
    }
    
    NSArray *oldData = [_dataManager data];
    [_updateManager updateWithAppendingData:data oldData:oldData completion:nil animation:NO];
}

- (void)setListData:(NSArray*)data
{
    if ([data count]) {
        [_dataManager updateData:data];
    }
}
- (void)insertData:(NSUInteger)index data:(id)data
{
    // TODO: bring the update logic to UpdateManager
    // TODO: update cell because index has changed
    NSMutableArray *newListData = [[_dataManager data] mutableCopy];
    if (index <= newListData.count) {
        [newListData insertObject:data atIndex:index];
        [_dataManager updateData:newListData];
        
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
        
        [UIView performWithoutAnimation:^{
            [self->_collectionView insertItemsAtIndexPaths:@[indexPath]];
        }];
    }
}

- (void)updateComponentData:(NSString*)componentDataId componentData:(NSDictionary*)componentData callback:(NSString*)callbackId
{
    NSMutableDictionary * virtualComponentData = [[_dataManager virtualComponentDataWithId:componentDataId] mutableCopy];
    NSIndexPath * indexPath = virtualComponentData[@"indexPath"];
    if (!indexPath) {
        return;
    }
    virtualComponentData = virtualComponentData?:[NSMutableDictionary new];
    [virtualComponentData addEntriesFromDictionary:componentData];
    [_dataManager updateVirtualComponentData:componentDataId data:[virtualComponentData copy]];
    virtualComponentData[@"@phase"] = @"update";
    virtualComponentData[@"callbackId"] = callbackId;
    [self _updateDataForCellSlotAtIndexPath:indexPath data:virtualComponentData];
}

- (void)_updateDataForCellSlotAtIndexPath:(NSIndexPath*)indexPath data:(NSDictionary*)data
{
    if(!indexPath || !data) {
        return;
    }
    WXPerformBlockOnMainThread(^{
        UICollectionViewCell * cellView = [self->_collectionView cellForItemAtIndexPath:indexPath];
        WXCellSlotComponent * cellSlotComponent = (WXCellSlotComponent*)cellView.wx_component;
        if (cellSlotComponent) {
            [self _updateBindingData:data forCell:cellSlotComponent atIndexPath:indexPath];
        }
        // callback when update virtual component data success.
        NSString * callbackId = data[@"callbackId"];
        if (callbackId) {
            [[WXSDKManager bridgeMgr] callBack:self.weexInstance.instanceId funcId:callbackId params:@{@"result":@"success"}];
        }
    });
}

- (void)updateData:(NSUInteger)index data:(id)data
{
    NSMutableArray *newListData = [[_dataManager data] mutableCopy];
    if (!data && index > [newListData count]) {
        return;
    }
    // TODO: bring the update logic to UpdateManager
    newListData[index] = data;
    [_dataManager updateData:newListData];
    NSMutableDictionary * newData = nil;
    if (![data isKindOfClass:[NSDictionary class]]) {
         newData = [NSMutableDictionary new];
        [newData setObject:@"data" forKey:data];
        data = newData;
    }
    newData = [data mutableCopy];
    newData[@"@phase"] = @"update";
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
    [self _updateDataForCellSlotAtIndexPath:indexPath data:[newData copy]];
}

- (void)insertRange:(NSInteger)index range:(NSArray*)data
{
    if (![data count]) {
        WXLogDebug(@"ignore invalid insertRange");
        return;
    }
    
    NSMutableArray * newListData = [[_dataManager data] mutableCopy];
    NSRange range = NSMakeRange(index,[data count]);
    NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
    [newListData insertObjects:data atIndexes:indexSet];
    [_dataManager updateData:newListData];
    [_collectionView reloadData];
}

- (void)removeData:(NSInteger)index count:(NSInteger)count
{
    // TODO: bring the update logic to UpdateManager
    
    NSMutableArray *newListData = [[_dataManager data] mutableCopy];
    if (index > [newListData count] || index + count - 1 > [newListData count]) {
        
        return;
    }
    NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(index, count)];
    [newListData removeObjectsAtIndexes:indexSet];
    __block NSMutableArray<NSIndexPath*>* indexPaths = [NSMutableArray new];
    [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
        NSIndexPath* indexPath = [NSIndexPath indexPathForRow:idx inSection:0];
        [indexPaths addObject:indexPath];
    }];
    
    [_dataManager updateData:newListData];
    [_dataManager deleteVirtualComponentAtIndexPaths:indexPaths];
    [UIView performWithoutAnimation:^{
        [self->_collectionView deleteItemsAtIndexPaths:indexPaths];
        [self->_collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
    }];
}

- (void)moveData:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex
{
    // TODO: bring the update logic to UpdateManager
    NSMutableArray *newListData = [[_dataManager data] mutableCopy];
    id data = newListData[fromIndex];
    [newListData removeObjectAtIndex:fromIndex];
    [newListData insertObject:data atIndex:toIndex];
    [_dataManager updateData:newListData];
    
    NSIndexPath *fromIndexPath = [NSIndexPath indexPathForItem:fromIndex inSection:0];
    NSIndexPath *toIndexPath = [NSIndexPath indexPathForItem:toIndex inSection:0];
    [UIView performWithoutAnimation:^{
        [self->_collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
    }];
}

- (void)scrollTo:(NSString *)virtualElementInfo options:(NSDictionary *)options
{
    NSUInteger position = 0;
    if ([virtualElementInfo isKindOfClass:[NSNumber class]]) {
        position = [virtualElementInfo integerValue];
    }
    else
    {
        if (virtualElementInfo.length == 0) {
            return;
        }
        position = [self _positionForVirtualElementInfo:virtualElementInfo];
    }
    NSIndexPath *toIndexPath = [NSIndexPath indexPathForItem:position inSection:0];
    BOOL animated = options[@"animated"] ? [WXConvert BOOL:options[@"animated"]] : YES;
    [_collectionView scrollToItemAtIndexPath:toIndexPath atScrollPosition:UICollectionViewScrollPositionTop animated:animated];
}

- (void)scrollToElement:(NSString *)virtualElementInfo options:(NSDictionary *)options
{
    [self scrollTo:virtualElementInfo options:options];
}

- (void)queryElement:(NSString *)virtualElementInfo cssSelector:(NSString *)cssSelector callback:(WXModuleCallback)callback
{
    [self _queryElement:virtualElementInfo cssSelector:cssSelector callback:callback isAll:NO];
}

- (void)queryElementAll:(NSString *)virtualElementInfo cssSelector:(NSString *)cssSelector callback:(WXModuleCallback)callback
{
    [self _queryElement:virtualElementInfo cssSelector:cssSelector callback:callback isAll:YES];
}

- (NSString *)_refForVirtualElementInfo:(NSString *)virtualElementInfo
{
    if ([virtualElementInfo isKindOfClass:[NSString class]]){
        NSArray *stringArray = [virtualElementInfo componentsSeparatedByString:@"@"];
        if (stringArray.count == 2) {
            return stringArray[0];
        }
    }
    return nil;
}

- (NSUInteger )_positionForVirtualElementInfo:(NSString *)virtualElementInfo
{
    NSArray *stringArray = [virtualElementInfo componentsSeparatedByString:@"@"];
    if (stringArray.count == 2) {
        return [stringArray[1] integerValue];
    }
    return 0;
}

- (void)closest:(NSString *)virtualElementInfo cssSelector:(NSString *)cssSelector callback:(WXModuleCallback)callback
{
    if(callback)
    {
        WXPerformBlockOnComponentThread(^{
            WXComponent *component = [self.weexInstance.componentManager componentForRef:[self _refForVirtualElementInfo:virtualElementInfo]];
            if (component) {
                callback([self _closestComponentForCSSSelector:cssSelector component:component]);
            }
        });
    }
}

- (NSDictionary *)_closestComponentForCSSSelector:(NSString *)cssSelector component:(WXComponent *)component
{
    WXComponent *supercomponent = component.supercomponent;
    if ([self _parseCssSelector:cssSelector component:supercomponent]) {
        NSDictionary *info = @{@"attrs":supercomponent.attributes,@"type":supercomponent->_type,@"ref":supercomponent.ref};
        return info;
    }
    else
    {
        if ([supercomponent isKindOfClass:[WXRecycleListComponent class]] ) {
            return nil;
        }
        return [self _closestComponentForCSSSelector:cssSelector component:supercomponent];
    }
}

- (void)_queryElement:(NSString *)virtualElementInfo cssSelector:(NSString *)cssSelector callback:(WXModuleCallback)callback isAll:(BOOL)isAll
{
    if(callback)
    {
        WXPerformBlockSyncOnComponentThread(^{
            WXComponent *component = [self.weexInstance.componentManager componentForRef:[self _refForVirtualElementInfo:virtualElementInfo]];
            if (component) {
                NSMutableArray *infoArray = [NSMutableArray new];
                [self _matchComponentForCSSSelector:cssSelector component:component infoArray:infoArray];
                if (isAll) {
                    callback(infoArray);
                }
                else
                {
                    if (infoArray.count != 0) {
                        callback(infoArray[0]);
                    }
                }
            }
        });
    }
}

- (void)_matchComponentForCSSSelector:(NSString *)cssSelector component:(WXComponent *)component infoArray:(NSMutableArray *)infoArray
{
    for (WXComponent *subcomponent in component.subcomponents) {
        if ([self _parseCssSelector:cssSelector component:subcomponent]) {
            NSDictionary *info = @{@"attrs":subcomponent.attributes,@"type":subcomponent->_type,@"ref":subcomponent.ref};
            [infoArray addObject:info];
        }
        if (subcomponent.subcomponents.count != 0) {
            [self _matchComponentForCSSSelector:cssSelector component:subcomponent infoArray:infoArray];
        }
    }
}

- (BOOL)_parseCssSelector:(NSString *)cssSelector component:(WXComponent *)component
{
    if (!cssSelector) {
        return NO;
    }
    if ([cssSelector hasPrefix:@"["]&&[cssSelector hasSuffix:@"]"]) {
        NSCharacterSet *unwantedChars = [NSCharacterSet characterSetWithCharactersInString:@"\"[]"];
        NSString *requiredString = [[cssSelector componentsSeparatedByCharactersInSet:unwantedChars] componentsJoinedByString:@""];
        NSArray *selectorArray = [requiredString componentsSeparatedByString:@"="];
        if (selectorArray.count == 2) {
            NSString *attribute = selectorArray[0];
            NSString *value = selectorArray[1];
            NSDictionary *componentAttrs = component.attributes;
            NSString *valueString = [NSString stringWithFormat:@"%@",componentAttrs[attribute]];
            if ([valueString isEqualToString:value]) {
                return YES;
            }
        }
    }
    return NO;
}

#pragma mark - WXComponent Internal Methods

- (BOOL)_insertSubcomponent:(WXComponent *)subcomponent atIndex:(NSInteger)index
{
    BOOL inserted = [super _insertSubcomponent:subcomponent atIndex:index];
    if ([subcomponent isKindOfClass:[WXCellSlotComponent class]]) {
        WXCellSlotComponent *cell = (WXCellSlotComponent*)subcomponent;
        [self.weexInstance.componentManager _addUITask:^{
            [_templateManager addTemplate:cell];
        }];
        //TODO: update collection view if adding template
    }
    return inserted;
}

#pragma mark - Private

- (void)_updateBindingData:(id)data forCell:(WXCellSlotComponent *)cellComponent atIndexPath:(NSIndexPath *)indexPath
{
    id originalData = data;
    if (![originalData isKindOfClass:[NSDictionary class]]) {
        if (_aliasKey) {
            NSMutableDictionary * dictionary = [NSMutableDictionary dictionary];
            [dictionary setObject:data forKey:_aliasKey];
            data = dictionary;
        } else {
            return;
        }
    }
    
    if (!data[@"indexPath"] || !data[@"recycleListComponentRef"]) {
        NSMutableDictionary * dataNew = [data mutableCopy];
        dataNew[@"recycleListComponentRef"] = self.ref;
        dataNew[@"indexPath"] = indexPath;
        data = dataNew;
    }
    
    if ([originalData isKindOfClass:[NSDictionary class]] && _aliasKey &&!data[@"phase"]) {
        data = @{_aliasKey:data,@"aliasKey":_aliasKey};
    }
    
    if (_indexKey) {
        NSMutableDictionary *dataNew = [data mutableCopy];
        dataNew[_indexKey] = @(indexPath.item);
        data = dataNew;
    }
    
#ifdef DEBUG
    NSDate *startTime = [NSDate date];
#endif
    
    WXPerformBlockSyncOnComponentThread(^{
        [cellComponent updateCellData:[data copy]];
    });
#ifdef DEBUG
    double duration = -[startTime timeIntervalSinceNow] * 1000;
    WXLogDebug(@"cell:%li update data time:%f", (long)indexPath.item, duration);
#endif
    
    NSValue *cachedSize = _sizeCache[indexPath];
    if (!cachedSize || !CGSizeEqualToSize([cachedSize CGSizeValue] , cellComponent.calculatedFrame.size)) {
        _sizeCache[indexPath] = [NSValue valueWithCGSize:cellComponent.calculatedFrame.size];
        [_collectionView.collectionViewLayout invalidateLayout];
    }
    NSNumber *cachedSticky = _stickyCache[indexPath];
    BOOL isSticky = cellComponent->_positionType == WXPositionTypeSticky;
    if (!cachedSticky || [cachedSticky boolValue] != isSticky) {
        _stickyCache[indexPath] = @(isSticky);
    }
}

- (void)_updateListData:(NSArray *)newData
        withCompletion:(WXRecycleListUpdateCompletion)completion
             animation:(BOOL)animation
{
    if (![newData isKindOfClass:[NSArray class]]) {
        WXLogError(@"wrong format of list data:%@", newData);
        if (completion) {
            completion(NO);
        }
        return;
    }
    
    NSArray *oldData = [_dataManager data];
    [_updateManager updateWithNewData:newData oldData:oldData completion:completion animation:animation];
}

- (void)_updateScrollDirection:(WXScrollDirection)newScrollDirection
{   
    WXRecycleListLayout *layout = [self recycleListLayout];
    _collectionView.collectionViewLayout = layout;
}

- (WXRecycleListLayout *)recycleListLayout
{
    WXRecycleListLayout *layout = [WXRecycleListLayout new];
    layout.delegate = self;
    // to show cells that original width / height is zero, otherwise cellForItemAtIndexPath will not be called
    layout.minimumLineSpacing = 0.01;
    layout.minimumInteritemSpacing = 0.01;
    if (WXScrollDirectionHorizontal == self.scrollDirection) {
        layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    }
    return layout;
}

#pragma mark - UICollectionViewDataSource

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    return 1;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return [_dataManager numberOfItems];
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    // 1. get the data relating to the cell
    id data = [_dataManager dataAtIndex:indexPath.row];
    
    // 2. get the template type specified by data, and if template is not found, return an empty view of any template to avoid crash.
    NSString * templateType = [self templateType:indexPath];
    _templateManager.collectionView = collectionView;
    if (!templateType) {
        WXLogError(@"Template %@ not registered for collection view.", templateType);
        UICollectionViewCell *cellView = [_collectionView dequeueReusableCellWithReuseIdentifier:[_templateManager anyRegisteredTemplate] forIndexPath:indexPath];
        for (UIView *view in cellView.contentView.subviews) {
            [view removeFromSuperview];
        }
        cellView.wx_component = nil;
        [cellView setAccessibilityIdentifier:nil];
        return cellView;
    }
    if (![_templateManager isTemplateRegistered:templateType]) {
        templateType = @"default";
    }
    
    // 3. dequeue a cell component by template type
    UICollectionViewCell *cellView = [_collectionView dequeueReusableCellWithReuseIdentifier:templateType forIndexPath:indexPath];
    WXCellSlotComponent *cellComponent = (WXCellSlotComponent *)cellView.wx_component;
    if (!cellComponent) {
        cellComponent = [_templateManager dequeueCellSlotWithType:templateType forIndexPath:indexPath];
        cellView.wx_component = cellComponent;
        WXPerformBlockOnComponentThread(^{
            //TODO: How can we avoid this?
            [super _insertSubcomponent:cellComponent atIndex:self.subcomponents.count];
        });
    }
    
    // 4. binding the data to the cell component
    [self _updateBindingData:data forCell:cellComponent atIndexPath:indexPath];

    // 5. Add cell component's view to content view.
    UIView *contentView = cellComponent.view;
    if (contentView.superview == cellView.contentView) {
        return cellView;
    }
    
    for (UIView *view in cellView.contentView.subviews) {
        [view removeFromSuperview];
    }
    [cellView.contentView addSubview:contentView];
    [cellView setAccessibilityIdentifier:contentView.accessibilityIdentifier];
    
    WXLogDebug(@"Return cell view:%@, indexPath:%@", cellView, indexPath);
    
    [self handleAppear];
    
    return cellView;
}

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
    return nil;
}

#pragma mark - UICollectionViewDelegate

- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
    WXLogDebug(@"will display cell:%@, at index path:%@", cell, indexPath);
}

- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
    cell.wx_component = nil;
    WXLogDebug(@"Did end displaying cell:%@, at index path:%@", cell, indexPath);
}

#pragma mark - UICollectionViewDelegateFlowLayout

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSValue *size = _sizeCache[indexPath];
    if (size) {
        return [size CGSizeValue];
    } else {

        WXCellSlotComponent *cell = [_templateManager templateWithType:[self templateType:indexPath]];
        CGSize size = cell.calculatedFrame.size;
        _sizeCache[indexPath] = [NSValue valueWithCGSize:size];
        return CGSizeMake(_collectionView.frame.size.width, size.height);
    }
}

#pragma mark - WXRecycleListLayoutDelegate

- (BOOL)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout isNeedStickyForIndexPath:(NSIndexPath *)indexPath
{
    NSNumber *cachedSticky = _stickyCache[indexPath];
    if (cachedSticky) {
        return [cachedSticky boolValue];
    } else {
        return NO;
    }
}

#pragma mark - WXRecycleListUpdateDelegate

- (void)updateManager:(WXRecycleListUpdateManager *)manager willUpdateData:(id)newData
{
    [_dataManager updateData:newData];
}

- (void)updateManager:(WXRecycleListUpdateManager *)manager didUpdateData:(id)newData withSuccess:(BOOL)finished
{
    
}

- (NSString*)templateType:(NSIndexPath*)indexPath
{
    NSDictionary *data = [_dataManager dataAtIndex:indexPath.row];
    // default is first template.
    NSString *templateType = [_templateManager topTemplate].templateCaseType;
    if (!data || ![data isKindOfClass:[NSDictionary class]]) {
        return templateType;
    }
    
    if (_templateSwitchKey && data[_templateSwitchKey]){
        templateType = data[_templateSwitchKey];
    } else if (data[WXDefaultRecycleTemplateType]){
        // read the default type.
        templateType = data[WXDefaultRecycleTemplateType];
    }
    return templateType;
}

@end
