/*
 * 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 "WXScrollerComponent.h"
#import "WXComponent_internal.h"
#import "WXSDKInstance_private.h"
#import "WXComponent.h"
#import "WXDefine.h"
#import "WXConvert.h"
#import "WXSDKInstance.h"
#import "WXUtility.h"
#import "WXLoadingComponent.h"
#import "WXRefreshComponent.h"
#import "WXConfigCenterProtocol.h"
#import "WXSDKEngine.h"
#import "WXComponent+Events.h"
#import "WXPageEventNotifyEvent.h"
#import "WXComponent+Layout.h"
#import "WXUtility.h"

@interface WXScrollerComponentView : UIScrollView
@end

@implementation WXScrollerComponentView
- (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 WXScrollToTarget : NSObject

@property (nonatomic, weak)   WXComponent *target;
@property (nonatomic, assign) BOOL hasAppear;

@end

@implementation WXScrollToTarget

@end


@interface WXScrollerComponent()

@property (nonatomic, strong) NSMutableArray *  stickyArray;
@property (nonatomic, strong) NSMutableArray * listenerArray;
@property (nonatomic, weak) WXRefreshComponent *refreshComponent;
@property (nonatomic, weak) WXLoadingComponent *loadingComponent;

@end

@implementation WXScrollerComponent
{
    CGSize _contentSize;
    BOOL _needsPlatformLayout;
    BOOL _listenLoadMore;
    BOOL _scrollEvent;
    BOOL _scrollStartEvent;
    BOOL _scrollEndEvent;
    BOOL _isScrolling;
    BOOL _isDragging;
    CGFloat _pageSize;
    CGFloat _loadMoreOffset;
    CGFloat _previousLoadMoreContentHeight;
    CGFloat _offsetAccuracy;
    CGPoint _lastContentOffset;
    CGPoint _lastScrollEventFiredOffset;
    BOOL _scrollable;
    NSString * _alwaysScrollableVertical;
    NSString * _alwaysScrollableHorizontal;
    BOOL _bounces;
    
    // refreshForAppear: load more when refresh component begin appear(if scroll is dragging or decelerating, should delay)
    // refreshForWholeVisible: load more until the whole refresh component visible
    NSString *_refreshType;

    // vertical & horizontal
    WXScrollDirection _scrollDirection;
    // left & right & up & down
    NSString *_direction;
    BOOL _showScrollBar;
    BOOL _pagingEnabled;
    
    BOOL _shouldNotifiAppearDescendantView;
    BOOL _shouldRemoveScrollerListener;
    CGPoint _scrollStartPoint;
    CGPoint _scrollEndPoint;

    //css_node_t *_scrollerCSSNode;
    
    NSHashTable* _delegates;
}

WX_EXPORT_METHOD(@selector(resetLoadmore))

- (void)resetLoadmore
{
    _previousLoadMoreContentHeight=0;
}

- (BOOL)_insertSubcomponent:(WXComponent *)subcomponent atIndex:(NSInteger)index
{
    BOOL inserted = [super _insertSubcomponent:subcomponent atIndex:index];
    
    if ([subcomponent isKindOfClass:[WXRefreshComponent class]]) {
        _refreshComponent = (WXRefreshComponent*)subcomponent;
    }
    else if ([subcomponent isKindOfClass:[WXLoadingComponent class]]) {
        _loadingComponent = (WXLoadingComponent*)subcomponent;
    }
    
    // If a vertical list is added to a horizontal scroller, we need platform dependent layout
    if (_flexCssNode && [self isKindOfClass:[WXScrollerComponent class]] &&
        [subcomponent isKindOfClass:[WXScrollerComponent class]] &&
        subcomponent->_positionType != WXPositionTypeFixed &&
        (((WXScrollerComponent*)subcomponent).scrollDirection == WXScrollDirectionVertical)) {
        if (subcomponent->_flexCssNode) {
            if (subcomponent->_flexCssNode->getFlex() > 0 && !isnan(subcomponent->_flexCssNode->getStyleWidth())) {
                _needsPlatformLayout = YES;
                _flexCssNode->setNeedsPlatformDependentLayout(true);
            }
        }
    }

    return inserted;
}

-(instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance
{
    self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance];
    if (self) {
        
        _stickyArray = [NSMutableArray array];
        _listenerArray = [NSMutableArray array];
        _scrollEvent = NO;
        _scrollStartEvent = NO;
        _scrollEndEvent = NO;
        _lastScrollEventFiredOffset = CGPointMake(0, 0);
        _scrollDirection = attributes[@"scrollDirection"] ? [WXConvert WXScrollDirection:attributes[@"scrollDirection"]] : WXScrollDirectionVertical;
        _showScrollBar = attributes[@"showScrollbar"] ? [WXConvert BOOL:attributes[@"showScrollbar"]] : YES;
        
        if (attributes[@"alwaysScrollableVertical"]) {
            _alwaysScrollableVertical = [WXConvert NSString:attributes[@"alwaysScrollableVertical"]];
        }
        if (attributes[@"alwaysScrollableHorizontal"]) {
            _alwaysScrollableHorizontal = [WXConvert NSString:attributes[@"alwaysScrollableHorizontal"]];
        }
        _bounces = attributes[@"bounce"]?[WXConvert BOOL:attributes[@"bounce"]]:YES;
        _refreshType = [WXConvert NSString:attributes[@"refreshType"]]?:@"refreshForWholeVisible";
        _pagingEnabled = attributes[@"pagingEnabled"] ? [WXConvert BOOL:attributes[@"pagingEnabled"]] : NO;
        _pageSize = attributes[@"pageSize"] ? [WXConvert WXPixelType:attributes[@"pageSize"] scaleFactor:self.weexInstance.pixelScaleFactor] : 0;
        if (_pageSize < 0) {
            _pageSize = 0;
        }
        _loadMoreOffset = attributes[@"loadmoreoffset"] ? [WXConvert WXPixelType:attributes[@"loadmoreoffset"] scaleFactor:self.weexInstance.pixelScaleFactor] : 0;
        _loadmoreretry = attributes[@"loadmoreretry"] ? [WXConvert NSUInteger:attributes[@"loadmoreretry"]] : 0;
        _listenLoadMore = [events containsObject:@"loadmore"];
        _scrollable = attributes[@"scrollable"] ? [WXConvert BOOL:attributes[@"scrollable"]] : YES;
        _offsetAccuracy = attributes[@"offsetAccuracy"] ? [WXConvert WXPixelType:attributes[@"offsetAccuracy"] scaleFactor:self.weexInstance.pixelScaleFactor] : 0;

        /* let scroller fill the rest space if it is a child component and has no fixed height & width.
         WeexCore also does this in C++, but only for "scroller" and "list" not including for
         subclasses of WXScrollerComponent. */
        if (_flexCssNode != nullptr) {
            if (((_scrollDirection == WXScrollDirectionVertical &&
                  flexIsUndefined(_flexCssNode->getStyleHeight())) ||
                 (_scrollDirection == WXScrollDirectionHorizontal &&
                  flexIsUndefined(_flexCssNode->getStyleWidth()))) &&
                _flexCssNode->getFlex() <= 0.0) {
                _flexCssNode->set_flex(1.0);
            }
        }
        
        id configCenter = [WXSDKEngine handlerForProtocol:@protocol(WXConfigCenterProtocol)];
        if ([configCenter respondsToSelector:@selector(configForKey:defaultValue:isDefault:)]) {
            BOOL shouldNotifiAppearDescendantView = [[configCenter configForKey:@"iOS_weex_ext_config.shouldNotifiAppearDescendantView" defaultValue:@(YES) isDefault:NULL] boolValue];
            _shouldNotifiAppearDescendantView = shouldNotifiAppearDescendantView;
            BOOL shouldRemoveScrollerListener = [[configCenter configForKey:@"iOS_weex_ext_config.shouldRemoveScrollerListener" defaultValue:@(YES) isDefault:NULL] boolValue];
            _shouldRemoveScrollerListener = shouldRemoveScrollerListener;
            
        }
        //may be list
        if ([@"scroller" isEqualToString:type]) {
            [weexInstance.apmInstance updateDiffStats:KEY_PAGE_STATS_SCROLLER_NUM withDiffValue:1];
        }

        if (weexInstance.instanceCallback) {
            weexInstance.instanceCallback(weexInstance, WXScrollerComponentCreatedCallback, self);
        }
    }
    
    return self;
}

- (UIView *)loadView
{
    return [[WXScrollerComponentView alloc] init];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self setContentSize:_contentSize];
    WXScrollerComponentView *scrollView = (WXScrollerComponentView *)self.view;
    scrollView.delegate = self;
    scrollView.exclusiveTouch = YES;
    scrollView.autoresizesSubviews = NO;
    scrollView.clipsToBounds = YES;
    scrollView.showsVerticalScrollIndicator = _showScrollBar;
    scrollView.showsHorizontalScrollIndicator = _showScrollBar;
    scrollView.scrollEnabled = _scrollable;
    scrollView.pagingEnabled = _pagingEnabled;
    
    if (scrollView.bounces != _bounces) {
        scrollView.bounces = _bounces;
    }
    
    if (_alwaysScrollableHorizontal) {
        scrollView.alwaysBounceHorizontal = [WXConvert BOOL:_alwaysScrollableHorizontal];
    }
    if (_alwaysScrollableVertical) {
        scrollView.alwaysBounceVertical = [WXConvert BOOL:_alwaysScrollableVertical];
    }
    
    if (@available(iOS 11.0, *)) {
        if ([scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
            scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
        }
    }
    
    if (self.ancestorScroller) {
        scrollView.scrollsToTop = NO;
    } else {
        scrollView.scrollsToTop = YES;
    }
    
    if (_pagingEnabled && _pageSize > 0) {
        scrollView.pagingEnabled = NO; // turn off system default paging
        scrollView.decelerationRate = UIScrollViewDecelerationRateFast;
    }
    else {
        scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
    }
}

- (void)layoutDidFinish
{
    if ([self isViewLoaded]) {
        [self setContentSize:_contentSize];
        [self adjustSticky];
        [self handleAppear];
    }
    
    [_loadingComponent resizeFrame];
}

- (void)_buildViewHierarchyLazily
{
    [super _buildViewHierarchyLazily];
    [self handleAppear];
}

- (void)viewWillUnload
{
    ((UIScrollView *)_view).delegate = nil;
}

- (void)dealloc
{
    ((UIScrollView *)_view).delegate = nil;
    [self.stickyArray removeAllObjects];
    [self.listenerArray removeAllObjects];
}

- (void)updateAttributes:(NSDictionary *)attributes
{
    if (attributes[@"showScrollbar"]) {
        _showScrollBar = [WXConvert BOOL:attributes[@"showScrollbar"]];
        ((UIScrollView *)self.view).showsHorizontalScrollIndicator = _showScrollBar;
        ((UIScrollView *)self.view).showsVerticalScrollIndicator = _showScrollBar;
    }
    
    if (attributes[@"pagingEnabled"]) {
        _pagingEnabled = [WXConvert BOOL:attributes[@"pagingEnabled"]];
        ((UIScrollView *)self.view).pagingEnabled = _pagingEnabled;
    }
    
    if (attributes[@"pageSize"]) {
        _pageSize = [WXConvert WXPixelType:attributes[@"pageSize"]
                               scaleFactor:self.weexInstance.pixelScaleFactor];
        if (_pageSize < 0) {
            _pageSize = 0;
        }
    }
    
    if ([self isViewLoaded]) {
        if (_pagingEnabled && _pageSize > 0) {
            ((UIScrollView *)self.view).pagingEnabled = NO; // turn off system default paging
            ((UIScrollView *)self.view).decelerationRate = UIScrollViewDecelerationRateFast;
        }
        else {
            ((UIScrollView *)self.view).decelerationRate = UIScrollViewDecelerationRateNormal;
        }
    }
    
    if (attributes[@"loadmoreoffset"]) {
        _loadMoreOffset = [WXConvert WXPixelType:attributes[@"loadmoreoffset"] scaleFactor:self.weexInstance.pixelScaleFactor];
    }
    
    if (attributes[@"bounce"]) {
        _bounces = [WXConvert BOOL:attributes[@"bounce"]];
        ((UIScrollView *)self.view).bounces = _bounces;
    }
    
    if (attributes[@"loadmoreretry"]) {
        NSUInteger loadmoreretry = [WXConvert NSUInteger:attributes[@"loadmoreretry"]];
        if (loadmoreretry != _loadmoreretry) {
            _previousLoadMoreContentHeight = 0;
        }
        self.loadmoreretry = loadmoreretry;
    }
    
    if (attributes[@"scrollable"]) {
        _scrollable = attributes[@"scrollable"] ? [WXConvert BOOL:attributes[@"scrollable"]] : YES;
        ((UIScrollView *)self.view).scrollEnabled = _scrollable;
    }
    if (attributes[@"alwaysScrollableHorizontal"]) {
        _alwaysScrollableHorizontal = [WXConvert NSString:attributes[@"alwaysScrollableHorizontal"]];
        ((UIScrollView*)self.view).alwaysBounceHorizontal = [WXConvert BOOL:_alwaysScrollableHorizontal];
    }
    
    if (attributes[@"alwaysScrollableVertical"]) {
        _alwaysScrollableVertical = [WXConvert NSString:attributes[@"alwaysScrollableVertical"]];
        ((UIScrollView*)self.view).alwaysBounceVertical = [WXConvert BOOL:_alwaysScrollableVertical];
    }
    
    if (attributes[@"refreshType"]) {
        _refreshType = [WXConvert NSString:attributes[@"refreshType"]];
    }
    
    if (attributes[@"offsetAccuracy"]) {
        _offsetAccuracy = [WXConvert WXPixelType:attributes[@"offsetAccuracy"] scaleFactor:self.weexInstance.pixelScaleFactor];
    }
    
    if (attributes[@"scrollDirection"]) {
        _scrollDirection = attributes[@"scrollDirection"] ? [WXConvert WXScrollDirection:attributes[@"scrollDirection"]] : WXScrollDirectionVertical;
    }
}

- (void)addEvent:(NSString *)eventName
{
    if ([eventName isEqualToString:@"loadmore"]) {
        _listenLoadMore = YES;
    }
    if ([eventName isEqualToString:@"scroll"]) {
        _scrollEvent = YES;
    }
    if ([eventName isEqualToString:@"scrollstart"]) {
        _scrollStartEvent = YES;
    }
    if ([eventName isEqualToString:@"scrollend"]) {
        _scrollEndEvent = YES;
    }
}

- (void)removeEvent:(NSString *)eventName
{
    if ([eventName isEqualToString:@"loadmore"]) {
        _listenLoadMore = NO;
    }
    if ([eventName isEqualToString:@"scroll"]) {
        _scrollEvent = NO;
    }
    if ([eventName isEqualToString:@"scrollstart"]) {
        _scrollStartEvent = NO;
    }
    if ([eventName isEqualToString:@"scrollend"]) {
        _scrollEndEvent = NO;
    }
}

#pragma mark WXScrollerProtocol

- (void)addStickyComponent:(WXComponent *)sticky
{
    if(![self.stickyArray containsObject:sticky]) {
        [self.stickyArray addObject:sticky];
        [self adjustSticky];
    }
}

- (void)removeStickyComponent:(WXComponent *)sticky
{
    if([self.stickyArray containsObject:sticky]) {
        [self.stickyArray removeObject:sticky];
		WXPerformBlockOnMainThread(^{
			[self adjustSticky];
		});
    }
}

- (void)adjustForRTL
{
    if (![WXUtility enableRTLLayoutDirection]) return;
    
    // this is scroll rtl solution.
    // scroll layout not use direction, use self tranform
    if (self.view && [self isDirectionRTL]) {
        if (_transform) {
            self.view.layer.transform = CATransform3DConcat(self.view.layer.transform, CATransform3DScale(CATransform3DIdentity, -1, 1, 1));
        } else {
            self.view.layer.transform = CATransform3DScale(CATransform3DIdentity, -1, 1, 1);
        }
    } else {
        if (!_transform) {
            self.view.layer.transform = CATransform3DIdentity;
        }
    }
}

- (void)_adjustForRTL {
    if (![WXUtility enableRTLLayoutDirection]) return;
    
    [super _adjustForRTL];
    [self adjustForRTL];
}

- (void)adjustSticky
{
    if (![self isViewLoaded]) {
        return;
    }
    CGFloat scrollOffsetY = ((UIScrollView *)self.view).contentOffset.y;
    for(WXComponent *component in self.stickyArray) {
        if (isnan(component->_absolutePosition.x) && isnan(component->_absolutePosition.y)) {
            component->_absolutePosition = [component.supercomponent.view convertPoint:component.view.frame.origin toView:self.view];
        }
        CGPoint relativePosition = component->_absolutePosition;
        if (isnan(relativePosition.y)) {
            continue;
        }
        
        WXComponent *supercomponent = component.supercomponent;
        if(supercomponent != self && component.view.superview != self.view) {
            [component.view removeFromSuperview];
            [self.view addSubview:component.view];
        } else {
            [self.view bringSubviewToFront:component.view];
        }
        
        CGFloat relativeY = relativePosition.y;
        BOOL needSticky = NO;
        
        if (scrollOffsetY >= relativeY) {
            needSticky = YES;
        } else {
            // important: reset views' frame
            component.view.frame = CGRectMake(relativePosition.x, relativePosition.y, component.calculatedFrame.size.width, component.calculatedFrame.size.height);
        }
        
        if (!needSticky) {
            continue;
        }
        
        // The minimum Y sticky view can reach is its original position
        CGFloat minY = relativeY;
        CGPoint superRelativePosition = supercomponent == self ? CGPointZero : [supercomponent.supercomponent.view convertPoint:supercomponent.view.frame.origin toView:self.view];
        CGFloat maxY = superRelativePosition.y + supercomponent.calculatedFrame.size.height - component.calculatedFrame.size.height;
        
        CGFloat stickyY = scrollOffsetY;
        if (stickyY < minY) {
            stickyY = minY;
        } else if (stickyY > maxY && ![supercomponent conformsToProtocol:@protocol(WXScrollerProtocol)]) {
            // Sticky component can not go beyond its parent's bounds when its parent is not scroller;
            stickyY = maxY;
        }
        
        UIView *stickyView = component.view;
        CGPoint origin = stickyView.frame.origin;
        origin.y = stickyY;
        stickyView.frame = (CGRect){origin,stickyView.frame.size};
    }
}

- (void)addScrollToListener:(WXComponent *)target
{
    BOOL has = NO;
    NSMutableArray *listenerArray = [self.listenerArray copy];
    for (WXScrollToTarget *targetData in listenerArray) {
        if (targetData.target == target) {
            has = YES;
            break;
        }
    }
    if (!has) {
        WXScrollToTarget *scrollTarget = [[WXScrollToTarget alloc] init];
        scrollTarget.target = target;
        scrollTarget.hasAppear = NO;
        [self.listenerArray addObject:scrollTarget];
    }
}

- (void)removeScrollToListener:(WXComponent *)target
{
    if (_shouldRemoveScrollerListener) {
        WXScrollToTarget *targetData = nil;
        NSMutableArray *listenerArray = [self.listenerArray copy];
        for (WXScrollToTarget *targetDataTemp in listenerArray) {
            if (targetDataTemp.target == target) {
                targetData = targetDataTemp;
                break;
            }
        }
        if(targetData) {
            [self.listenerArray removeObject:targetData];
        }
    }
}

- (void)scrollToComponent:(WXComponent *)component withOffset:(CGFloat)offset animated:(BOOL)animated
{
    UIScrollView *scrollView = (UIScrollView *)self.view;
    // http://dotwe.org/vue/aa1af34e5fc745c0f1520e346904682a
    // ignore scroll action if contentSize smaller than scroller frame
    if (_scrollDirection == WXScrollDirectionVertical && scrollView.contentSize.height < scrollView.frame.size.height) {
        return;
    }
    if (_scrollDirection == WXScrollDirectionHorizontal && scrollView.contentSize.width < scrollView.frame.size.width) {
        return;
    }


    CGPoint contentOffset = scrollView.contentOffset;
    CGFloat scaleFactor = self.weexInstance.pixelScaleFactor;
    
    if (_scrollDirection == WXScrollDirectionHorizontal) {
        CGFloat contentOffetX = 0;
        if (self.isDirectionRTL && component.supercomponent != self) {
            contentOffetX = [component.supercomponent.view convertPoint:CGPointMake(CGRectGetMaxX(component.view.frame), component.view.frame.origin.y) toView:self.view].x;
        } else {
            contentOffetX = [component.supercomponent.view convertPoint:component.view.frame.origin toView:self.view].x;
        }
        contentOffetX += offset * scaleFactor;
        
        if (scrollView.contentSize.width >= scrollView.frame.size.width && contentOffetX > scrollView.contentSize.width - scrollView.frame.size.width) {
            contentOffset.x = scrollView.contentSize.width - scrollView.frame.size.width;
        } else {
            contentOffset.x = contentOffetX;
        }
    } else {
        CGFloat contentOffetY = [component.supercomponent.view convertPoint:component.view.frame.origin toView:self.view].y;
        contentOffetY += offset * scaleFactor;
        
        if (scrollView.contentSize.height >= scrollView.frame.size.height && contentOffetY > scrollView.contentSize.height - scrollView.frame.size.height) {
            contentOffset.y = scrollView.contentSize.height - scrollView.frame.size.height;
        } else {
            contentOffset.y = contentOffetY;
        }
    }
    
    [scrollView setContentOffset:contentOffset animated:animated];
}

- (BOOL)isNeedLoadMore
{
    if (WXScrollDirectionVertical == _scrollDirection) {
        if (_loadMoreOffset >= 0.0 && ((UIScrollView *)self.view).contentOffset.y >= 0) {
            return _previousLoadMoreContentHeight != ((UIScrollView *)self.view).contentSize.height && ((UIScrollView *)self.view).contentSize.height - ((UIScrollView *)self.view).contentOffset.y -  self.view.frame.size.height <= _loadMoreOffset;
        }
    } else if (WXScrollDirectionHorizontal == _scrollDirection) {
        if (_loadMoreOffset >= 0.0 && ((UIScrollView *)self.view).contentOffset.x >= 0) {
            return _previousLoadMoreContentHeight != ((UIScrollView *)self.view).contentSize.width && ((UIScrollView *)self.view).contentSize.width - ((UIScrollView *)self.view).contentOffset.x -  self.view.frame.size.width <= _loadMoreOffset;
        }
    }
    
    return NO;
}

- (void)loadMore
{
    [self fireEvent:@"loadmore" params:nil];
    
    if (WXScrollDirectionVertical == _scrollDirection) {
        _previousLoadMoreContentHeight = ((UIScrollView *)self.view).contentSize.height;
    } else if (WXScrollDirectionHorizontal == _scrollDirection) {
        _previousLoadMoreContentHeight = ((UIScrollView *)self.view).contentSize.width;
    }
}

- (CGPoint)contentOffset
{
    CGPoint rtv = CGPointZero;
    UIScrollView *scrollView = (UIScrollView *)self.view;
    if (scrollView) {
        rtv = scrollView.contentOffset;
    }
    return rtv;
}

- (void)setContentOffset:(CGPoint)contentOffset
{
    UIScrollView *scrollView = (UIScrollView *)self.view;
    [scrollView setContentOffset:contentOffset];
}

- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
{
    UIScrollView *scrollView = (UIScrollView *)self.view;
    [scrollView setContentOffset:contentOffset animated:animated];
}

- (CGSize)contentSize
{
    return ((UIScrollView *)self.view).contentSize;
}

- (void)setContentSize:(CGSize)size
{
    UIScrollView *scrollView = (UIScrollView *)self.view;
    scrollView.contentSize = size;
}

- (UIEdgeInsets)contentInset
{
    UIEdgeInsets rtv = UIEdgeInsetsZero;
    UIScrollView *scrollView = (UIScrollView *)self.view;
    if (scrollView) {
        rtv = scrollView.contentInset;
    }
    return rtv;
}

- (void)setContentInset:(UIEdgeInsets)contentInset
{
    UIScrollView *scrollView = (UIScrollView *)self.view;
    [scrollView setContentInset:contentInset];
}

- (void)addScrollDelegate:(id<UIScrollViewDelegate>)delegate
{
    if (delegate) {
        if (!_delegates) {
            _delegates = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
        }
        [_delegates addObject:delegate];
    }
}

- (void)removeScrollDelegate:(id<UIScrollViewDelegate>)delegate
{
    if (delegate) {
        [_delegates removeObject:delegate];
    }
}

- (WXScrollDirection)scrollDirection
{
    return _scrollDirection;
}

- (NSString*)refreshType
{
    return _refreshType;
}
- (BOOL)requestGestureShouldStopPropagation:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    return [self gestureShouldStopPropagation:gestureRecognizer shouldReceiveTouch:touch];
}

#pragma mark UIScrollViewDelegate

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    _isDragging = YES;
    
    if ([_refreshType isEqualToString:@"refreshForAppear"] && _refreshComponent) {
        [_refreshComponent setIndicatorHidden:NO];
    }
    
    _scrollStartPoint = scrollView.contentOffset;
    
    if (_scrollStartEvent || _scrollEventListener) {
        CGFloat scaleFactor = self.weexInstance.pixelScaleFactor;
        NSDictionary *contentSizeData = @{@"width":@(scrollView.contentSize.width / scaleFactor),
                                          @"height":@(scrollView.contentSize.height / scaleFactor)};
        NSDictionary *contentOffsetData = @{@"x":@(-scrollView.contentOffset.x / scaleFactor),
                                            @"y":@(-scrollView.contentOffset.y / scaleFactor)};
        NSDictionary *params = @{@"contentSize":contentSizeData, @"contentOffset":contentOffsetData, @"isDragging":@(scrollView.isDragging)};
        
        if (_scrollStartEvent) {
            [self fireEvent:@"scrollstart" params:params domChanges:nil];
        }
        if (_scrollEventListener) {
            _scrollEventListener(self, @"scrollstart", params);
        }
    }
    
    NSHashTable *delegates = [_delegates copy];
    for (id<UIScrollViewDelegate> delegate in delegates) {
        if ([delegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]) {
            [delegate scrollViewWillBeginDragging:scrollView];
        }
    }
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    //apply block which are registered
    WXSDKInstance *instance = self.weexInstance;
    if ([self.ref isEqualToString:WX_SDK_ROOT_REF] &&
        [self isKindOfClass:[WXScrollerComponent class]]) {
        if (instance.onScroll) {
            instance.onScroll(scrollView.contentOffset);
        }
    }
    
    if (_lastContentOffset.x > scrollView.contentOffset.x) {
        _direction = @"right";
    } else if (_lastContentOffset.x < scrollView.contentOffset.x) {
        _direction = @"left";
        if (WXScrollDirectionHorizontal == _scrollDirection) {
            [self handleLoadMore];
        }
    } else if(_lastContentOffset.y > scrollView.contentOffset.y) {
        _direction = @"down";
    } else if(_lastContentOffset.y < scrollView.contentOffset.y) {
        _direction = @"up";
        if (WXScrollDirectionVertical == _scrollDirection) {
            [self handleLoadMore];
        }
    }
    
    CGFloat scaleFactor = self.weexInstance.pixelScaleFactor;
    if (_isDragging) {
        // only trigger pullingDown event when user is dragging
        [_refreshComponent pullingdown:@{
                                         REFRESH_DISTANCE_Y: @(fabs((scrollView.contentOffset.y - _lastContentOffset.y)/scaleFactor)),
                                         REFRESH_VIEWHEIGHT: @(_refreshComponent.view.frame.size.height/scaleFactor),
                                         REFRESH_PULLINGDISTANCE: @(fabs(scrollView.contentOffset.y/scaleFactor)),
                                         @"type":@"pullingdown"
                                         }];
    }
    _lastContentOffset = scrollView.contentOffset;
    // check sticky
    [self adjustSticky];
    [self handleAppear];
    
    if (self.onScroll) {
        self.onScroll(scrollView);
    }
    
    if (_scrollEvent || _scrollEventListener) {
        CGFloat distance = 0;
        if (_scrollDirection == WXScrollDirectionHorizontal) {
            distance = scrollView.contentOffset.x - _lastScrollEventFiredOffset.x;
        } else {
            distance = scrollView.contentOffset.y - _lastScrollEventFiredOffset.y;
        }
        if (fabs(distance) >= _offsetAccuracy) {
            NSDictionary *contentSizeData = @{@"width": @(scrollView.contentSize.width / scaleFactor),
                                              @"height": @(scrollView.contentSize.height / scaleFactor)};
            //contentOffset values are replaced by (-contentOffset.x,-contentOffset.y), in order to be consistent with Android client.
            NSDictionary *contentOffsetData = @{@"x": @(-scrollView.contentOffset.x / scaleFactor),
                                                @"y": @(-scrollView.contentOffset.y / scaleFactor)};
            
            if (_scrollEvent) {
                [self fireEvent:@"scroll" params:@{@"contentSize":contentSizeData, @"contentOffset":contentOffsetData} domChanges:nil];
            }
            if (_scrollEventListener) {
                _scrollEventListener(self, @"scroll", @{@"contentSize":contentSizeData, @"contentOffset":contentOffsetData});
            }
            _lastScrollEventFiredOffset = scrollView.contentOffset;
        }
    }
    
    NSHashTable *delegates = [_delegates copy];
    for (id<UIScrollViewDelegate> delegate in delegates) {
        if ([delegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
            [delegate scrollViewDidScroll:scrollView];
        }
    }
}

- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
    [self dispatchScrollEndEvent:scrollView];
    
    UIEdgeInsets inset = [scrollView contentInset];
    
//  currently only set contentInset when loading
//    if ([_refreshComponent displayState]) {
//        inset.top = _refreshComponent.view.frame.size.height;
//    }
//    else {
//        inset.top = 0;
//    }
    
    if ([_loadingComponent displayState]) {
        inset.bottom = _loadingComponent.view.frame.size.height;
    } else {
        inset.bottom = 0;
    }
    [scrollView setContentInset:inset];
    
    NSHashTable *delegates = [_delegates copy];
    for (id<UIScrollViewDelegate> delegate in delegates) {
        if ([delegate respondsToSelector:@selector(scrollViewDidEndScrollingAnimation:)]) {
            [delegate scrollViewDidEndScrollingAnimation:scrollView];
        }
    }
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    if (!_isScrolling) {
        [self dispatchScrollEndEvent:scrollView];
        _scrollEndPoint = scrollView.contentOffset;
        id<WXPageEventNotifyEventProtocol> eventNotify = [WXSDKEngine handlerForProtocol:@protocol(WXPageEventNotifyEventProtocol)];
        if ([eventNotify respondsToSelector:@selector(notifyScrollEvent:from:to:)]) {
            [eventNotify notifyScrollEvent:self.weexInstance.instanceId from:_scrollStartPoint to:_scrollEndPoint];
        }
    }
    
    NSHashTable *delegates = [_delegates copy];
    for (id<UIScrollViewDelegate> delegate in delegates) {
        if ([delegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]) {
            [delegate scrollViewDidEndDecelerating:scrollView];
        }
    }
}

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
    // Page stop effect
    if (_pagingEnabled && _pageSize > 0) {
        if (_scrollDirection == WXScrollDirectionVertical) {
            CGFloat targetY = scrollView.contentOffset.y + velocity.y * 120.0;
            CGFloat targetIndex = round(targetY / _pageSize);
            
            /*
             When user's finger departs from screen with any velocity (like swipe gesture).
             We make sure that target index is changed.
             */
            CGFloat sourceIndex = round(_scrollStartPoint.y / _pageSize);
            if (velocity.y > 0.3) {
                if (targetIndex <= sourceIndex) {
                    targetIndex = sourceIndex + 1;
                }
            }
            else if (velocity.y < -0.3) {
                if (targetIndex >= sourceIndex) {
                    targetIndex = sourceIndex - 1;
                }
            }
            
            if (targetIndex < 0)
                targetIndex = 0;
            
            targetContentOffset->y = targetIndex * _pageSize;
        }
        else {
            CGFloat targetX = scrollView.contentOffset.x + velocity.x * 120.0;
            CGFloat targetIndex = round(targetX / _pageSize);
            
            /*
             When user's finger departs from screen with any velocity (like swipe gesture).
             We make sure that target index is changed.
             */
            CGFloat sourceIndex = round(_scrollStartPoint.x / _pageSize);
            if (velocity.x > 0.3) {
                if (targetIndex <= sourceIndex) {
                    targetIndex = sourceIndex + 1;
                }
            }
            else if (velocity.x < -0.3) {
                if (targetIndex >= sourceIndex) {
                    targetIndex = sourceIndex - 1;
                }
            }
            
            if (targetIndex < 0)
                targetIndex = 0;
            targetContentOffset->x = targetIndex * _pageSize;
        }
    }
    
    if ([_refreshType isEqualToString:@"refreshForAppear"]) {
        if(targetContentOffset == nil)
            return;
        CGPoint offset = *targetContentOffset;
        if(velocity.y <= 0) {
            // drop down
            if( offset.y <= _refreshComponent.calculatedFrame.size.height ) {
                [self loadMoreIfNeed];
            }
        }
    }
    
    NSHashTable *delegates = [_delegates copy];
    for (id<UIScrollViewDelegate> delegate in delegates) {
        if ([delegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
            [delegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
        }
    }
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    [_loadingComponent.view setHidden:NO];
    [_refreshComponent.view setHidden:NO];
    
    //refresh
    if ([_refreshType isEqualToString:@"refreshForWholeVisible"]) {
        if (_refreshComponent && scrollView.contentOffset.y < 0 && scrollView.contentOffset.y + _refreshComponent.calculatedFrame.size.height < _refreshComponent.calculatedFrame.origin.y) {
            [_refreshComponent refresh];
        }
    }
    
    //loading
    if (_loadingComponent && scrollView.contentOffset.y > 0 &&
        scrollView.contentOffset.y + scrollView.frame.size.height > _loadingComponent.view.frame.origin.y + _loadingComponent.calculatedFrame.size.height) {
        [_loadingComponent loading];
    }
    if (!decelerate) {
        _isScrolling = NO;
        [self performSelector:@selector(scrollViewDidEndDecelerating:) withObject:scrollView afterDelay:0.1];
    }
    
    NSHashTable *delegates = [_delegates copy];
    for (id<UIScrollViewDelegate> delegate in delegates) {
        if ([delegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]) {
            [delegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
        }
    }
    
    _isDragging = NO;
}

- (void)loadMoreIfNeed
{
    WXScrollerComponentView* scrollView = (WXScrollerComponentView *)self.view;
    if (scrollView.isDragging || scrollView.isTracking || scrollView.isDecelerating) {
        [self performSelector:@selector(loadMoreIfNeed) withObject:nil afterDelay:0.1];
        return;
    }
    [_refreshComponent refresh];
}

- (void)handleAppear
{
    if (![self isViewLoaded]) {
        return;
    }
    UIScrollView *scrollView = (UIScrollView *)self.view;
    CGFloat vx = scrollView.contentInset.left + scrollView.contentOffset.x;
    CGFloat vy = scrollView.contentInset.top + scrollView.contentOffset.y;
    CGFloat vw = scrollView.frame.size.width - scrollView.contentInset.left - scrollView.contentInset.right;
    CGFloat vh = scrollView.frame.size.height - scrollView.contentInset.top - scrollView.contentInset.bottom;
    CGRect scrollRect = CGRectMake(vx, vy, vw, vh);;
    
    // notify action for appear
    NSArray *listenerArrayCopy = [self.listenerArray copy];
    for(WXScrollToTarget *target in listenerArrayCopy){
        if (_shouldNotifiAppearDescendantView) {
            // if target component is descendant of scrollerview, it should notify the appear event handler, or here will skip this appear calculation.
            if ([target.target isViewLoaded] && [target.target.view isDescendantOfView:self.view]) {
                [self scrollToTarget:target scrollRect:scrollRect];
            }
        } else {
            [self scrollToTarget:target scrollRect:scrollRect];
        }
    }
}

- (CGPoint)absolutePositionForComponent:(WXComponent *)component
{
    return [component->_view.superview convertPoint:component->_view.frame.origin toView:_view];
}

#pragma mark  Private Methods
- (void)dispatchScrollEndEvent:(UIScrollView *)scrollView
{
    if (_scrollEndEvent || _scrollEventListener) {
        CGFloat scaleFactor = self.weexInstance.pixelScaleFactor;
        NSDictionary *contentSizeData = @{@"width":@(scrollView.contentSize.width / scaleFactor),
                                          @"height":@(scrollView.contentSize.height / scaleFactor)};
        NSDictionary *contentOffsetData = @{@"x":@(-scrollView.contentOffset.x / scaleFactor),
                                            @"y":@(-scrollView.contentOffset.y / scaleFactor)};
        if (_scrollEndEvent) {
            [self fireEvent:@"scrollend" params:@{@"contentSize":contentSizeData, @"contentOffset":contentOffsetData} domChanges:nil];
        }
        if (_scrollEventListener) {
            _scrollEventListener(self, @"scrollend", @{@"contentSize":contentSizeData, @"contentOffset":contentOffsetData});
        }
    }
}

- (void)scrollToTarget:(WXScrollToTarget *)target scrollRect:(CGRect)rect
{
    WXComponent *component = target.target;
    if (![component isViewLoaded]) { 
        return;
    }
    
    CGFloat ctop;
    if (component && component->_view && component->_view.superview) {
        ctop = [self absolutePositionForComponent:component].y;
    } else {
        ctop = 0.0;
    }
    CGFloat cbottom = ctop + CGRectGetHeight(component.calculatedFrame);
    CGFloat cleft;
    if (component && component->_view && component->_view.superview) {
        cleft = [self absolutePositionForComponent:component].x;
    } else {
        cleft = 0.0;
    }
    CGFloat cright = cleft + CGRectGetWidth(component.calculatedFrame);
    
    CGFloat vtop = CGRectGetMinY(rect), vbottom = CGRectGetMaxY(rect), vleft = CGRectGetMinX(rect), vright = CGRectGetMaxX(rect);
    if(cbottom > vtop && ctop <= vbottom && cleft <= vright && cright > vleft){
        if(!target.hasAppear && component){
            target.hasAppear = YES;
            if (component->_appearEvent) {
//                NSLog(@"appear:%@, %.2f", component, ctop);
                [component fireEvent:@"appear" params:_direction ? @{@"direction":_direction} : nil];
            }
        }
    } else {
        if(target.hasAppear && component){
            target.hasAppear = NO;
            if(component->_disappearEvent){
//                NSLog(@"disappear:%@", component);
                [component fireEvent:@"disappear" params:_direction ? @{@"direction":_direction} : nil];
            }
        }
    }
}

- (void)handleLoadMore
{
    if (_listenLoadMore && [self isNeedLoadMore]) {
        [self loadMore];
    }
}

#pragma mark Layout

- (CGFloat)_getInnerContentMainSize
{
    if (_scrollDirection == WXScrollDirectionVertical) {
        return _contentSize.height;
    }
    else if (_scrollDirection == WXScrollDirectionHorizontal) {
        return _contentSize.width;
    }
    else {
        return -1.0f;
    }
}

- (void)_assignInnerContentMainSize:(CGFloat)value
{
    if (_scrollDirection == WXScrollDirectionVertical) {
        _contentSize.height = value;
    }
    else if (_scrollDirection == WXScrollDirectionHorizontal) {
        _contentSize.width = value;
    }
}

- (void)_layoutPlatform
{
    /* Handle multiple vertical scrollers inside horizontal scroller case. In weexcore,
     a verticall list with NAN height will be set flex=1, which suppresses its style-width property.
     This will cause two lists with style-width 750px in a horizontal scroller sharing one screen width.
     Here we respect its style-width property so that the two lists will both be screen width wide. */
    
    if (_needsPlatformLayout) {
        if (_flexCssNode) {
            float top = _flexCssNode->getLayoutPositionTop();
            float left = _flexCssNode->getLayoutPositionLeft();
            float width = _flexCssNode->getLayoutWidth();
            float height = _flexCssNode->getLayoutHeight();

            if (_scrollDirection == WXScrollDirectionVertical) {
                _flexCssNode->setFlexDirection(WeexCore::kFlexDirectionColumn, NO);
                _flexCssNode->setStyleWidth(_flexCssNode->getLayoutWidth(), NO);
                _flexCssNode->setStyleHeight(FlexUndefined);
            } else {
                _flexCssNode->setFlexDirection(WeexCore::kFlexDirectionRow, NO);
                _flexCssNode->setStyleHeight(_flexCssNode->getLayoutHeight());
                _flexCssNode->setStyleWidth(FlexUndefined, NO);
            }

            _flexCssNode->markAllDirty();
            
            // this is scroll rtl solution.
            // scroll layout not use direction, use self tranform
            // but we need inherit direction in CSS, so we set children layout diretion manually
            _flexCssNode->determineChildLayoutDirection(_flexCssNode->getLayoutDirectionFromPathNode());
            
            std::pair<float, float> renderPageSize;
            renderPageSize.first = self.weexInstance.frame.size.width;
            renderPageSize.second = self.weexInstance.frame.size.height;
            auto parent = _flexCssNode->getParent(); // clear parent temporarily
            _flexCssNode->setParent(nullptr, _flexCssNode);
            _flexCssNode->calculateLayout(renderPageSize);
            _flexCssNode->setParent(parent, _flexCssNode);
            
            /* We must clear BFCs becuase we have set parent of _flexCSSNode to nullptr and
             manually called its calculateLayout method. This will cause a non-bfc layout node
             to have items in its BFCs vector. Later, a wild pointer may cause crash. */
            _flexCssNode->clearBFCs();
            
            // set origin and size back
            _flexCssNode->rewriteLayoutResult(left, top, width, height);
        }
    }
    else {
        // should not happen, set platform layout to false
        _flexCssNode->setNeedsPlatformDependentLayout(false);
    }
}

@end
