//
//  EAIntroView.m
//
//  Copyright (c) 2013-2015 Evgeny Aleksandrov. License: MIT. 

#import "EAIntroView.h"
#import "EARestrictedScrollView.h"

@interface EAIntroView()

@property (nonatomic, strong) UIImageView *bgImageView;
@property (nonatomic, strong) UIImageView *pageBgBack;
@property (nonatomic, strong) UIImageView *pageBgFront;

@property (nonatomic, strong) NSMutableArray<NSLayoutConstraint *> *footerConstraints;
@property (nonatomic, strong) NSMutableArray<NSLayoutConstraint *> *titleViewConstraints;

@property (nonatomic, assign) BOOL skipped;

@end

@interface EAIntroPage()

@property (nonatomic, strong, readwrite) UIView *pageView;

@end


@implementation EAIntroView

@synthesize pageControl = _pageControl;
@synthesize skipButton = _skipButton;

#pragma mark - Init

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self applyDefaultsToSelfDuringInitializationWithFrame:frame pages:nil];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self applyDefaultsToSelfDuringInitializationWithFrame:self.frame pages:nil];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame andPages:(NSArray<EAIntroPage *> *)pagesArray {
    self = [super initWithFrame:frame];
    if (self) {
        [self applyDefaultsToSelfDuringInitializationWithFrame:self.frame pages:pagesArray];
    }
    return self;
}

#pragma mark - Private

- (void)applyDefaultsToSelfDuringInitializationWithFrame:(CGRect)frame pages:(NSArray<EAIntroPage *> *)pagesArray {
    self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    self.swipeToExit = YES;
    self.easeOutCrossDisolves = YES;
    self.hideOffscreenPages = YES;
    self.bgViewContentMode = UIViewContentModeScaleAspectFill;
    self.motionEffectsRelativeValue = 40.f;
    self.backgroundColor = [UIColor blackColor];
    _scrollingEnabled = YES;
    _titleViewY = 20.f;
    _pageControlY = 70.f;
    _skipButtonY = EA_EMPTY_PROPERTY;
    _skipButtonSideMargin = 10.f;
    _skipButtonAlignment = EAViewAlignmentRight;
	_skipped = NO;
    _limitPageIndex = -1;
    
    [self buildBackgroundImage];
    
    self.pages = [pagesArray copy];
    
    [self buildFooterView];
}

- (void)applyDefaultsToBackgroundImageView:(UIImageView *)backgroundImageView {
    backgroundImageView.backgroundColor = [UIColor clearColor];
    backgroundImageView.contentMode = self.bgViewContentMode;
    backgroundImageView.autoresizesSubviews = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
    backgroundImageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
}

- (void)makePanelVisibleAtIndex:(NSUInteger)panelIndex{
    [UIView animateWithDuration:0.3 animations:^{
        for (int idx = 0; idx < _pages.count; idx++) {
            if (idx == panelIndex) {
                [[self viewForPageIndex:idx] setAlpha:[self alphaForPageIndex:idx]];
            } else {
                if(!self.hideOffscreenPages) {
                    [[self viewForPageIndex:idx] setAlpha:0];
                }
            }
        }
    }];
}

- (EAIntroPage *)pageForIndex:(NSUInteger)idx {
    if(idx >= _pages.count) {
        return nil;
    }
    
    return (EAIntroPage *)_pages[idx];
}

- (CGFloat)alphaForPageIndex:(NSUInteger)idx {
    if(![self pageForIndex:idx]) {
        return 1.f;
    }
    
    return [self pageForIndex:idx].alpha;
}

- (BOOL)showTitleViewForPage:(NSUInteger)idx {
    if(![self pageForIndex:idx]) {
        return NO;
    }
    
    return [self pageForIndex:idx].showTitleView;
}

- (UIView *)viewForPageIndex:(NSUInteger)idx {
    return [self pageForIndex:idx].pageView;
}

- (UIImage *)bgImageForPage:(NSUInteger)idx {
    return [self pageForIndex:idx].bgImage;
}

- (UIColor *)bgColorForPage:(NSUInteger)idx {
    return [self pageForIndex:idx].bgColor;
}

- (void)showPanelAtPageControl {
    [self makePanelVisibleAtIndex:self.currentPageIndex];
    
    [self setCurrentPageIndex:self.pageControl.currentPage animated:YES];
}

- (void)checkIndexForScrollView:(EARestrictedScrollView *)scrollView {
    NSUInteger newPageIndex = (scrollView.contentOffset.x + scrollView.bounds.size.width/2)/self.scrollView.frame.size.width;
    [self notifyDelegateWithPreviousPage:self.currentPageIndex andCurrentPage:newPageIndex];
    _currentPageIndex = newPageIndex;
    
    if (self.currentPageIndex == (_pages.count)) {
        
        //if run here, it means you can't  call _pages[self.currentPageIndex],
        //to be safe, set to the biggest index
        _currentPageIndex = _pages.count - 1;

        if ([self.delegate respondsToSelector:@selector(introWillFinish:wasSkipped:)]) {
            [self.delegate introWillFinish:self wasSkipped:self.skipped];
        }

        [self finishIntroductionAndRemoveSelf];
    }
}

- (void)finishIntroductionAndRemoveSelf {
    //prevent last page flicker on disappearing
    self.alpha = 0;
    
    //Calling removeFromSuperview from scrollViewDidEndDecelerating: method leads to crash on iOS versions < 7.0.
    //removeFromSuperview should be called after a delay
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)0);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        if ([(id)self.delegate respondsToSelector:@selector(introDidFinish:wasSkipped:)]) {
            [self.delegate introDidFinish:self wasSkipped:self.skipped];
        }
        
        [self removeFromSuperview];
    });
}

- (void)skipIntroduction {
	self.skipped = YES;
    [self hideWithFadeOutDuration:0.3];
}

- (void)notifyDelegateWithPreviousPage:(NSUInteger)previousPageIndex andCurrentPage:(NSUInteger)currentPageIndex {
    if(currentPageIndex!=_currentPageIndex && currentPageIndex < _pages.count) {
        EAIntroPage* previousPage = _pages[previousPageIndex];
        EAIntroPage* currentPage = _pages[currentPageIndex];
        if(previousPage.onPageDidDisappear) previousPage.onPageDidDisappear();
        if(currentPage.onPageDidAppear) currentPage.onPageDidAppear();
        
        if ([(id)self.delegate respondsToSelector:@selector(intro:pageAppeared:withIndex:)]) {
            [self.delegate intro:self pageAppeared:_pages[currentPageIndex] withIndex:currentPageIndex];
        }
    }
}

#pragma mark - Properties

- (EARestrictedScrollView *)scrollView {
    if (!_scrollView) {
        _scrollView = [[EARestrictedScrollView alloc] initWithFrame:self.bounds];
        _scrollView.accessibilityIdentifier = @"intro_scroll";
        _scrollView.pagingEnabled = YES;
        _scrollView.alwaysBounceHorizontal = YES;
        _scrollView.showsHorizontalScrollIndicator = NO;
        _scrollView.showsVerticalScrollIndicator = NO;
        _scrollView.delegate = self;
        _scrollView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
    }
    return _scrollView;
}

- (NSUInteger)visiblePageIndex {
    return (NSUInteger) ((self.scrollView.contentOffset.x + self.scrollView.bounds.size.width/2) / self.scrollView.frame.size.width);
}

- (UIImageView *)bgImageView {
    if (!_bgImageView) {
        _bgImageView = [[UIImageView alloc] initWithFrame:self.bounds];
        [self applyDefaultsToBackgroundImageView:_bgImageView];
    }
    return _bgImageView;
}

- (UIImageView *)pageBgBack {
    if (!_pageBgBack) {
        _pageBgBack = [[UIImageView alloc] initWithFrame:self.bounds];
        [self applyDefaultsToBackgroundImageView:_pageBgBack];
        _pageBgBack.alpha = 0;
    }
    return _pageBgBack;
}

- (UIImageView *)pageBgFront {
    if (!_pageBgFront) {
        _pageBgFront = [[UIImageView alloc] initWithFrame:self.bounds];
        [self applyDefaultsToBackgroundImageView:_pageBgFront];
        _pageBgFront.alpha = 0;
    }
    return _pageBgFront;
}

- (UIPageControl *)pageControl {
    if (!_pageControl) {
        _pageControl = [[UIPageControl alloc] init];
        [self applyDefaultsToPageControl];
    }
    return _pageControl;
}

- (void)applyDefaultsToPageControl {
    _pageControl.defersCurrentPageDisplay = YES;
    _pageControl.numberOfPages = _pages.count;
    _pageControl.translatesAutoresizingMaskIntoConstraints = NO;
    [_pageControl addTarget:self action:@selector(showPanelAtPageControl) forControlEvents:UIControlEventValueChanged];
}

- (UIButton *)skipButton {
    if (!_skipButton) {
        _skipButton = [[UIButton alloc] init];
        [_skipButton setTitle:NSLocalizedString(@"Skip", nil) forState:UIControlStateNormal];
        [self applyDefaultsToSkipButton];
    }
    return _skipButton;
}

- (void)applyDefaultsToSkipButton {
    _skipButton.translatesAutoresizingMaskIntoConstraints = NO;
    [_skipButton addTarget:self action:@selector(skipIntroduction) forControlEvents:UIControlEventTouchUpInside];
}

- (NSMutableArray *)footerConstraints {
    if (!_footerConstraints) {
        _footerConstraints = [NSMutableArray array];
    }
    return _footerConstraints;
}

- (NSMutableArray *)titleViewConstraints {
    if (!_titleViewConstraints) {
        _titleViewConstraints = [NSMutableArray array];
    }
    return _titleViewConstraints;
}

#pragma mark - UI building

- (void)buildBackgroundImage {
    [self addSubview:self.bgImageView];
    [self addSubview:self.pageBgBack];
    [self addSubview:self.pageBgFront];
    
    if (self.useMotionEffects) {
        [self addMotionEffectsOnBg];
    }
}

- (void)buildScrollView {
    CGFloat contentXIndex = 0;
    for (NSUInteger idx = 0; idx < _pages.count; idx++) {
        EAIntroPage *page = _pages[idx];
        page.pageView = [self viewForPage:page atXIndex:contentXIndex];
        contentXIndex += self.scrollView.frame.size.width;
        [self.scrollView addSubview:page.pageView];
        if(page.onPageDidLoad) page.onPageDidLoad();
    }
    
    [self makePanelVisibleAtIndex:0];
    
    if (self.swipeToExit) {
        [self appendCloseViewAtXIndex:&contentXIndex];
    }
    
    [self insertSubview:self.scrollView aboveSubview:self.pageBgFront];
    self.scrollView.contentSize = CGSizeMake(contentXIndex, self.scrollView.frame.size.height);
    
    self.pageBgBack.alpha = 0;
    self.pageBgBack.image = [self bgImageForPage:1];
    self.pageBgBack.backgroundColor = [self bgColorForPage:1];
    self.pageBgFront.alpha = [self alphaForPageIndex:0];
    self.pageBgFront.image = [self bgImageForPage:0];
    self.pageBgFront.backgroundColor = [self bgColorForPage:0];
}

- (UIView *)viewForPage:(EAIntroPage *)page atXIndex:(CGFloat)xIndex {
    UIView *pageView = [self createViewForPage:page atXIndex:xIndex];

    if(page.customView) {
        [self configurePageView:pageView withCustomView:page.customView];
    } else {
        [self configurePageView:pageView forPage:page];
    }

    return pageView;
}

- (UIView *)createViewForPage:(EAIntroPage *)page atXIndex:(CGFloat)xIndex {
    UIView *pageView = [[UIView alloc] initWithFrame:CGRectMake(xIndex, 0, self.scrollView.frame.size.width, self.scrollView.frame.size.height)];

    pageView.accessibilityLabel = [NSString stringWithFormat:@"intro_page_%lu",(unsigned long)[self.pages indexOfObject:page]];

    if(page.alpha < 1.f || !page.bgImage) {
        self.backgroundColor = [UIColor clearColor];
    }
    return pageView;
}

- (void)configurePageView:(UIView *)pageView withCustomView:(UIView *)customView {
    [self addTapToNextActionToPageView:customView];
    [pageView addSubview:customView];

    NSMutableArray *constraints = @[].mutableCopy;
    [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[customView]-0-|" options:0 metrics:nil views:@{@"customView": customView}]];
    [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[customView]-0-|" options:0 metrics:nil views:@{@"customView": customView}]];

    [pageView addConstraints:constraints];
}

- (void)configurePageView:(UIView *)pageView forPage:(EAIntroPage *)page {
    [self addTapToNextActionToPageView:pageView];
    [self applyAccessibilityLabelForPage:page toView:pageView];

    UIView *titleImageView;
    if(page.titleIconView) {
        titleImageView = page.titleIconView;
        titleImageView.tag = kTitleImageViewTag;
        titleImageView.translatesAutoresizingMaskIntoConstraints = NO;

        [pageView addSubview:titleImageView];
        [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-topSpace@250-[titleImageView(imageHeight)]" options:NSLayoutFormatAlignAllTop metrics:@{@"imageHeight" : @(page.titleIconView.frame.size.height), @"topSpace" : @(page.titleIconPositionY)} views:@{@"titleImageView" : titleImageView}]];
        [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[titleImageView(imageWidth)]" options:0 metrics:@{@"imageWidth" : @(page.titleIconView.frame.size.width)} views:@{@"titleImageView" : titleImageView}]];
        [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[superview]-(<=1)-[titleImageView]" options:NSLayoutFormatAlignAllCenterX metrics:nil views:@{@"superview" : pageView, @"titleImageView" : titleImageView}]];
    }

    UILabel *titleLabel;
    if(page.title.length) {
        titleLabel = [[UILabel alloc] init];
        titleLabel.text = page.title;
        titleLabel.font = page.titleFont;
        titleLabel.textColor = page.titleColor;
        titleLabel.backgroundColor = [UIColor clearColor];
        titleLabel.textAlignment = page.titleAlignment;
        titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
        titleLabel.numberOfLines = 0;
        titleLabel.tag = kTitleLabelTag;
        titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
        titleLabel.isAccessibilityElement = NO;

        [pageView addSubview:titleLabel];
        NSLayoutConstraint *weakConstraint = [NSLayoutConstraint constraintWithItem:pageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:titleLabel attribute:NSLayoutAttributeTop multiplier:1.0 constant:page.titlePositionY];
        weakConstraint.priority = UILayoutPriorityDefaultLow;
        [pageView addConstraint:weakConstraint];
        [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[titleLabel]-10-|" options:NSLayoutFormatAlignAllTop metrics:nil views:@{@"titleLabel" : titleLabel}]];
    }

    UITextView *descLabel;
    if(page.desc.length) {
        descLabel = [[UITextView alloc] init];
        descLabel.text = page.desc;
        descLabel.scrollEnabled = NO;
        descLabel.font = page.descFont;
        descLabel.textColor = page.descColor;
        descLabel.backgroundColor = [UIColor clearColor];
        descLabel.textAlignment = page.descAlignment;
        descLabel.userInteractionEnabled = NO;
        descLabel.tag = kDescLabelTag;
        descLabel.translatesAutoresizingMaskIntoConstraints = NO;
        descLabel.isAccessibilityElement = NO;

        [pageView addSubview:descLabel];
        NSLayoutConstraint *weakConstraint = [NSLayoutConstraint constraintWithItem:pageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:descLabel attribute:NSLayoutAttributeTop multiplier:1.0 constant:page.descPositionY];
        weakConstraint.priority = UILayoutPriorityDefaultLow;
        [pageView addConstraint:weakConstraint];
        [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-descMargin-[descLabel]-descMargin-|" options:NSLayoutFormatAlignAllTop metrics:@{@"descMargin" : @(page.descSideMargin)} views:@{@"descLabel" : descLabel}]];
    }

    // Constraints for handling landscape orientation
    if(titleImageView && titleLabel && descLabel) {
        [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|->=0-[titleImageView]->=0-[titleLabel]->=0-[descLabel]" options:0 metrics:nil views:@{@"titleImageView" : titleImageView, @"titleLabel" : titleLabel, @"descLabel" : descLabel}]];
    } else if(!titleImageView && titleLabel && descLabel) {
        [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|->=0-[titleLabel]->=0-[descLabel]" options:0 metrics:nil views:@{@"titleLabel" : titleLabel, @"descLabel" : descLabel}]];
    }

    if(page.subviews) {
        for (UIView *subV in page.subviews) {
            [pageView addSubview:subV];
        }
    }

    pageView.alpha = page.alpha;
}

- (void)addTapToNextActionToPageView:(UIView *)pageView {
    UITapGestureRecognizer *tapRecognizer =
            [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleBackgroundTap:)];

    [pageView addGestureRecognizer:tapRecognizer];
}

- (void)applyAccessibilityLabelForPage:(EAIntroPage *)page toView:(UIView *)view {
    NSString *accessibilityLabel = [self accessibilityLabelForPage:page];
    if (accessibilityLabel.length > 0) {
        view.isAccessibilityElement = YES;
        view.accessibilityLabel = accessibilityLabel;
        view.accessibilityTraits = UIAccessibilityTraitButton;
    }
}

- (NSString*)accessibilityLabelForPage:(EAIntroPage*)page {
    NSString *accessibilityLabel = nil;
    if (page.title) {
        if (page.desc) {
            accessibilityLabel = [NSString stringWithFormat:@"%@, %@", page.title, page.desc];
        } else {
            accessibilityLabel = page.title;
        }
    } else {
        accessibilityLabel = page.desc;
    }
    return accessibilityLabel;
}

- (void)appendCloseViewAtXIndex:(CGFloat*)xIndex {
    UIView *closeView = [[UIView alloc] initWithFrame:CGRectMake(*xIndex, 0, self.bounds.size.width, self.bounds.size.height)];
    closeView.tag = 124;
    [self.scrollView addSubview:closeView];
    
    *xIndex += self.scrollView.frame.size.width;
}

- (void)removeCloseViewAtXIndex:(CGFloat*)xIndex {
    UIView *closeView = [self.scrollView viewWithTag:124];
    if(closeView) {
        [closeView removeFromSuperview];
    }
    
    *xIndex -= self.scrollView.frame.size.width;
}

- (void)buildTitleView {
    if (!self.titleView.superview) {
        [self addSubview:self.titleView];
    }
    
    if (self.titleViewConstraints.count) {
        [self removeConstraints:self.titleViewConstraints];
        [self.titleViewConstraints removeAllObjects];
    }
    
    NSDictionary *views = @{@"titleView" : self.titleView};
    NSDictionary *metrics = @{@"titleViewTopPadding" : @(self.titleViewY), @"titleViewHeight" : @(self.titleView.frame.size.height), @"titleViewWidth" : @(self.titleView.frame.size.width)};
    
    [self.titleViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-titleViewTopPadding@250-[titleView(titleViewHeight)]" options:NSLayoutFormatAlignAllLeft metrics:metrics views:views]];
    [self.titleViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[titleView(titleViewWidth)]" options:NSLayoutFormatAlignAllTop metrics:metrics views:views]];
    [self.titleViewConstraints addObject:[NSLayoutConstraint constraintWithItem:self.titleView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
    
    self.titleView.translatesAutoresizingMaskIntoConstraints = NO;
    [self addConstraints:self.titleViewConstraints];
    
    [self.titleView setNeedsUpdateConstraints];
}

- (void)buildFooterView {
    if (!self.pageControl.superview) {
        [self insertSubview:self.pageControl aboveSubview:self.scrollView];
    }
    
    if (!self.skipButton.superview) {
        [self insertSubview:self.skipButton aboveSubview:self.scrollView];
    }
    
    [self.pageControl.superview bringSubviewToFront:self.pageControl];
    [self.skipButton.superview bringSubviewToFront:self.skipButton];
    
    if (self.footerConstraints.count) {
        [self removeConstraints:self.footerConstraints];
        [self.footerConstraints removeAllObjects];
    }

    CGFloat pageControlHeight = self.pageControl.frame.size.height > 0 ? self.pageControl.frame.size.height : PAGE_CTRL_DEFAULT_HEIGHT;
    CGFloat skipButtonWidth = self.skipButton.frame.size.width > 0 ? self.skipButton.frame.size.width : SKIP_BTN_DEFAULT_WIDTH;
    CGFloat skipButtonHeight = self.skipButton.frame.size.height > 0 ? self.skipButton.frame.size.height : SKIP_BTN_DEFAULT_HEIGHT;
    
    NSDictionary *views = @{@"pageControl" : self.pageControl, @"skipButton" : self.skipButton};
    NSDictionary *metrics = @{@"pageControlBottomPadding" : @(self.pageControlY - pageControlHeight), @"pageControlHeight" : @(pageControlHeight), @"skipButtonBottomPadding" : @(self.skipButtonY - skipButtonHeight), @"skipButtonSideMargin" : @(self.skipButtonSideMargin), @"skipButtonWidth" : @(skipButtonWidth)};
    
    [self.footerConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[pageControl]-|" options:NSLayoutFormatAlignAllCenterX metrics:metrics views:views]];
    [self.footerConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[pageControl(pageControlHeight)]-pageControlBottomPadding@250-|" options:NSLayoutFormatAlignAllBottom metrics:metrics views:views]];
    
    if (self.skipButton && !self.skipButton.hidden) {
        if(self.skipButtonAlignment == EAViewAlignmentCenter) {
            [self.footerConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[skipButton(skipButtonWidth)]" options:NSLayoutFormatAlignAllTop metrics:metrics views:views]];
            [self.footerConstraints addObject:[NSLayoutConstraint constraintWithItem:self.skipButton attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
        } else if(self.skipButtonAlignment == EAViewAlignmentLeft) {
            [self.footerConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-skipButtonSideMargin-[skipButton]" options:NSLayoutFormatAlignAllLeft metrics:metrics views:views]];
        } else if(self.skipButtonAlignment == EAViewAlignmentRight) {
            [self.footerConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[skipButton]-skipButtonSideMargin-|" options:NSLayoutFormatAlignAllRight metrics:metrics views:views]];
        }
        
        if(self.skipButtonY == EA_EMPTY_PROPERTY) {
            [self.footerConstraints addObject:[NSLayoutConstraint constraintWithItem:self.pageControl attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.skipButton attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
        } else {
            [self.footerConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[skipButton]-skipButtonBottomPadding-|" options:NSLayoutFormatAlignAllBottom metrics:metrics views:views]];
        }
    }
    
    [self addConstraints:self.footerConstraints];
    
    [self.pageControl setNeedsUpdateConstraints];
    [self.skipButton setNeedsUpdateConstraints];
}

#pragma mark - UIScrollView Delegate

- (void)scrollViewWillBeginDragging:(EARestrictedScrollView *)scrollView {
    if ([self.delegate respondsToSelector:@selector(intro:pageStartScrolling:withIndex:)] && self.currentPageIndex < [self.pages count]) {
        [self.delegate intro:self pageStartScrolling:_pages[self.currentPageIndex] withIndex:self.currentPageIndex];
    }
}

- (void)scrollViewDidEndDecelerating:(EARestrictedScrollView *)scrollView {
    [self checkIndexForScrollView:scrollView];
    if ([self.delegate respondsToSelector:@selector(intro:pageEndScrolling:withIndex:)] && self.currentPageIndex < [self.pages count]) {
        [self.delegate intro:self pageEndScrolling:_pages[self.currentPageIndex] withIndex:self.currentPageIndex];
    }
}

- (void)scrollViewDidEndScrollingAnimation:(EARestrictedScrollView *)scrollView {
    [self checkIndexForScrollView:scrollView];
}

- (void)scrollViewDidScroll:(EARestrictedScrollView *)scrollView {
    if(!self.scrollingEnabled) {
        return;
    }
    
    CGFloat offset = scrollView.contentOffset.x / self.scrollView.frame.size.width;
    NSUInteger page = (NSUInteger)(offset);
    
    if (page == (_pages.count - 1) && self.swipeToExit) {
        self.alpha = ((self.scrollView.frame.size.width*_pages.count)-self.scrollView.contentOffset.x)/self.scrollView.frame.size.width;
    } else {
        if([self pageForIndex:page]) {
            self.alpha = 1.f;
        }
    }
    
    [self crossDissolveForOffset:offset];
    
    if (self.visiblePageIndex < _pages.count) {
        self.pageControl.currentPage = self.visiblePageIndex;
        
        [self makePanelVisibleAtIndex:self.visiblePageIndex];
    }
    
    if ([self.delegate respondsToSelector:@selector(intro:didScrollWithOffset:)]) {
        [self.delegate intro:self didScrollWithOffset:offset];
    }
}

CGFloat easeOutValue(CGFloat value) {
    CGFloat inverse = value - 1.f;
    return (CGFloat) (1.f + inverse * inverse * inverse);
}

- (void)crossDissolveForOffset:(CGFloat)offset {
    NSUInteger page = (NSUInteger)(offset);
    CGFloat alphaValue = offset - page;
    
    if (alphaValue < 0 && self.visiblePageIndex == 0){
        self.pageBgBack.image = nil;
        return;
    }
    
    self.pageBgFront.alpha = [self alphaForPageIndex:page];
    self.pageBgFront.image = [self bgImageForPage:page];
    self.pageBgFront.backgroundColor = [self bgColorForPage:page];
    self.pageBgBack.alpha = 0;
    self.pageBgBack.image = [self bgImageForPage:page+1];
    self.pageBgBack.backgroundColor = [self bgColorForPage:page+1];
    
    CGFloat backLayerAlpha = alphaValue;
    CGFloat frontLayerAlpha = (1 - alphaValue);
    
    if (self.easeOutCrossDisolves) {
        backLayerAlpha = easeOutValue(backLayerAlpha);
        frontLayerAlpha = easeOutValue(frontLayerAlpha);
    }
    
    self.pageBgBack.alpha = MIN(backLayerAlpha,[self alphaForPageIndex:page+1]);
    self.pageBgFront.alpha = MIN(frontLayerAlpha,[self alphaForPageIndex:page]);
    
    if(self.titleView) {
        if([self showTitleViewForPage:page] && [self showTitleViewForPage:page+1]) {
            [self.titleView setAlpha:1.0];
        } else if(![self showTitleViewForPage:page] && ![self showTitleViewForPage:page+1]) {
            [self.titleView setAlpha:0.0];
        } else if([self showTitleViewForPage:page]) {
            [self.titleView setAlpha:(1 - alphaValue)];
        } else {
            [self.titleView setAlpha:alphaValue];
        }
    }
    
    if(self.skipButton && self.showSkipButtonOnlyOnLastPage) {
        if(page < (long)[self.pages count] - 2) {
            [self.skipButton setAlpha:0.0];
        } else if(page == [self.pages count] - 1) {
            [self.skipButton setAlpha:(1 - alphaValue)];
        } else {
            [self.skipButton setAlpha:alphaValue];
        }
    }
}

#pragma mark - UIView lifecycle calls

- (void)layoutSubviews {
    [super layoutSubviews];

    // Get amount of pages:
    NSInteger numberOfPages = _pages.count;
    
    // Increase with 1 page when feature enabled:
    if (self.swipeToExit) {
        numberOfPages = numberOfPages + 1;
    }
    
    // Descrease to limited index when scrolling is restricted:
    if (self.limitPageIndex != -1) {
        numberOfPages = self.limitPageIndex + 1;
    }
    
    // Adjust contentSize of ScrollView:
    CGSize newContentSize = CGSizeMake(numberOfPages * self.scrollView.frame.size.width, self.scrollView.frame.size.height);
    if(self.scrollView.contentOffset.x > newContentSize.width) {
        CGPoint newOffset = self.scrollView.contentOffset;
        if (self.swipeToExit) {
            newOffset.x = newContentSize.width - (self.scrollView.frame.size.width * 2);
        } else {
            newOffset.x = newContentSize.width - self.scrollView.frame.size.width;
        }
        self.scrollView.contentOffset = newOffset;
    }
    self.scrollView.contentSize = newContentSize;
    
    // Adjust frame of each page:
    NSUInteger i = 0;
    for (EAIntroPage *page in _pages) {
        page.pageView.frame = CGRectMake(i * self.scrollView.bounds.size.width,
                                         0,
                                         self.scrollView.bounds.size.width,
                                         self.scrollView.bounds.size.height);
        i++;
    }
    
    // Adjust scrolling to fit resized page:
    CGFloat offset = self.currentPageIndex * self.scrollView.frame.size.width;
    CGRect pageRect = { .origin.x = offset, .origin.y = 0.0, .size.width = self.scrollView.frame.size.width, .size.height = self.scrollView.frame.size.height };
    [self.scrollView scrollRectToVisible:pageRect animated:NO];
    
    // Adjust restricted scroll area:
    if(!self.scrollingEnabled) {
        self.scrollView.restrictionArea = CGRectMake(self.visiblePageIndex * self.bounds.size.width, 0, self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
    } else {
        self.scrollView.restrictionArea = CGRectZero;
    }
}

#pragma mark - Custom setters

- (void)setScrollingEnabled:(BOOL)scrollingEnabled {
    if(!scrollingEnabled) {
        self.scrollView.restrictionArea = CGRectMake(self.visiblePageIndex * self.bounds.size.width, 0, self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
    } else {
        self.scrollView.restrictionArea = CGRectZero;
    }
    
    _scrollingEnabled = scrollingEnabled;
}

- (void)setPages:(NSArray<EAIntroPage *> *)pages {
    _pages = [pages copy];
    [self.scrollView removeFromSuperview];
    self.scrollView = nil;
    
    _currentPageIndex = 0;
    self.pageControl.numberOfPages = _pages.count;
    self.pageControl.currentPage = self.currentPageIndex;
    
    [self buildScrollView];
}

- (void)setBgImage:(UIImage *)bgImage {
    _bgImage = bgImage;
    self.bgImageView.image = _bgImage;
    
    [self setNeedsDisplay];
}

- (void)setBgViewContentMode:(UIViewContentMode)bgViewContentMode {
    _bgViewContentMode = bgViewContentMode;
    self.bgImageView.contentMode = bgViewContentMode;
    self.pageBgBack.contentMode = bgViewContentMode;
    self.pageBgFront.contentMode = bgViewContentMode;
    
    [self setNeedsDisplay];
}

- (void)setSwipeToExit:(BOOL)swipeToExit {
    if (swipeToExit != _swipeToExit) {
        CGFloat contentXIndex = self.scrollView.contentSize.width;
        if(swipeToExit) {
            [self appendCloseViewAtXIndex:&contentXIndex];
        } else {
            [self removeCloseViewAtXIndex:&contentXIndex];
        }
        self.scrollView.contentSize = CGSizeMake(contentXIndex, self.scrollView.frame.size.height);
    }
    _swipeToExit = swipeToExit;
}

- (void)setTitleView:(UIView *)titleView {
    [_titleView removeFromSuperview];
    _titleView = titleView;
    
    if ([_titleView respondsToSelector:@selector(setTranslatesAutoresizingMaskIntoConstraints:)]) {
        _titleView.translatesAutoresizingMaskIntoConstraints = NO;
    }
    
    CGFloat offset = self.scrollView.contentOffset.x / self.scrollView.frame.size.width;
    [self crossDissolveForOffset:offset];
    
    [self buildTitleView];
    
    [self setNeedsDisplay];
}

- (void)setTitleViewY:(CGFloat)titleViewY {
    _titleViewY = titleViewY;
    
    [self buildTitleView];
    
    [self setNeedsDisplay];
}

- (void)setPageControl:(UIPageControl *)pageControl {
    if(!pageControl) {
        _pageControl.hidden = YES;
        return;
    }
    
    [_pageControl removeFromSuperview];
    _pageControl = pageControl;
    [self applyDefaultsToPageControl];
    
    [self buildFooterView];
    
    [self setNeedsDisplay];
}

- (void)setPageControlY:(CGFloat)pageControlY {
    _pageControlY = pageControlY;
    
    [self buildFooterView];
    
    [self setNeedsDisplay];
}

- (void)setSkipButton:(UIButton *)skipButton {
    if(!skipButton) {
        _skipButton.hidden = YES;
        return;
    }
    
    [_skipButton removeFromSuperview];
    _skipButton = skipButton;
    _skipButton.hidden = NO;
    [self applyDefaultsToSkipButton];
    
    [self buildFooterView];
    
    [self setNeedsDisplay];
}

- (void)setSkipButtonY:(CGFloat)skipButtonY {
    _skipButtonY = skipButtonY;
    
    [self buildFooterView];
    
    [self setNeedsDisplay];
}

- (void)setSkipButtonSideMargin:(CGFloat)skipButtonSideMargin {
    _skipButtonSideMargin = skipButtonSideMargin;
    
    [self buildFooterView];
    
    [self setNeedsDisplay];
}

- (void)setSkipButtonAlignment:(EAViewAlignment)skipButtonAlignment {
    _skipButtonAlignment = skipButtonAlignment;
    
    [self buildFooterView];
    
    [self setNeedsDisplay];
}

- (void)setShowSkipButtonOnlyOnLastPage:(BOOL)showSkipButtonOnlyOnLastPage {
    _showSkipButtonOnlyOnLastPage = showSkipButtonOnlyOnLastPage;
    
    CGFloat offset = self.scrollView.contentOffset.x / self.scrollView.frame.size.width;
    [self crossDissolveForOffset:offset];
}

- (void)setUseMotionEffects:(BOOL)useMotionEffects {
    if(_useMotionEffects == useMotionEffects) {
        return;
    }
    _useMotionEffects = useMotionEffects;
    
    if(useMotionEffects) {
        [self addMotionEffectsOnBg];
    } else {
        [self removeMotionEffectsOnBg];
    }
}

- (void)setMotionEffectsRelativeValue:(CGFloat)motionEffectsRelativeValue {
    _motionEffectsRelativeValue = motionEffectsRelativeValue;
    if(self.useMotionEffects) {
        [self addMotionEffectsOnBg];
    }
}

#pragma mark - Motion effects actions

- (void)addMotionEffectsOnBg {
    if(![self respondsToSelector:@selector(setMotionEffects:)]) {
        return;
    }
    
    CGRect parallaxFrame = CGRectMake(-self.motionEffectsRelativeValue, -self.motionEffectsRelativeValue, self.bounds.size.width + (self.motionEffectsRelativeValue * 2), self.bounds.size.height + (self.motionEffectsRelativeValue * 2));
    [self.pageBgFront setFrame:parallaxFrame];
    [self.pageBgBack setFrame:parallaxFrame];
    [self.bgImageView setFrame:parallaxFrame];
    
    // Set vertical effect
    UIInterpolatingMotionEffect *verticalMotionEffect =
    [[UIInterpolatingMotionEffect alloc]
     initWithKeyPath:@"center.y"
     type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
    verticalMotionEffect.minimumRelativeValue = @(self.motionEffectsRelativeValue);
    verticalMotionEffect.maximumRelativeValue = @(-self.motionEffectsRelativeValue);
    
    // Set horizontal effect
    UIInterpolatingMotionEffect *horizontalMotionEffect =
    [[UIInterpolatingMotionEffect alloc]
     initWithKeyPath:@"center.x"
     type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
    horizontalMotionEffect.minimumRelativeValue = @(self.motionEffectsRelativeValue);
    horizontalMotionEffect.maximumRelativeValue = @(-self.motionEffectsRelativeValue);
    
    // Create group to combine both
    UIMotionEffectGroup *group = [UIMotionEffectGroup new];
    group.motionEffects = @[horizontalMotionEffect, verticalMotionEffect];
    
    // Add both effects to all background image views
    [UIView animateWithDuration:0.5f animations:^{
        [self.pageBgFront setMotionEffects:@[group]];
        [self.pageBgBack setMotionEffects:@[group]];
        [self.bgImageView setMotionEffects:@[group]];
    }];
}

- (void)removeMotionEffectsOnBg {
    if(![self respondsToSelector:@selector(removeMotionEffect:)]) {
        return;
    }
    
    [UIView animateWithDuration:0.5f animations:^{
        [self.pageBgFront removeMotionEffect:self.pageBgFront.motionEffects[0]];
        [self.pageBgBack removeMotionEffect:self.pageBgBack.motionEffects[0]];
        [self.bgImageView removeMotionEffect:self.bgImageView.motionEffects[0]];
    }];
}

#pragma mark - Actions

- (void)showFullscreen {
    [self showFullscreenWithAnimateDuration:0.3f andInitialPageIndex:0];
}

- (void)showFullscreenWithAnimateDuration:(CGFloat)duration {
    [self showFullscreenWithAnimateDuration:duration andInitialPageIndex:0];
}

- (void)showFullscreenWithAnimateDuration:(CGFloat)duration andInitialPageIndex:(NSUInteger)initialPageIndex {
    UIView *selectedView;
    
    NSEnumerator *frontToBackWindows = [UIApplication.sharedApplication.windows reverseObjectEnumerator];
    for (UIWindow *window in frontToBackWindows) {
        BOOL windowOnMainScreen = window.screen == UIScreen.mainScreen;
        BOOL windowIsVisible = !window.hidden && window.alpha > 0;
        BOOL windowLevelNormal = window.windowLevel == UIWindowLevelNormal;
        
        if (windowOnMainScreen && windowIsVisible && windowLevelNormal) {
            selectedView = window;
            break;
        }
    }
    
    [self showInView:selectedView animateDuration:duration withInitialPageIndex:initialPageIndex];
}

- (void)showInView:(UIView *)view {
    [self showInView:view animateDuration:0.3f withInitialPageIndex:0];
}

- (void)showInView:(UIView *)view animateDuration:(CGFloat)duration {
    [self showInView:view animateDuration:duration withInitialPageIndex:0];
}

- (void)showInView:(UIView *)view animateDuration:(CGFloat)duration withInitialPageIndex:(NSUInteger)initialPageIndex {
    if(![self pageForIndex:initialPageIndex]) {
        NSLog(@"Wrong initialPageIndex received: %ld",(long)initialPageIndex);
        return;
    }

	self.skipped = NO;
    self.currentPageIndex = initialPageIndex;
    self.alpha = 0;

    if(self.superview != view) {
        [view addSubview:self];
    } else {
        [view bringSubviewToFront:self];
    }
   
    [UIView animateWithDuration:duration animations:^{
        self.alpha = 1;
    } completion:^(BOOL finished) {
        EAIntroPage *currentPage = _pages[self.currentPageIndex];
        if(currentPage.onPageDidAppear) currentPage.onPageDidAppear();
        
        if ([(id)self.delegate respondsToSelector:@selector(intro:pageAppeared:withIndex:)]) {
            [self.delegate intro:self pageAppeared:_pages[self.currentPageIndex] withIndex:self.currentPageIndex];
        }
    }];
}

- (void)hideWithFadeOutDuration:(CGFloat)duration {
    if ([self.delegate respondsToSelector:@selector(introWillFinish:wasSkipped:)]) {
        [self.delegate introWillFinish:self wasSkipped:self.skipped];
    }

    [UIView animateWithDuration:duration animations:^{
        self.alpha = 0;
    } completion:^(BOOL finished){
		[self finishIntroductionAndRemoveSelf];
	}];
}

- (void)setCurrentPageIndex:(NSUInteger)currentPageIndex {
    [self setCurrentPageIndex:currentPageIndex animated:NO];
}

- (void)setCurrentPageIndex:(NSUInteger)currentPageIndex animated:(BOOL)animated {
    if(![self pageForIndex:currentPageIndex]) {
        NSLog(@"Wrong currentPageIndex received: %ld",(long)currentPageIndex);
        return;
    }
    
    _currentPageIndex = currentPageIndex;
    
    [self scrollToPageForIndex:currentPageIndex animated:animated];
}

- (void)scrollToPageForIndex:(NSUInteger)newPageIndex animated:(BOOL)animated
{
    if(![self pageForIndex:newPageIndex]) {
        NSLog(@"Wrong newPageIndex received: %ld",(long)newPageIndex);
        return;
    }

    CGFloat offset = newPageIndex * self.scrollView.frame.size.width;
    CGRect pageRect = { .origin.x = offset, .origin.y = 0.0, .size.width = self.scrollView.frame.size.width, .size.height = self.scrollView.frame.size.height };
    [self.scrollView scrollRectToVisible:pageRect animated:animated];
    
    if(!animated) {
        [self scrollViewDidScroll:self.scrollView];
    }
}

- (void)handleBackgroundTap:(UIGestureRecognizer *)tapRecognizer {
    if (tapRecognizer.state == UIGestureRecognizerStateEnded) {
        [self goToNext:tapRecognizer];
    }
}

- (IBAction)goToNext:(id)sender {
    if(!self.tapToNext) {
        return;
    }
    if(self.currentPageIndex + 1 >= [self.pages count]) {
        [self hideWithFadeOutDuration:0.3];
    } else {
        // Just scroll to the new page.
        // After scrolling ends, we call -checkIndexForScrollView:, which itself sets the new currentPageIndex.
        [self scrollToPageForIndex:self.currentPageIndex + 1 animated:YES];
    }
}

- (void)setLimitPageIndex:(NSInteger)limitPageIndex {
    _limitPageIndex = limitPageIndex;
    
    if (limitPageIndex < 0 || limitPageIndex >= self.pages.count) {
        _limitPageIndex = -1;
        self.scrollingEnabled = YES;
        return;
    } else {
        self.scrollView.restrictionArea = CGRectMake(0, 0, (self.limitPageIndex + 1) * self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
    }
}

@end