//
//  PageViewController.swift
//  PaiAi
//
//  Created by ffib on 2018/12/6.
//  Copyright © 2018 yb. All rights reserved.
//

import UIKit

fileprivate let baseTag = 55161750

open class PageViewController: UIViewController {
    
    private enum ScrollDirection {
        case left
        case right
    }

    private var contentRect = CGRect.zero
    
       //animation auxiliary
    private var currentIndex: Int = 0
    private var distance: CGFloat = 0
    private var currentItem: UILabel?
    private var startOffset: CGFloat = 0
    private var isBeginScroll: Bool = false
    private var menuItemWidths: [CGFloat] = []
    private var direction: ScrollDirection = .right
    private var sliderConstraint: NSLayoutConstraint?
    
    public private(set) lazy var scrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.bounces = false
        scrollView.delegate = self
        scrollView.isPagingEnabled = true
        scrollView.alwaysBounceVertical = false
        scrollView.showsVerticalScrollIndicator = false
        scrollView.showsHorizontalScrollIndicator = false
        return scrollView
    }()
    
    public private(set) lazy var menuView: UIView = {
        let view = UIView()
        
        return view
    }()
    
    public private(set) lazy var sliderView: UIView = {
        let view = UIView()
        view.layer.cornerRadius = 2.5
        view.backgroundColor = option.selectedColor
        
        return view
    }()
   
    public var option = PageOption()
    public var pageItems = [PageItem]() {
        didSet {
            setPageItems()
            setMenuItems()
        }
    }
    override open func viewDidLoad() {
        super.viewDidLoad()
        contentRect = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
        
        if #available(iOS 11, *) {
            scrollView.contentInsetAdjustmentBehavior = .never
        }
        
        constructViewHierarchy()
        activateConstraints()
        setMenuGestureRecognizer()
        setupNavigationBarInteractivePopDelegate()
    }
    
    open override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        cacheMenuItemWidth()
    }
    
    private func cacheMenuItemWidth() {
        guard menuItemWidths.isEmpty else { return }
        menuItemWidths = Array(repeating: 0, count: menuView.subviews.count - 1)
        for view in menuView.subviews {
            guard let label = view as? UILabel else { continue }
            menuItemWidths[label.tag - baseTag] = label.frame.width
        }
    }
    
    private func constructViewHierarchy() {
        view.addSubview(scrollView)
        navigationController?.navigationBar.addSubview(menuView)
        menuView.addSubview(sliderView)
    }
}

/// set PageItem and MenuItem
fileprivate extension PageViewController {
    func setPageItems() {
        guard !pageItems.isEmpty else { return }
        var navigationBarHeight: CGFloat
        if UIApplication.shared.statusBarFrame.height == 44 {
            navigationBarHeight = 88
        } else {
            navigationBarHeight = 64
        }
        scrollView.contentSize = CGSize(width: contentRect.width * CGFloat(pageItems.count),
                                        height: contentRect.height - navigationBarHeight)
        var last: UIView?
        for item in pageItems {
            addChild(item.viewController)
            scrollView.addSubview(item.viewController.view)
            
            item.viewController.view.translatesAutoresizingMaskIntoConstraints = false
            
            let width = item.viewController.view
                .widthAnchor
                .constraint(equalTo: scrollView.widthAnchor)
            let height = item.viewController.view
                .heightAnchor
                .constraint(equalTo: scrollView.heightAnchor)
            let top = item.viewController.view
                .topAnchor
                .constraint(equalTo: scrollView.topAnchor)
            let leading = item.viewController.view
                .leadingAnchor
                .constraint(equalTo: last?.trailingAnchor ?? scrollView.leadingAnchor)
            
            NSLayoutConstraint.activate([width, height, top, leading])
            last = item.viewController.view
        }
    }
    
    func setMenuItems() {
        guard !pageItems.isEmpty else { return }
        
        var last: UILabel?
        for (i, item) in pageItems.enumerated() {
            let label = UILabel()
            label.text = item.title
            label.tag = baseTag + i
            label.font = option.font
            label.textAlignment = .center
            label.textColor = option.normalColor
            label.translatesAutoresizingMaskIntoConstraints = false
            
            menuView.addSubview(label)
            
            let left: NSLayoutConstraint
            if let lastLabel = last {
                left = label.leftAnchor
                    .constraint(equalTo: lastLabel.rightAnchor, constant: option.spacing)
            } else {
                left = label.leadingAnchor
                    .constraint(equalTo: menuView.leadingAnchor)
            }
            
            let centerY = label.centerYAnchor
                .constraint(equalTo: menuView.centerYAnchor)
            
            if i == 0 {
                label.textColor = option.selectedColor
                label.font = UIFont.systemFont(ofSize: option.font.pointSize + 1)
                currentItem = label
            } else if i == pageItems.count - 1 {
                NSLayoutConstraint.activate([label.trailingAnchor
                    .constraint(equalTo: menuView.trailingAnchor)])
            }
            
            NSLayoutConstraint.activate([left, centerY])
            last = label
        }
        
        setSliderViewDetail()
    }
    
    func setSliderViewDetail() {
        guard let label = menuView.viewWithTag(baseTag) else { return }
        sliderConstraint = sliderView.centerXAnchor
            .constraint(equalTo: label.centerXAnchor)
        
        NSLayoutConstraint.activate([sliderConstraint!])
    }
}

/// layout
fileprivate extension PageViewController {
    func activateConstraints() {
        activateConstraintsMenuView()
        activateConstraintsSliderView()
        activateConstraintsScrollView()
    }
    
    func activateConstraintsScrollView() {
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        
        let top = scrollView.topAnchor.constraint(equalTo: view.topAnchor)
        let width = scrollView.widthAnchor.constraint(equalTo: view.widthAnchor)
        let bottom = scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        let height = scrollView.heightAnchor.constraint(equalTo: view.heightAnchor)
        let leading = scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
        let trailing = scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
        
        NSLayoutConstraint.activate([width, height, top, leading, bottom, trailing])
    }
    
    func activateConstraintsMenuView() {
        guard let barContentView = navigationController?.navigationBar else {  return }
        
        menuView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            menuView.topAnchor.constraint(equalTo: barContentView.topAnchor),
            menuView.bottomAnchor.constraint(equalTo: barContentView.bottomAnchor),
            menuView.centerXAnchor.constraint(equalTo: barContentView.centerXAnchor)
            ])
    }
    
    func activateConstraintsSliderView() {
        
        sliderView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            sliderView.widthAnchor.constraint(equalToConstant: 5),
            sliderView.heightAnchor.constraint(equalToConstant: 5),
            sliderView.bottomAnchor.constraint(equalTo: menuView.bottomAnchor, constant: -2)
            ])
    }
}

/// GuestureRecognizer
fileprivate extension PageViewController {
    func setMenuGestureRecognizer() {
        let tap = UITapGestureRecognizer(target: self, action: #selector(tapMenu(tap:)))
        menuView.addGestureRecognizer(tap)
    }
    
    @objc func tapMenu(tap: UITapGestureRecognizer) {
        var x = tap.location(in: menuView).x
        for (i, width) in menuItemWidths.enumerated() {
            x -= (width + option.spacing / 2)
            guard x <= 0 else { continue }
            if i != currentIndex {
                didSelect(i)
            }
            return
        }
    }
}

/// UIScrollViewDelegate implementation
extension PageViewController: UIScrollViewDelegate {
    
    public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        startOffset = self.scrollView.contentOffset.x
        isBeginScroll = true
    }
    
    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
        guard scrollView.isDragging || scrollView.isDecelerating || scrollView.isTracking else { return }
        initializeScrollParameter()
        moveSlider(percentage: (self.scrollView.contentOffset.x - startOffset ) / contentRect.width)
    }
    
    public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        guard !decelerate else { return }
        pageViewDidEndScroll()
    }

    public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        pageViewDidEndScroll()
    }
    
    public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
        pageViewDidEndScroll()
    }
}

/// linkage
fileprivate extension PageViewController {
    func didSelect(_ index: Int) {
        startOffset = self.scrollView.contentOffset.x
        sliderAnimation(index)
        didSelectPageItem(index)
    }
    
    func initializeScrollParameter() {
        if isBeginScroll {
            direction = self.scrollView.contentOffset.x - startOffset > 0 ? .right : .left
            guard let label = currentItem else { return }
            switch direction {
            case .left:
                guard label.tag - baseTag - 1 > 0 else { return }
                distance = menuItemWidths[label.tag - baseTag] / 2 + menuItemWidths[label.tag - baseTag - 1] / 2 + option.spacing
            case .right:
                guard label.tag - baseTag + 1 < menuItemWidths.count else { return }
                distance = menuItemWidths[label.tag - baseTag] / 2 + menuItemWidths[label.tag - baseTag + 1] / 2 + option.spacing
            }
            isBeginScroll = false
        }
    }
}

/// menu linkage
fileprivate extension PageViewController {
    func moveSlider(percentage: CGFloat) {
        sliderConstraint?.constant = percentage * distance
    }
    
    func didSelectMenuItem(_ index: Int) {
        guard let currentLabel = currentItem,
            let label = menuView.viewWithTag(baseTag + index) as? UILabel else { return }
        
        currentItem = label
        
        currentLabel.font = option.font
        currentLabel.textColor = option.normalColor
        
        label.textColor = option.selectedColor
        label.font = UIFont.systemFont(ofSize: option.font.pointSize + 1)
        
        currentIndex = index
        
        updateSliderConstraint()
    }
    
    func sliderAnimation(_ index: Int) {
        let isLeft = currentIndex - index > 0
        var animationDistance: CGFloat = 0
        for i in isLeft ? (index..<currentIndex) : (currentIndex..<index) {
            animationDistance += menuItemWidths[i] / 2 + menuItemWidths[i + 1] / 2 + option.spacing
        }
        
        UIView.animate(withDuration: 0.25) {
            self.sliderConstraint?.constant = isLeft ? -animationDistance : animationDistance
            self.menuView.layoutIfNeeded()
        }
    }
    
    func updateSliderConstraint() {
        NSLayoutConstraint.deactivate([sliderConstraint!])
        sliderConstraint = sliderView.centerXAnchor.constraint(equalTo: currentItem!.centerXAnchor)
        NSLayoutConstraint.activate([sliderConstraint!])
    }
}

/// page linkage
fileprivate extension PageViewController {
    func didSelectPageItem(_ index: Int) {
        scrollView.setContentOffset(CGPoint.init(x: CGFloat(index) * contentRect.width, y: 0), animated: true)
    }
    
    func pageViewDidEndScroll() {
        guard self.scrollView.contentOffset.x != startOffset else { return }
        let index = Int(self.scrollView.contentOffset.x / contentRect.width)
        
        pageItems[index].viewController.didMove(toParent: self)

        switch direction {
        case .left:
            didSelectMenuItem(index)
        case .right:
            didSelectMenuItem(index)
        }
    }
}

extension PageViewController: NavigationBarInteractiveViewController {    
    public var navigationView: UIView {
        return menuView
    }
}