/*
 * 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 "WXSliderNeighborComponent.h"
#import "WXConvert.h"
#import "WXUtility.h"
#import "WXComponent_internal.h"
#import "WXIndicatorComponent.h"
#import "WXSDKInstance.h"
#import "NSTimer+Weex.h"
#import "WXComponent+Layout.h"

#define MAX_VISIBLE_ITEMS 30
#define MIN_TOGGLE_DURATION 0.2
#define MAX_TOGGLE_DURATION 0.4
#define SCROLL_DURATION 0.4
#define INSERT_DURATION 0.4
#define DECELERATE_THRESHOLD 0.1
#define SCROLL_SPEED_THRESHOLD 2.0
#define SCROLL_DISTANCE_THRESHOLD 0.1
#define DECELERATION_MULTIPLIER 30.0
#define FLOAT_ERROR_MARGIN 0.000001

@protocol WXSliderNeighborDelegate, WXSliderNeighborDataSource;

@interface WXSliderNeighborView: UIView
@property (nonatomic, strong) WXIndicatorView *indicator;
@property (nonatomic, weak) id<WXSliderNeighborDelegate> delegate;
@property (nonatomic, weak) id<WXSliderNeighborDataSource> dataSource;
@property (nonatomic, assign) CGFloat perspective;
@property (nonatomic, readonly, getter = isDragging) BOOL dragging;
@property (nonatomic, readonly, getter = isScrolling) BOOL scrolling;
@property (nonatomic, assign) CGFloat autoscroll;
@property (nonatomic, assign) CGFloat scrollOffset;
@property (nonatomic, assign) CGFloat previousTranslation;
@property (nonatomic, assign, getter = isVertical) BOOL vertical;
@property (nonatomic, assign) CGFloat decelerationRate;
@property (nonatomic, assign, getter = isScrollEnabled) BOOL scrollEnabled;
@property (nonatomic, assign, getter = isPagingEnabled) BOOL pagingEnabled;
@property (nonatomic, assign) BOOL bounces;
@property (nonatomic, assign) BOOL didDrag;
@property (nonatomic, readonly, getter = isDecelerating) BOOL decelerating;
@property (nonatomic, strong, readonly) UIView *contentView;
@property (nonatomic, strong) NSMutableDictionary *itemViews;
@property (nonatomic, readonly) NSInteger numberOfVisibleItems;
@property (nonatomic, readonly) NSInteger numberOfItems;
@property (nonatomic, readonly) NSInteger numberOfPlaceholders;
@property (nonatomic, strong) NSMutableSet *itemViewPool;
@property (nonatomic, strong) NSMutableSet *placeholderViewPool;
@property (nonatomic, assign) NSTimeInterval startTime;
@property (nonatomic, assign) NSTimeInterval lastTime;
@property (nonatomic, assign) CGFloat startVelocity;
@property (nonatomic, assign) CGFloat offsetMultiplier;
@property (nonatomic, assign) CGFloat startOffset;
@property (nonatomic, assign) CGFloat endOffset;
@property (nonatomic, assign) CGSize contentOffset;
@property (nonatomic, assign) CGSize viewpointOffset;
@property (nonatomic, assign) CGFloat scrollSpeed;
@property (nonatomic, assign) CGFloat bounceDistance;
@property (nonatomic, assign) BOOL stopAtItemBoundary;
@property (nonatomic, assign) BOOL scrollToItemBoundary;
@property (nonatomic, assign) BOOL centerItemWhenSelected;
@property (nonatomic, assign) NSTimeInterval scrollDuration;
@property (nonatomic, readonly, getter = isWrapEnabled) BOOL wrapEnabled;
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, assign) NSInteger numberOfPlaceholdersToShow;
@property (nonatomic, assign) CGFloat previousScrollOffset;
@property (nonatomic, assign) NSTimeInterval toggleTime;
@property (nonatomic, readonly) CGFloat toggle;
@property (nonatomic, assign) NSInteger previousItemIndex;
@property (nonatomic, readonly) CGFloat itemWidth;
@property (nonatomic, assign) BOOL inited;
@end

@implementation WXSliderNeighborView

- (instancetype)init {
    
    self = [super init];
    _decelerationRate = 0.01;
    _scrollEnabled = YES;
    _bounces = YES;
    _offsetMultiplier = 1.0;
    _perspective = -1.0/500.0;
    _contentOffset = CGSizeZero;
    _viewpointOffset = CGSizeZero;
    _scrollSpeed = 1.0;
    _bounceDistance = 1.0;
    _stopAtItemBoundary = YES;
    _scrollToItemBoundary = YES;
    _centerItemWhenSelected = YES;
    _inited = NO;
    
    _contentView = [[UIView alloc] initWithFrame:self.bounds];
    
    //pan gesture recognizer
    self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(didPan:)];
    panGesture.delegate = (id <UIGestureRecognizerDelegate>)self;
    [_contentView addGestureRecognizer:panGesture];
    
    //tap gesture recognizer
    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTap:)];
    tapGesture.delegate = (id <UIGestureRecognizerDelegate>)self;
    [_contentView addGestureRecognizer:tapGesture];
    
    self.accessibilityTraits = UIAccessibilityTraitAllowsDirectInteraction;
    self.isAccessibilityElement = YES;
    
    [self addSubview:_contentView];
    
    if (_dataSource) {
        [self reloadData];
    }
    
    return self;
}

- (void)dealloc
{
    [self stopAnimation];
}

- (void)accessibilityIncrement
{
    [self setCurrentItemIndex: [self lastItemIndex]];
}

- (void)accessibilityDecrement
{
    [self setCurrentItemIndex:[self nextItemIndex]];
}

- (void)setDataSource:(id<WXSliderNeighborDataSource>)dataSource
{
    if (_dataSource != dataSource) {
        _dataSource = dataSource;
        if (_dataSource) {
            [self reloadData];
        }
    }
}

- (void)setDelegate:(id<WXSliderNeighborDelegate>)delegate
{
    if (_delegate != delegate) {
        _delegate = delegate;
        if (_delegate && _dataSource) {
            [self setNeedsLayout];
        }
    }
}

- (void)setVertical:(BOOL)vertical
{
    if (_vertical != vertical)
    {
        _vertical = vertical;
        [self layOutItemViews];
    }
}

#pragma clang diagnostic push
#pragma GCC diagnostic ignored "-Wundeclared-selector"
- (void)setCurrentItemIndex:(NSInteger)currentItemIndex
{
    if ([self currentItemIndex] == currentItemIndex) return;
    
    [self setScrollOffset:currentItemIndex];
    [self.indicator setCurrentPoint:currentItemIndex];
    
    if (self.delegate && [self.delegate respondsToSelector:@selector(sliderView:didScrollToItemAtIndex:)]) {
        [self.delegate sliderNeighbor:self didScrollToItemAtIndex:currentItemIndex];
    }
    [self scroll2ItemViewAtIndex:currentItemIndex animated:YES];
    
}
#pragma clang diagnostic pop

- (void)updateItemWidth
{
    _itemWidth = [_delegate sliderNeighborItemWidth:self] ?: _itemWidth;
    if (_numberOfItems > 0) {
        if ([_itemViews count] == 0) {
            [self loadViewAtIndex:0];
        }
    } else if (_numberOfPlaceholders > 0) {
        if ([_itemViews count] == 0) {
            [self loadViewAtIndex:-1];
        }
    }
}

- (NSInteger)circularCarouselItemCount
{
    NSInteger count = 0;
    CGFloat spacing = [self valueForOption:WXSliderNeighborOptionSpacing withDefault:1.0];
    CGFloat width = _vertical ? self.bounds.size.height: self.bounds.size.width;
    count = MIN(MAX_VISIBLE_ITEMS, MAX(12, ceil(width / (spacing * _itemWidth)) * M_PI));
    count = MIN(_numberOfItems + _numberOfPlaceholdersToShow, count);
    return [self valueForOption:WXSliderNeighborOptionCount withDefault:count];
}

- (void)updateNumberOfVisibleItems
{
    //get number of visible items
    //based on count value
    CGFloat spacing = [self valueForOption:WXSliderNeighborOptionSpacing withDefault:1.0];
    CGFloat width = _vertical ? self.bounds.size.height: self.bounds.size.width;
    CGFloat itemWidth = _itemWidth * spacing;
    _numberOfVisibleItems = ceil(width / itemWidth) + 2;
    _numberOfVisibleItems = MIN(MAX_VISIBLE_ITEMS, _numberOfVisibleItems);
    _numberOfVisibleItems = [self valueForOption:WXSliderNeighborOptionVisibleItems withDefault:_numberOfVisibleItems];
    _numberOfVisibleItems = MAX(0, MIN(_numberOfVisibleItems, _numberOfItems + _numberOfPlaceholdersToShow));
    
}

- (CGFloat)offsetForItemAtIndex:(NSInteger)index
{
    //calculate relative position
    CGFloat offset = index - _scrollOffset;
    if (_wrapEnabled) {
        if (offset > _numberOfItems/2.0) {
            offset -= _numberOfItems;
        } else if (offset < -_numberOfItems/2.0) {
            offset += _numberOfItems;
        }
    }
    
    return offset;
}

- (CGFloat)alphaForItemWithOffset:(CGFloat)offset
{
    CGFloat fadeMin = -INFINITY;
    CGFloat fadeMax = INFINITY;
    CGFloat fadeRange = 1.0;
    CGFloat fadeMinAlpha = 0.0;
    fadeMin = [self valueForOption:WXSliderNeighborOptionFadeMin withDefault:fadeMin];
    fadeMax = [self valueForOption:WXSliderNeighborOptionFadeMax withDefault:fadeMax];
    fadeRange = [self valueForOption:WXSliderNeighborOptionFadeRange withDefault:fadeRange];
    fadeMinAlpha = [self valueForOption:WXSliderNeighborOptionFadeMinAlpha withDefault:fadeMinAlpha];
    
    CGFloat factor = 0.0;
    if (offset > fadeMax) {
        factor = offset - fadeMax;
    } else if (offset < fadeMin) {
        factor = fadeMin - offset;
    }
    return 1.0 - MIN(factor, fadeRange) / fadeRange * (1.0 - fadeMinAlpha);
}


- (UIView *)containView:(UIView *)view
{
    //set item width
    if (!_itemWidth) {
        _itemWidth = _vertical? view.frame.size.height: view.frame.size.width;
    }
    
    //set container frame
    CGRect frame = view.frame;
    frame.size.width = _vertical? frame.size.width: _itemWidth;
    frame.size.height = _vertical? _itemWidth: frame.size.height;
    UIView *containerView = [[UIView alloc] initWithFrame:frame];
    
    //set view frame
    frame = view.frame;
    frame.origin.x = (containerView.bounds.size.width - frame.size.width) / 2.0;
    frame.origin.y = (containerView.bounds.size.height - frame.size.height) / 2.0;
    view.frame = frame;
    [containerView addSubview:view];
    containerView.layer.opacity = 0;
    containerView.transform = CGAffineTransformMakeScale(0.8, 1.0);
    return containerView;
}

- (void)transformItemViews
{
    for (NSNumber *number in _itemViews)
    {
        NSInteger index = [number integerValue];
        UIView *view = _itemViews[number];
        [self transformItemView:view atIndex:index];
    }
}

- (CATransform3D)transformForItemViewWithOffset:(CGFloat)offset
{
    //set up base transform
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = _perspective;
    transform = CATransform3DTranslate(transform, -_viewpointOffset.width, -_viewpointOffset.height, 0.0);
    
    //perform transforms
    CGFloat spacing = [self valueForOption:WXSliderNeighborOptionSpacing withDefault:1.0];
    if (_vertical) {
        return CATransform3DTranslate(transform, 0.0, offset * _itemWidth * spacing, 0.0);
    } else {
        return CATransform3DTranslate(transform, offset * _itemWidth * spacing, 0.0, 0.0);
    }
}

- (void)transformItemView:(UIView *)view atIndex:(NSInteger)index
{
    //calculate offset
    CGFloat offset = [self offsetForItemAtIndex:index];
    
    //update alpha
    view.superview.layer.opacity = [self alphaForItemWithOffset:offset];
    
    //center view
    view.superview.center = CGPointMake(self.bounds.size.width/2.0 + _contentOffset.width,
                                        self.bounds.size.height/2.0 + _contentOffset.height);
    
    //enable/disable interaction
    view.superview.userInteractionEnabled = (!_centerItemWhenSelected || index == self.currentItemIndex);
    view.superview.layer.rasterizationScale = [UIScreen mainScreen].scale;
    
    [view layoutIfNeeded];
    
    CGFloat clampedOffset = MAX(-1.0, MIN(1.0, offset));
    if (_decelerating || (_scrolling && !_dragging && !_didDrag) || (_autoscroll && !_dragging) ||
        (!_wrapEnabled && (_scrollOffset < 0 || _scrollOffset >= _numberOfItems - 1))) {
        if (offset > 0) {
            _toggle = (offset <= 0.5)? -clampedOffset: (1.0 - clampedOffset);
        } else {
            _toggle = (offset > -0.5)? -clampedOffset: (- 1.0 - clampedOffset);
        }
    }
    
    //calculate transform
    CATransform3D transform = [self transformForItemViewWithOffset:offset];
    
    //transform view
    view.superview.layer.transform = transform;
    
    //backface culling
    BOOL showBackfaces = view.layer.doubleSided;
    if (showBackfaces) {
        showBackfaces = YES;
    }
    showBackfaces = !![self valueForOption:WXSliderNeighborOptionShowBackfaces withDefault:showBackfaces];
    
    view.superview.hidden = !(showBackfaces ?: (transform.m33 > 0.0));
}

NSComparisonResult sliderNeighorCompareViewDepth(UIView *view1, UIView *view2, WXSliderNeighborView *self)
{
    //compare depths
    CATransform3D t1 = view1.superview.layer.transform;
    CATransform3D t2 = view2.superview.layer.transform;
    CGFloat z1 = t1.m13 + t1.m23 + t1.m33 + t1.m43;
    CGFloat z2 = t2.m13 + t2.m23 + t2.m33 + t2.m43;
    CGFloat difference = z1 - z2;
    
    //if depths are equal, compare distance from current view
    if (difference == 0.0) {
        CATransform3D t3 = [self currentItemView].superview.layer.transform;
        if (self.vertical) {
            CGFloat y1 = t1.m12 + t1.m22 + t1.m32 + t1.m42;
            CGFloat y2 = t2.m12 + t2.m22 + t2.m32 + t2.m42;
            CGFloat y3 = t3.m12 + t3.m22 + t3.m32 + t3.m42;
            difference = fabs(y2 - y3) - fabs(y1 - y3);
        } else {
            CGFloat x1 = t1.m11 + t1.m21 + t1.m31 + t1.m41;
            CGFloat x2 = t2.m11 + t2.m21 + t2.m31 + t2.m41;
            CGFloat x3 = t3.m11 + t3.m21 + t3.m31 + t3.m41;
            difference = fabs(x2 - x3) - fabs(x1 - x3);
        }
    }
    return (difference < 0.0)? NSOrderedAscending: NSOrderedDescending;
}

- (void)depthSortViews
{
    for (UIView *view in [[_itemViews allValues] sortedArrayUsingFunction:(NSInteger (*)(id, id, void *))sliderNeighorCompareViewDepth context:(__bridge void *)self])
    {
        [_contentView bringSubviewToFront:(UIView *__nonnull)view.superview];
    }
}

- (void)layOutItemViews
{
    //bail out if not set up yet
    if (!_dataSource || !_contentView) {
        return;
    }
    
    //update wrap
    _wrapEnabled = YES;
    _wrapEnabled = !![self valueForOption:WXSliderNeighborOptionWrap withDefault:_wrapEnabled];
    
    //no placeholders on wrapped carousels
    _numberOfPlaceholdersToShow = _wrapEnabled? 0: _numberOfPlaceholders;
    
    //set item width
    [self updateItemWidth];
    
    //update number of visible items
    [self updateNumberOfVisibleItems];
    
    //prevent false index changed event
    _previousScrollOffset = self.scrollOffset;
    
    //update offset multiplier
    _offsetMultiplier = 1.0;
    _offsetMultiplier = [self valueForOption:WXSliderNeighborOptionOffsetMultiplier withDefault:_offsetMultiplier];
    
    //align
    if (!_scrolling && !_decelerating && !_autoscroll) {
        if (_scrollToItemBoundary && self.currentItemIndex != -1) {
            [self scroll2ItemViewAtIndex:self.currentItemIndex animated:YES];
        } else {
            _scrollOffset = [self clampedOffset:_scrollOffset];
        }
    }
    //update views
    if (_inited) {
        [self didScroll];
    }
    
    _inited = YES;
}

- (CGFloat)valueForOption:(WXSliderNeighborOption)option withDefault:(CGFloat)value
{
    if (_delegate && [_delegate respondsToSelector:@selector(sliderNeighbor:valueForOption:withDefault:)]) {
        return [_delegate sliderNeighbor:self valueForOption:option withDefault:value];
    }
    return value;
}

#pragma mark -
#pragma mark Gestures and taps

- (NSInteger)viewOrSuperviewIndex:(UIView *)view
{
    if (view == nil || view == _contentView) {
        return NSNotFound;
    }
    NSInteger index = [self indexOfItemView:view];
    if (index == NSNotFound) {
        return [self viewOrSuperviewIndex:view.superview];
    }
    return index;
}

- (BOOL)viewOrSuperview:(UIView *)view implementsSelector:(SEL)selector
{
    if (!view || view == self.contentView) {
        return NO;
    }
    
    Class viewClass = [view class];
    while (viewClass && viewClass != [UIView class])
    {
        unsigned int numberOfMethods;
        Method *methods = class_copyMethodList(viewClass, &numberOfMethods);
        for (unsigned int i = 0; i < numberOfMethods; i++)
        {
            if (method_getName(methods[i]) == selector) {
                free(methods);
                return YES;
            }
        }
        if (methods) free(methods);
        viewClass = [viewClass superclass];
    }
    
    return [self viewOrSuperview:view.superview implementsSelector:selector];
}

- (id)viewOrSuperview:(UIView *)view ofClass:(Class)swizzClass
{
    if (!view || view == self.contentView) {
        return nil;
    } else if ([view isKindOfClass:swizzClass]) {
        return view;
    }
    return [self viewOrSuperview:view.superview ofClass:swizzClass];
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gesture shouldReceiveTouch:(UITouch *)touch
{
    if (_scrollEnabled) {
        _dragging = NO;
        _scrolling = NO;
        _decelerating = NO;
    }
    
    if ([gesture isKindOfClass:[UITapGestureRecognizer class]]) {
        //handle tap
        NSInteger index = [self viewOrSuperviewIndex:touch.view];
        if (index == NSNotFound && _centerItemWhenSelected) {
            //view is a container view
            index = [self viewOrSuperviewIndex:[touch.view.subviews lastObject]];
        }
        if (index != NSNotFound) {
            if ([self viewOrSuperview:touch.view implementsSelector:@selector(touchesBegan:withEvent:)]) {
                return NO;
            }
        }
    } else if ([gesture isKindOfClass:[UIPanGestureRecognizer class]]) {
        if (!_scrollEnabled) {
            return NO;
        } else if ([self viewOrSuperview:touch.view implementsSelector:@selector(touchesMoved:withEvent:)]) {
            UIScrollView *scrollView = [self viewOrSuperview:touch.view ofClass:[UIScrollView class]];
            if (scrollView) {
                return !scrollView.scrollEnabled ||
                (self.vertical && scrollView.contentSize.height <= scrollView.frame.size.height) ||
                (!self.vertical && scrollView.contentSize.width <= scrollView.frame.size.width);
            }
            if ([self viewOrSuperview:touch.view ofClass:[UIButton class]] ||
                [self viewOrSuperview:touch.view ofClass:[UIBarButtonItem class]]) {
                return YES;
            }
            return NO;
        }
    }
    return YES;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
   //if the view which the otherGestureRecognizer works on is a scrollview and also it is scrollEnabled vertically ,at this time,we should not block the guesture from being recognized by the otherGestureRecognize
    if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
        if ([otherGestureRecognizer.view isKindOfClass:[UIScrollView class]]) {
            UIScrollView* scrollview = (UIScrollView *)otherGestureRecognizer.view;
            if (scrollview.scrollEnabled) {
                UIPanGestureRecognizer* panRcgn= (UIPanGestureRecognizer *)gestureRecognizer;
                //check offset for confirming vertival movement
                if (fabs([panRcgn translationInView:panRcgn.view].y) > fabs([panRcgn translationInView:panRcgn.view].x)*16) {
                    return YES;
                }
            }
        }
    }
    return NO;
}


- (void)didPan:(UIPanGestureRecognizer *)panGesture
{
    if (_scrollEnabled && _numberOfItems) {
        switch (panGesture.state)
        {
            case UIGestureRecognizerStateBegan:
            {
                _dragging = YES;
                _scrolling = NO;
                _decelerating = NO;
                _previousTranslation = _vertical? [panGesture translationInView:self].y: [panGesture translationInView:self].x;
                
                [_delegate sliderNeighborWillBeginDragging:self];
                break;
            }
            case UIGestureRecognizerStateEnded:
            case UIGestureRecognizerStateCancelled:
            case UIGestureRecognizerStateFailed:
            {
                _dragging = NO;
                _didDrag = YES;
                if ([self shouldDecelerate]) {
                    _didDrag = NO;
                    [self startDecelerating];
                }
                
                [self pushAnimationState:YES];
                [_delegate sliderNeighborDidEndDragging:self willDecelerate:_decelerating];
                [self popAnimationState];
                
                if (!_decelerating) {
                    if ((_scrollToItemBoundary || fabs(_scrollOffset - [self clampedOffset:_scrollOffset]) > FLOAT_ERROR_MARGIN) && !_autoscroll) {
                        if (fabs(_scrollOffset - self.currentItemIndex) < FLOAT_ERROR_MARGIN) {
                            //call scroll to trigger events for legacy support reasons
                            //even though technically we don't need to scroll at all
                            [self scroll2ItemViewAtIndex:self.currentItemIndex duration:0.01];
                        } else {
                            [self scroll2ItemViewAtIndex:self.currentItemIndex animated:YES];
                        }
                    } else {
                        [self depthSortViews];
                    }
                } else {
                    [self pushAnimationState:YES];
                    [_delegate sliderNeighborWillBeginDecelerating:self];
                    [self popAnimationState];
                }
                break;
            }
            case UIGestureRecognizerStateChanged:
            {
                CGFloat translation = _vertical? [panGesture translationInView:self].y: [panGesture translationInView:self].x;
                CGFloat velocity = _vertical? [panGesture velocityInView:self].y: [panGesture velocityInView:self].x;
                
                CGFloat factor = 1.0;
                if (!_wrapEnabled && _bounces) {
                    factor = 1.0 - MIN(fabs(_scrollOffset - [self clampedOffset:_scrollOffset]),
                                       _bounceDistance) / _bounceDistance;
                }
                
                _startVelocity = -velocity * factor * _scrollSpeed / _itemWidth;
                _scrollOffset -= (translation - _previousTranslation) * factor * _offsetMultiplier / _itemWidth;
                _previousTranslation = translation;
                [self didScroll];
                break;
            }
            case UIGestureRecognizerStatePossible:
            {
                //do nothing
                break;
            }
        }
    }
}

- (void)didTap:(UITapGestureRecognizer *)tapGesture
{
    //check for tapped view
    NSInteger index = [self indexOfItemView:[self itemViewAtPoint:[tapGesture locationInView:_contentView]]];
    if (index != NSNotFound) {
        if (!_delegate || [_delegate sliderNeighbor:self shouldSelectItemAtIndex:index]) {
            if ((index != self.currentItemIndex && _centerItemWhenSelected) ||
                (index == self.currentItemIndex && _scrollToItemBoundary)) {
                [self scroll2ItemViewAtIndex:index animated:YES];
            }
            [_delegate sliderNeighbor:self didSelectItemAtIndex:index];
        } else if (_scrollEnabled && _scrollToItemBoundary && _autoscroll) {
            [self scroll2ItemViewAtIndex:self.currentItemIndex animated:YES];
        }
    } else {
        [self scroll2ItemViewAtIndex:self.currentItemIndex animated:YES];
    }
}

- (void)pushAnimationState:(BOOL)enabled
{
    [CATransaction begin];
    [CATransaction setDisableActions:!enabled];
}

- (void)popAnimationState
{
    [CATransaction commit];
}

- (void)reloadData
{
    //remove old views
    for (UIView *view in [_itemViews allValues])
    {
        [view.superview removeFromSuperview];
    }
    
    //bail out if not set up yet
    if (!_dataSource || !_contentView)
    {
        return;
    }
    
    //get number of items and placeholders
    _numberOfVisibleItems = 0;
    _numberOfItems = [_dataSource numberOfItemsInSliderNeighbor:self];
    if ([_dataSource respondsToSelector:@selector(numberOfPlaceholdersInsliderNeighbor:)]) {
        _numberOfPlaceholders = [_dataSource numberOfPlaceholdersInsliderNeighbor:self];
    }
    
    //reset view pools
    self.itemViews = [NSMutableDictionary dictionary];
    self.itemViewPool = [NSMutableSet set];
    self.placeholderViewPool = [NSMutableSet setWithCapacity:_numberOfPlaceholders];
    
    //layout views
    [self setNeedsLayout];
    
    //fix scroll offset
    if (_numberOfItems > 0 && _scrollOffset < 0.0)
    {
        [self scroll2ItemViewAtIndex:0 animated:(_numberOfPlaceholders > 0)];
    }
}

#pragma mark scrolling

- (NSInteger)currentItemIndex
{
    return [self clampedIndex:round(_scrollOffset)];
}

- (NSInteger)nextItemIndex
{
    return ([self currentItemIndex]+1)%_numberOfItems;
}

- (NSInteger)lastItemIndex
{
    if ([self currentItemIndex] == 0) {
        return _numberOfItems - 1;
    }
    return ((NSInteger)round(abs((int)[self currentItemIndex] - 1))) % _numberOfItems;
}

- (NSInteger)minScrollDistanceFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex
{
    NSInteger directDistance = toIndex - fromIndex;
    if (_wrapEnabled)
    {
        NSInteger wrappedDistance = MIN(toIndex, fromIndex) + _numberOfItems - MAX(toIndex, fromIndex);
        if (fromIndex < toIndex)
        {
            wrappedDistance = -wrappedDistance;
        }
        return (ABS(directDistance) <= ABS(wrappedDistance))? directDistance: wrappedDistance;
    }
    return directDistance;
}

- (NSInteger)clampedIndex:(NSInteger)index
{
    if (_numberOfItems == 0)
    {
        return -1;
    } else if (_wrapEnabled) {
        return index - floor((CGFloat)index / (CGFloat)_numberOfItems) * _numberOfItems;
    } else {
        return MIN(MAX(0, index), MAX(0, _numberOfItems - 1));
    }
}

- (CGFloat)clampedOffset:(CGFloat)offset
{
    if (_numberOfItems == 0) {
        return -1.0;
    } else if (_wrapEnabled) {
        return offset - floor(offset / (CGFloat)_numberOfItems) * _numberOfItems;
    } else {
        return MIN(MAX(0.0, offset), MAX(0.0, (CGFloat)_numberOfItems - 1.0));
    }
}

- (CGFloat)minScrollDistanceFromOffset:(CGFloat)fromOffset toOffset:(CGFloat)toOffset
{
    CGFloat directDistance = toOffset - fromOffset;
    if (_wrapEnabled) {
        CGFloat wrappedDistance = MIN(toOffset, fromOffset) + _numberOfItems - MAX(toOffset, fromOffset);
        if (fromOffset < toOffset) {
            wrappedDistance = -wrappedDistance;
        }
        return (fabs(directDistance) <= fabs(wrappedDistance))? directDistance: wrappedDistance;
    }
    return directDistance;
}

- (void)scrollByOffset:(CGFloat)offset duration:(NSTimeInterval)duration
{
    if (duration > 0.0) {
        _decelerating = NO;
        _scrolling = YES;
        _startTime = CACurrentMediaTime();
        _startOffset = _scrollOffset;
        _scrollDuration = duration;
        _endOffset = _startOffset + offset;
        if (!_wrapEnabled) {
            _endOffset = [self clampedOffset:_endOffset];
        }
        [_delegate sliderNeighborWillBeginScrollingAnimation:self];
        [self startAnimation];
    } else {
        self.scrollOffset += offset;
    }
}

- (void)scroll2Offset:(CGFloat)offset duration:(NSTimeInterval)duration
{
    [self scrollByOffset:[self minScrollDistanceFromOffset:_scrollOffset toOffset:offset] duration:duration];
    if (_delegate && [_delegate respondsToSelector:@selector(sliderNeighbor:didScrollToItemAtIndex:)]) {
        [_delegate sliderNeighbor:self didScrollToItemAtIndex:offset];
    }
}

- (void)scroll2ItemViewAtIndex:(NSInteger)index duration:(NSTimeInterval)duration
{
    [self scroll2Offset:index duration:duration];
}

- (void)scroll2ItemViewAtIndex:(NSInteger)index animated:(BOOL)animated
{
    [self scroll2ItemViewAtIndex:index duration:animated? 0.6: 0];
}

- (void)scrollByNumberOfItems:(NSInteger)itemCount duration:(NSTimeInterval)duration
{
    if (duration > 0.0) {
        CGFloat offset = 0.0;
        if (itemCount > 0) {
            offset = (floor(_scrollOffset) + itemCount) - _scrollOffset;
        } else if (itemCount < 0) {
            offset = (ceil(_scrollOffset) + itemCount) - _scrollOffset;
        } else {
            offset = round(_scrollOffset) - _scrollOffset;
        }
        [self scrollByOffset:offset duration:duration];
    } else {
        self.scrollOffset = [self clampedIndex:_previousItemIndex + itemCount];
    }
}

- (void)removeItemAtIndex:(NSInteger)index animated:(BOOL)animated
{
    index = [self clampedIndex:index];
    UIView *itemView = [self itemViewAtIndex:index];
    
    if (animated) {
        [UIView beginAnimations:nil context:nil];
        [UIView setAnimationDuration:0.1];
        [UIView setAnimationDelegate:itemView.superview];
        [UIView setAnimationDidStopSelector:@selector(removeFromSuperview)];
        [self performSelector:@selector(queueItemView:) withObject:itemView afterDelay:0.1];
        itemView.superview.layer.opacity = 0.0;
        [UIView commitAnimations];
        
        [UIView beginAnimations:nil context:nil];
        [UIView setAnimationDelay:0.1];
        [UIView setAnimationDuration:INSERT_DURATION];
        [UIView setAnimationDelegate:self];
        [UIView setAnimationDidStopSelector:@selector(depthSortViews)];
        [self removeViewAtIndex:index];
        _numberOfItems --;
        _wrapEnabled = !![self valueForOption:WXSliderNeighborOptionWrap withDefault:_wrapEnabled];
        [self updateNumberOfVisibleItems];
        _scrollOffset = self.currentItemIndex;
        [self didScroll];
        [UIView commitAnimations];
    } else {
        [self pushAnimationState:NO];
        [self queueItemView:itemView];
        [itemView.superview removeFromSuperview];
        [self removeViewAtIndex:index];
        _numberOfItems --;
        _wrapEnabled = !![self valueForOption:WXSliderNeighborOptionWrap withDefault:_wrapEnabled];
        _scrollOffset = self.currentItemIndex;
        [self didScroll];
        [self depthSortViews];
        [self popAnimationState];
    }
}

- (void)insertItemAtIndex:(NSInteger)index animated:(BOOL)animated
{
    _numberOfItems ++;
    _wrapEnabled = !![self valueForOption:WXSliderNeighborOptionWrap withDefault:_wrapEnabled];
    [self updateNumberOfVisibleItems];
    
    index = [self clampedIndex:index];
    [self insertView:nil atIndex:index];
    [self loadViewAtIndex:index];
    
    if (fabs(_itemWidth) < FLOAT_ERROR_MARGIN) {
        [self updateItemWidth];
    }
    
    if (animated) {
        [UIView beginAnimations:nil context:nil];
        [UIView setAnimationDuration:INSERT_DURATION];
        [UIView setAnimationDelegate:self];
        [UIView setAnimationDidStopSelector:@selector(didScroll)];
        [self transformItemViews];
        [UIView commitAnimations];
    } else {
        [self pushAnimationState:NO];
        [self didScroll];
        [self popAnimationState];
    }
    
    if (_scrollOffset < 0.0) {
        [self scroll2ItemViewAtIndex:0 animated:(animated && _numberOfPlaceholders)];
    }
}

- (void)reloadItemAtIndex:(NSInteger)index animated:(BOOL)animated
{
    //get container view
    UIView *containerView = [[self itemViewAtIndex:index] superview];
    if (containerView) {
        if (animated) {
            //fade transition
            CATransition *transition = [CATransition animation];
            transition.duration = INSERT_DURATION;
            transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
            transition.type = kCATransitionFade;
            [containerView.layer addAnimation:transition forKey:nil];
        }
        
        //reload view
        [self loadViewAtIndex:index withContainerView:containerView];
    }
}

#pragma mark Animation
- (void)step
{
    [self pushAnimationState:NO];
    NSTimeInterval currentTime = CACurrentMediaTime();
    double delta = currentTime - _lastTime;
    _lastTime = currentTime;
    
    if (_scrolling && !_dragging) {
        NSTimeInterval time = MIN(1.0, (currentTime - _startTime) / _scrollDuration);
        delta = [self easeInOut:time];
        _scrollOffset = _startOffset + (_endOffset - _startOffset) * delta;
        [self didScroll];
        if (time >= 1.0) {
            _scrolling = NO;
            [self depthSortViews];
            [self pushAnimationState:YES];
            [_delegate sliderNeighborDidEndScrollingAnimation:self];
            [self popAnimationState];
        }
    } else if (_decelerating) {
        CGFloat time = MIN(_scrollDuration, currentTime - _startTime);
        CGFloat acceleration = -_startVelocity/_scrollDuration;
        CGFloat distance = _startVelocity * time + 0.5 * acceleration * pow(time, 2.0);
        _scrollOffset = _startOffset + distance;
        [self didScroll];
        if (fabs(time - _scrollDuration) < FLOAT_ERROR_MARGIN) {
            _decelerating = NO;
            [self pushAnimationState:YES];
            [_delegate sliderNeighborDidEndDecelerating:self];
            [self popAnimationState];
            if ((_scrollToItemBoundary || fabs(_scrollOffset - [self clampedOffset:_scrollOffset]) > FLOAT_ERROR_MARGIN) && !_autoscroll) {
                if (fabs(_scrollOffset - self.currentItemIndex) < FLOAT_ERROR_MARGIN) {
                    //call scroll to trigger events for legacy support reasons
                    //even though technically we don't need to scroll at all
                    [self scroll2ItemViewAtIndex:self.currentItemIndex duration:0.01];
                } else {
                    [self scroll2ItemViewAtIndex:self.currentItemIndex animated:YES];
                }
            } else {
                CGFloat difference = round(_scrollOffset) - _scrollOffset;
                if (difference > 0.5) {
                    difference = difference - 1.0;
                } else if (difference < -0.5) {
                    difference = 1.0 + difference;
                }
                _toggleTime = currentTime - MAX_TOGGLE_DURATION * fabs(difference);
                _toggle = MAX(-1.0, MIN(1.0, -difference));
            }
        }
    } else if (_autoscroll && !_dragging) {
        //autoscroll goes backwards from what you'd expect, for historical reasons
        self.scrollOffset = [self clampedOffset:_scrollOffset - delta * _autoscroll];
    } else if (fabs(_toggle) > FLOAT_ERROR_MARGIN) {
        NSTimeInterval toggleDuration = _startVelocity? MIN(1.0, MAX(0.0, 1.0 / fabs(_startVelocity))): 1.0;
        toggleDuration = MIN_TOGGLE_DURATION + (MAX_TOGGLE_DURATION - MIN_TOGGLE_DURATION) * toggleDuration;
        NSTimeInterval time = MIN(1.0, (currentTime - _toggleTime) / toggleDuration);
        delta = [self easeInOut:time];
        _toggle = (_toggle < 0.0)? (delta - 1.0): (1.0 - delta);
        [self didScroll];
    } else if (!_autoscroll) {
        [self stopAnimation];
    }
    
    [self popAnimationState];
}

- (CGFloat)easeInOut:(CGFloat)time
{
    return (time < 0.5)? 0.5 * pow(time * 2.0, 3.0): 0.5 * pow(time * 2.0 - 2.0, 3.0) + 1.0;
}

- (CGFloat)decelerationDistance
{
    CGFloat acceleration = -_startVelocity * DECELERATION_MULTIPLIER * (1.0 - _decelerationRate);
    return -pow(_startVelocity, 2.0) / (2.0 * acceleration);
}

- (void)startDecelerating
{
    CGFloat distance = [self decelerationDistance];
    _startOffset = _scrollOffset;
    _endOffset = _startOffset + distance;
    if (_pagingEnabled) {
        if (distance > 0.0) {
            _endOffset = ceil(_startOffset);
        } else {
            _endOffset = floor(_startOffset);
        }
    } else if (_stopAtItemBoundary) {
        if (distance > 0.0) {
            _endOffset = ceil(_endOffset);
        } else {
            _endOffset = floor(_endOffset);
        }
    }
    if (!_wrapEnabled) {
        if (_bounces) {
            _endOffset = MAX(-_bounceDistance, MIN(_numberOfItems - 1.0 + _bounceDistance, _endOffset));
        } else {
            _endOffset = [self clampedOffset:_endOffset];
        }
    }
    distance = _endOffset - _startOffset;
    
    _startTime = CACurrentMediaTime();
    _scrollDuration = fabs(distance) / fabs(0.5 * _startVelocity);
    
    if (distance != 0.0) {
        _decelerating = YES;
        [self startAnimation];
    }
}


- (BOOL)shouldDecelerate
{
    return (fabs(_startVelocity) > SCROLL_SPEED_THRESHOLD) &&
    (fabs([self decelerationDistance]) > DECELERATE_THRESHOLD);
}

- (BOOL)shouldScroll
{
    return (fabs(_startVelocity) > SCROLL_SPEED_THRESHOLD) &&
    (fabs(_scrollOffset - self.currentItemIndex) > SCROLL_DISTANCE_THRESHOLD);
}

- (void)didScroll
{
    if (_wrapEnabled || !_bounces) {
        _scrollOffset = [self clampedOffset:_scrollOffset];
    } else {
        CGFloat min = -_bounceDistance;
        CGFloat max = MAX(_numberOfItems - 1, 0.0) + _bounceDistance;
        if (_scrollOffset < min) {
            _scrollOffset = min;
            _startVelocity = 0.0;
        } else if (_scrollOffset > max) {
            _scrollOffset = max;
            _startVelocity = 0.0;
        }
    }
    
    //check if index has changed
    NSInteger difference = [self minScrollDistanceFromIndex:self.currentItemIndex toIndex:self.previousItemIndex];
    if (difference) {
        _toggleTime = CACurrentMediaTime();
        _toggle = MAX(-1, MIN(1, difference));
        
        [self startAnimation];
    }
    
    [self loadUnloadViews];
    [self transformItemViews];
    
    //notify delegate of offset change
    if (fabs(_scrollOffset - _previousScrollOffset) > 0.000001)
    {
        [self pushAnimationState:YES];
        [_delegate sliderNeighborDidScroll:self];
        [self popAnimationState];
    }
    
    //notify delegate of index change
    if (_previousItemIndex != self.currentItemIndex)
    {
        [self pushAnimationState:YES];
        if([_delegate respondsToSelector:@selector(sliderNeighborCurrentItemIndexDidChange:from:to:)]) {
            [_delegate sliderNeighborCurrentItemIndexDidChange:self from:_previousItemIndex to:self.currentItemIndex];
        }
        [self popAnimationState];
    }
    
    //update previous index
    _previousScrollOffset = _scrollOffset;
    _previousItemIndex = self.currentItemIndex;
    [self.indicator setCurrentPoint:self.currentItemIndex];
}

- (void)startAnimation
{
    if (!_timer) {
        self.timer = [NSTimer timerWithTimeInterval:1.0/60.0
                                             target:self
                                           selector:@selector(step)
                                           userInfo:nil
                                            repeats:YES];
        
        [[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
        [[NSRunLoop mainRunLoop] addTimer:_timer forMode:UITrackingRunLoopMode];
        
    }
}

- (void)stopAnimation
{
    [_timer invalidate];
    _timer = nil;
}

#pragma mark -
#pragma mark view management

- (NSArray *)indexesForVisibleItems
{
    return [[_itemViews allKeys] sortedArrayUsingSelector:@selector(compare:)];
}

- (NSArray *)visibleItemViews
{
    NSArray *indexes = [self indexesForVisibleItems];
    return [_itemViews objectsForKeys:indexes notFoundMarker:[NSNull null]];
}

- (UIView *)itemViewAtIndex:(NSInteger)index
{
    return _itemViews[@(index)];
}

- (UIView *)currentItemView
{
    return [self itemViewAtIndex:self.currentItemIndex];
}

- (NSInteger)indexOfItemView:(UIView *)view
{
    NSInteger index = [[_itemViews allValues] indexOfObject:view];
    if (index != NSNotFound) {
        return [[_itemViews allKeys][index] integerValue];
    }
    return NSNotFound;
}

- (NSInteger)indexOfItemViewOrSubview:(UIView *)view
{
    NSInteger index = [self indexOfItemView:view];
    if (index == NSNotFound && view != nil && view != _contentView) {
        return [self indexOfItemViewOrSubview:view.superview];
    }
    return index;
}

- (UIView *)itemViewAtPoint:(CGPoint)point
{
    for (UIView *view in [[[_itemViews allValues] sortedArrayUsingFunction:(NSInteger (*)(id, id, void *))sliderNeighorCompareViewDepth context:(__bridge void *)self] reverseObjectEnumerator])
    {
        if ([view.superview.layer hitTest:point]) {
            return view;
        }
    }
    return nil;
}

- (void)setItemView:(UIView *)view forIndex:(NSInteger)index
{
    _itemViews[@(index)] = view;
}

- (void)removeViewAtIndex:(NSInteger)index
{
    NSMutableDictionary *newItemViews = [NSMutableDictionary dictionaryWithCapacity:[_itemViews count] - 1];
    for (NSNumber *number in [self indexesForVisibleItems])
    {
        NSInteger i = [number integerValue];
        if (i < index) {
            newItemViews[number] = _itemViews[number];
        } else if (i > index) {
            newItemViews[@(i - 1)] = _itemViews[number];
        }
    }
    self.itemViews = newItemViews;
    [self.indicator setPointCount:self.itemViews.count];
}

- (void)insertView:(UIView *)view atIndex:(NSInteger)index
{
    NSMutableDictionary *newItemViews = [NSMutableDictionary dictionaryWithCapacity:[_itemViews count] + 1];
    for (NSNumber *number in [self indexesForVisibleItems])
    {
        NSInteger i = [number integerValue];
        if (i < index) {
            newItemViews[number] = _itemViews[number];
        } else {
            newItemViews[@(i + 1)] = _itemViews[number];
        }
    }
    if (view) {
        [self setItemView:view forIndex:index];
    }
    self.itemViews = newItemViews;
    
    [self.indicator setPointCount:self.itemViews.count];
}

#pragma mark -
#pragma mark View loading

- (UIView *)loadViewAtIndex:(NSInteger)index withContainerView:(UIView *)containerView
{
    [self pushAnimationState:NO];
    
    UIView *view = nil;
    if (index < 0) {
        view = [_dataSource sliderNeighbor:self placeholderViewAtIndex:(NSInteger)(ceil((CGFloat)_numberOfPlaceholdersToShow/2.0)) + index reusingView:[self dequeuePlaceholderView]];
    } else if (index >= _numberOfItems) {
        view = [_dataSource sliderNeighbor:self placeholderViewAtIndex:_numberOfPlaceholdersToShow/2.0 + index - _numberOfItems reusingView:[self dequeuePlaceholderView]];
    } else {
        view = [_dataSource sliderNeighbor:self viewForItemAtIndex:index reusingView:[self dequeueItemView]];
    }
    
    if (view == nil) {
        view = [[UIView alloc] init];
    }
    
    [self setItemView:view forIndex:index];
    if (containerView) {
        //get old item view
        UIView *oldItemView = [containerView.subviews lastObject];
        if (index < 0 || index >= _numberOfItems) {
            [self queuePlaceholderView:oldItemView];
        } else {
            [self queueItemView:oldItemView];
        }
        
        //set container frame
        CGRect frame = containerView.bounds;
        if(_vertical) {
            frame.size.width = view.frame.size.width;
            frame.size.height = MIN(_itemWidth, view.frame.size.height);
        } else {
            frame.size.width = MIN(_itemWidth, view.frame.size.width);
            frame.size.height = view.frame.size.height;
        }
        containerView.bounds = frame;
        
        //set view frame
        frame = view.frame;
        frame.origin.x = (containerView.bounds.size.width - frame.size.width) / 2.0;
        frame.origin.y = (containerView.bounds.size.height - frame.size.height) / 2.0;
        view.frame = frame;
        
        //switch views
        [oldItemView removeFromSuperview];
        [containerView addSubview:view];
    } else {
        [_contentView addSubview:[self containView:view]];
    }
    view.superview.layer.opacity = 0.0;
    [self transformItemView:view atIndex:index];
    
    [self popAnimationState];
    
    return view;
}

- (UIView *)loadViewAtIndex:(NSInteger)index
{
    return [self loadViewAtIndex:index withContainerView:nil];
}

- (void)loadUnloadViews
{
    //set item width
    [self updateItemWidth];
    
    //update number of visible items
    [self updateNumberOfVisibleItems];
    
    //calculate visible view indices
    NSMutableSet *visibleIndices = [NSMutableSet setWithCapacity:_numberOfVisibleItems];
    NSInteger min = -(NSInteger)(ceil((CGFloat)_numberOfPlaceholdersToShow/2.0));
    NSInteger max = _numberOfItems - 1 + _numberOfPlaceholdersToShow/2;
    NSInteger offset = self.currentItemIndex - _numberOfVisibleItems/2;
    if (!_wrapEnabled) {
        offset = MAX(min, MIN(max - _numberOfVisibleItems + 1, offset));
    }
    for (NSInteger i = 0; i < _numberOfVisibleItems; i++)
    {
        NSInteger index = i + offset;
        if (_wrapEnabled) {
            index = [self clampedIndex:index];
        }
        CGFloat alpha = [self alphaForItemWithOffset:[self offsetForItemAtIndex:index]];
        if (alpha) {
            //only add views with alpha > 0
            [visibleIndices addObject:@(index)];
        }
    }
    
    //remove offscreen views
    for (NSNumber *number in [_itemViews allKeys])
    {
        if (![visibleIndices containsObject:number]) {
            UIView *view = _itemViews[number];
            if ([number integerValue] < 0 || [number integerValue] >= _numberOfItems) {
                [self queuePlaceholderView:view];
            } else {
                [self queueItemView:view];
            }
            [view.superview removeFromSuperview];
            [(NSMutableDictionary *)_itemViews removeObjectForKey:number];
        }
    }
    
    //add onscreen views
    for (NSNumber *number in visibleIndices)
    {
        UIView *view = _itemViews[number];
        if (view == nil) {
            [self loadViewAtIndex:[number integerValue]];
        }
    }
}

#pragma mark View queing

- (void)queueItemView:(UIView *)view
{
    if (view) {
        [_itemViewPool addObject:view];
    }
}

- (void)queuePlaceholderView:(UIView *)view
{
    if (view) {
        [_placeholderViewPool addObject:view];
    }
}

- (UIView *)dequeueItemView
{
    UIView *view = [_itemViewPool anyObject];
    if (view) {
        [_itemViewPool removeObject:view];
    }
    return view;
}

- (UIView *)dequeuePlaceholderView
{
    UIView *view = [_placeholderViewPool anyObject];
    if (view) {
        [_placeholderViewPool removeObject:view];
    }
    return view;
}

- (void)setIndicator:(WXIndicatorView *)indicator
{
    _indicator = indicator;
    [_indicator setPointCount:self.numberOfItems];
    [_indicator setCurrentPoint:[self currentItemIndex]];
}

- (void)layoutSubviews
{
    [super layoutSubviews];
    _contentView.frame = self.bounds;
    [self layOutItemViews];
}

@end

@implementation NSObject (WXSliderNeighborView)

- (NSUInteger)numberOfPlaceholdersInSliderNeighborView:(__unused WXSliderNeighborView *)sliderNeighbor { return 0; }
- (void)sliderNeighborWillBeginScrollingAnimation:(__unused WXSliderNeighborView *)sliderNeighbor {}
- (void)sliderNeighborDidEndScrollingAnimation:(__unused WXSliderNeighborView *)sliderNeighbor {}
- (void)sliderNeighborDidScroll:(__unused WXSliderNeighborView *)sliderNeighbor {}

- (void)sliderNeighborCurrentItemIndexDidChange:(__unused WXSliderNeighborView *)sliderNeighbor {}
- (void)sliderNeighborWillBeginDragging:(__unused WXSliderNeighborView *)sliderNeighbor {}
- (void)sliderNeighborDidEndDragging:(__unused WXSliderNeighborView *)sliderNeighbor willDecelerate:(__unused BOOL)decelerate {}
- (void)sliderNeighborWillBeginDecelerating:(__unused WXSliderNeighborView *)sliderNeighbor {}
- (void)sliderNeighborDidEndDecelerating:(__unused WXSliderNeighborView *)sliderNeighbor {}

- (BOOL)sliderNeighbor:(__unused WXSliderNeighborView *)sliderNeighbor shouldSelectItemAtIndex:(__unused NSInteger)index { return YES; }
- (void)sliderNeighbor:(__unused WXSliderNeighborView *)sliderNeighbor didSelectItemAtIndex:(__unused NSInteger)index {}

- (CGFloat)sliderNeighborItemWidth:(__unused WXSliderNeighborView *)sliderNeighbor { return 0; }
- (CATransform3D)sliderNeighbor:(__unused WXSliderNeighborView *)sliderNeighbor
   itemTransformForOffset:(__unused CGFloat)offset
            baseTransform:(CATransform3D)transform { return transform; }
- (CGFloat)sliderNeighbor:(__unused WXSliderNeighborView *)sliderNeighbor
     valueForOption:(__unused WXSliderNeighborOption)option
        withDefault:(CGFloat)value { return value; }

@end

@interface WXSliderNeighborComponent () <WXIndicatorComponentDelegate>{
    WXPixelType neighborSpace;
    CGFloat neighborAlpha;
    CGFloat neighborScale;
    CGFloat currentItemScale;
}

@property (nonatomic, strong) WXSliderNeighborView *sliderView;
@property (nonatomic, assign) BOOL  autoPlay;
@property (nonatomic, assign) NSUInteger interval;
@property (nonatomic, assign) NSInteger index;
@property (nonatomic, strong) NSMutableArray *items;
@property (nonatomic, strong) NSTimer *autoTimer;
@property (nonatomic, assign) BOOL  sliderChangeEvent;
@property (nonatomic, assign) NSInteger currentIndex;
@property (nonatomic) CGRect itemRect;
@property (nonatomic, assign) BOOL scrollable;

@end

#define DEFAULT_NEIGHBOR_ITEM_SCALE 0.8
#define DEFAULT_CURRENT_ITEM_SCALE 0.9
#define DEFAULT_NEIGHBOR_ALPHA 0.6
#define DEFAULT_ANIMATION_DURATION 0.3
#define DEFAULT_NEIGHBOR_SPACE 25


@implementation WXSliderNeighborComponent

- (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]) {
        _sliderChangeEvent = NO;
        _interval = 3000;
        _items = [NSMutableArray array];
        _itemRect = CGRectNull;
        self->neighborAlpha = DEFAULT_NEIGHBOR_ALPHA;
        self->neighborScale = DEFAULT_NEIGHBOR_ITEM_SCALE;
        self->currentItemScale = DEFAULT_CURRENT_ITEM_SCALE;
        self->neighborSpace = [WXConvert WXPixelType:@(DEFAULT_NEIGHBOR_SPACE) scaleFactor:self.weexInstance.pixelScaleFactor];
        _scrollable = YES;
        [self setAttributes:attributes];
    
        _scrollable = attributes[@"scrollable"] ? [WXConvert BOOL:attributes[@"scrollable"]] : YES;
    }
    self.flexCssNode->setFlexDirection(WeexCore::kFlexDirectionRow,NO);
    return self;
}


#pragma mark life circle

- (UIView *)loadView
{
    _sliderView = [[WXSliderNeighborView alloc] init];
    return _sliderView;
}

- (void)dealloc
{
    _sliderView.delegate = nil;
    _sliderView.dataSource = nil;
    if (_autoPlay) {
        [self _stopAutoPlayTimer];
    }
    _sliderView = nil;
    [self.items removeAllObjects];
}

- (void)viewDidUnload
{
    [self.items removeAllObjects];
}

- (void)viewDidLoad
{
    _sliderView = (WXSliderNeighborView *)self.view;
    _sliderView.delegate = self;
    _sliderView.dataSource = self;
    _sliderView.contentView.clipsToBounds = YES;
    _sliderView.scrollEnabled = _scrollable;
    UIAccessibilityTraits traits = UIAccessibilityTraitAdjustable;
    
    if (_autoPlay) {
        traits |= UIAccessibilityTraitUpdatesFrequently;
        [self _startAutoPlayTimer];
    } else {
        [self _stopAutoPlayTimer];
    }
    _sliderView.accessibilityTraits = traits;
}

- (void)layoutDidFinish {
    
    [_sliderView setCurrentItemIndex:_index];
}

- (void)insertSubview:(WXComponent *)subcomponent atIndex:(NSInteger)index
{
    if (subcomponent->_positionType == WXPositionTypeFixed) {
        [self.weexInstance.rootView addSubview:subcomponent.view];
        return;
    }
    
    // use _lazyCreateView to forbid component like cell's view creating
    if(_lazyCreateView) {
        subcomponent->_lazyCreateView = YES;
    }
    
    if (!subcomponent->_lazyCreateView || (self->_lazyCreateView && [self isViewLoaded])) {
        UIView *view = subcomponent.view;
        
        WXSliderNeighborView *sliderView = (WXSliderNeighborView *)self.view;
        if ([view isKindOfClass:[WXIndicatorView class]]) {
            ((WXIndicatorComponent *)subcomponent).delegate = self;
            [sliderView addSubview:view];
            return;
        }
        
        if(index < 0) {
            [self.items addObject:view];
        } else {
            [self.items insertObject:view atIndex:index];
        }
        
        subcomponent.isViewFrameSyncWithCalculated = NO;
        
        if (index == -1) {
            [sliderView insertView:view atIndex:index];
        } else {
            NSInteger offset = 0;
            for (int i = 0; i < [self.items count]; ++i) {
                if (index == i) break;
                
                if ([self.items[i] isKindOfClass:[WXIndicatorView class]]) {
                    offset++;
                }
            }
            [sliderView insertView:view atIndex:index - offset];
        }
        
        [sliderView reloadData];
    }
}

#pragma mark attributes update
- (void)setAttributes:(NSDictionary *)attributes
{
    if (attributes[@"index"]) {
        _index = [attributes[@"index"] integerValue];
        _currentIndex = _index;
    }
    if (attributes[@"autoPlay"]) {
        _autoPlay = [attributes[@"autoPlay"] boolValue];
        if (_autoPlay) {
            [self _startAutoPlayTimer];
        } else {
            [self _stopAutoPlayTimer];
        }
    }
    if (attributes[@"interval"]) {
        _interval = [attributes[@"interval"] integerValue];
        
        [self _stopAutoPlayTimer];
        
        if (_autoPlay) {
            [self _startAutoPlayTimer];
        }
    }
    if (attributes[@"neighborScale"]) {
        [self setNeighborScale:attributes];
    }
    if (attributes[@"currentItemScale"]) {
        [self setCurrentItemScale:attributes];
    }
    if (attributes[@"neighborAlpha"]) {
        [self setNeighborAlpha:attributes];
    }
    if (attributes[@"neighborSpace"]) {
        [self setNeighborSpace:attributes];
    }
    if (attributes[@"scrollable"]) {
        _scrollable = attributes[@"scrollable"] ? [WXConvert BOOL:attributes[@"scrollable"]] : YES;
        ((WXSliderNeighborView *)self.view).scrollEnabled = _scrollable;
    }
}

- (void)updateAttributes:(NSDictionary *)attributes
{
    [self setAttributes:attributes];
    [self.sliderView setCurrentItemIndex:_index];
    [self updateSliderPage:YES];
}

#pragma mark styles update
- (void)updateStyles:(NSDictionary *)styles {
    
}

#pragma mark event
- (void)addEvent:(NSString *)eventName
{
    if ([eventName isEqualToString:@"change"]) {
        _sliderChangeEvent = YES;
    }
}

- (void)removeEvent:(NSString *)eventName
{
    if ([eventName isEqualToString:@"change"]) {
        _sliderChangeEvent = NO;
    }
}

#pragma mark WXIndicatorComponentDelegate Methods

- (void)setIndicatorView:(WXIndicatorView *)indicatorView
{
    NSAssert(_sliderView, @""); //!OCLint
    [_sliderView setIndicator:indicatorView];
}

- (void)setNeighborSpace:(NSDictionary *)attributes{
    if(attributes[@"neighborSpace"]) {
        self->neighborSpace = [WXConvert WXPixelType:attributes[@"neighborSpace"] scaleFactor:self.weexInstance.pixelScaleFactor];
    }
}

- (void)setNeighborAlpha:(NSDictionary *)attributes {
    if (attributes[@"neighborAlpha"]) {
        self->neighborAlpha = [WXConvert CGFloat:attributes[@"neighborAlpha"]];
        self->neighborAlpha = self->neighborAlpha >= 0 ? self->neighborAlpha : 0;
        self->neighborAlpha = self->neighborAlpha <= 1 ? self->neighborAlpha: 1;
    }
}

- (void)setCurrentItemScale:(NSDictionary *)attributes {
    if (attributes[@"currentItemScale"]) {
        self->currentItemScale = [WXConvert CGFloat:attributes[@"currentItemScale"]];
        self->currentItemScale = self->currentItemScale >= 0 ? self->currentItemScale : 0;
        self->currentItemScale = self->currentItemScale <= 1 ? self->currentItemScale: 1;
    }
}

- (void)setNeighborScale:(NSDictionary *)attributes
{
    if (attributes[@"neighborScale"]) {
        self->neighborScale = [WXConvert CGFloat:attributes[@"neighborScale"]];
        self->neighborScale = self->neighborScale >= 0? self->neighborScale : 0;
        self->neighborScale = self->neighborScale <= 1? self->neighborScale :1;
    }
}

#pragma mark Private Methods

- (void)_startAutoPlayTimer
{
    if (!self.autoTimer || ![self.autoTimer isValid]) {
        __weak __typeof__(self) weakSelf = self;
        self.autoTimer = [NSTimer wx_scheduledTimerWithTimeInterval:_interval/1000.0f block:^() {
            [weakSelf _autoPlayOnTimer];
        } repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:self.autoTimer forMode:NSRunLoopCommonModes];
    }
}

- (void)_stopAutoPlayTimer
{
    if (self.autoTimer && [self.autoTimer isValid]) {
        [self.autoTimer invalidate];
        self.autoTimer = nil;
    }
}

- (void)_autoPlayOnTimer
{
    WXSliderNeighborView *sliderNeighborView = (WXSliderNeighborView *)self.view;
    
    int indicatorCnt = 0;
    for (int i = 0; i < [self.items count]; ++i) {
        if ([self.items[i] isKindOfClass:[WXIndicatorView class]]) {
            indicatorCnt++;
        }
    }
    
    self.currentIndex ++;
    if (self.currentIndex >= self.items.count - indicatorCnt) {
        self.currentIndex = 0;
    }
    
    [sliderNeighborView setCurrentItemIndex:self.currentIndex];
}

#pragma mark sliderNeighbor Delegate && dataSource

- (NSInteger)numberOfItemsInSliderNeighbor:(WXSliderNeighborView *)sliderNeighbor {
    return [self.items count];
}

- (NSInteger)numberOfPlaceholdersInsliderNeighbor:(WXSliderNeighborView *)sliderNeighbor {
    return 2;
}

- (UIView *)sliderNeighbor:(WXSliderNeighborView *)sliderNeighbor viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view {
    
    if (!view) {
        if (index < [self.items count]) {
            view = self.items[index];
        }else {
            return nil;
        }
    } else {
        view.tag = 1;
    }
    
    if (CGRectIsNull(_itemRect)) {
        _itemRect = view.frame;
    }
    if (index == [self.items count] - 1) {
        [self updateSliderPage:NO];
    }
    
    return view;
}

- (BOOL)sliderNeighbor:(WXSliderNeighborView *)sliderNeighbor shouldSelectItemAtIndex:(NSInteger)index {
    
    return YES;
}

- (UIView *)sliderNeighbor:(WXSliderNeighborView *)sliderNeighbor placeholderViewAtIndex:(NSInteger)index reusingView:(UIView *)view
{
    
    return view;
}

- (void)sliderNeighborDidEndScrollingAnimation:(WXSliderNeighborView *)sliderNeighbor
{
    [self updateSliderPage:YES];
}

- (void)sliderNeighbor:(WXSliderNeighborView *)sliderView didScrollToItemAtIndex:(NSInteger)index
{
    self.currentIndex = index;
    
    if (_sliderChangeEvent) {
        [self fireEvent:@"change" params:@{@"index":@(index)} domChanges:@{@"attrs": @{@"index": @(index)}}];
    }
}

- (void)sliderNeighborWillBeginDragging:(WXSliderNeighborView *)sliderNeighbor
{
    [self _stopAutoPlayTimer];
}

- (void)sliderNeighborDidEndDragging:(WXSliderNeighborView *)sliderNeighbor willDecelerate:(BOOL)decelerate {
    if (_autoPlay) {
        [self _startAutoPlayTimer];
    }
}

- (void)updateSliderPage:(BOOL)animate
{
    __block UIView * currentView  = [self.sliderView itemViewAtIndex:[_sliderView currentItemIndex]];
    __block UIView * lastView  = [self.sliderView itemViewAtIndex:[_sliderView lastItemIndex]];
    __block UIView * nextView  = [self.sliderView itemViewAtIndex:[_sliderView nextItemIndex]];
    
    __block CGAffineTransform transfrom = CGAffineTransformIdentity;
    float duration = 0;
    __weak typeof(self) weakSelf = self;
    if (animate) {
        duration = DEFAULT_ANIMATION_DURATION;
    }
    
    [UIView animateWithDuration:duration animations:^{
        __strong typeof(self) strongSelf = weakSelf;
        if (strongSelf) {
            currentView.alpha = 1.0;
            if (fabs(strongSelf->currentItemScale) > CGFLOAT_MIN) {
                transfrom = CGAffineTransformConcat(transfrom,CGAffineTransformMakeScale(strongSelf->currentItemScale, strongSelf->currentItemScale));
            }
            currentView.transform = transfrom;
            transfrom = CGAffineTransformIdentity;
            if (fabs(strongSelf->neighborScale) <= CGFLOAT_MIN) {
                strongSelf->neighborScale = DEFAULT_NEIGHBOR_ITEM_SCALE;
            }
            
            CGFloat tx = 0.5*_itemRect.size.width*((1-self->neighborScale)+(1-self->currentItemScale))-self->neighborSpace;
            transfrom = CGAffineTransformConcat(transfrom, CGAffineTransformMakeScale(strongSelf->neighborScale, strongSelf->neighborScale));
            nextView.transform = CGAffineTransformConcat(transfrom,  CGAffineTransformMakeTranslation(-tx, 0));
            lastView.transform = CGAffineTransformConcat(transfrom,  CGAffineTransformMakeTranslation(tx, 0));
            lastView.alpha = strongSelf->neighborAlpha;
            nextView.alpha = strongSelf->neighborAlpha;
        }
    }];
}

@end
