//
//  BarIndicatorView.swift
//  MVMCoreUI
//
//  Created by Kevin Christiano on 2/3/20.
//  Copyright © 2020 Verizon Wireless. All rights reserved.
//

import UIKit


open class BarsIndicatorView: CarouselIndicator {
    //--------------------------------------------------
    // MARK: - Stored Properties
    //--------------------------------------------------
    
    /// Represents the data of each individual bar.
    private class IndicatorBar: View {
        // Dimensions are based on InVision Design Guidelines.
        static let width: CGFloat = 24
        static let selectedHeight: CGFloat = 4
        static let unselectedHeight: CGFloat = 1
        
        var constraint: NSLayoutConstraint?
        
        init() {
            super.init(frame: .zero)
        }
        
        public required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        public required init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
            fatalError("init(model:_:_:) has not been implemented")
        }
        
        override func setupView() {
            super.setupView()
            isAccessibilityElement = true
            accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "AccTabHint")
            widthAnchor.constraint(equalToConstant: BarsIndicatorView.IndicatorBar.width).isActive = true
            accessibilityTraits = .button
        }
    }
    
    /// Structured container to hold and layout the indicator bars.
    public let stackView: UIStackView = {
        let stackView = UIStackView()
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.isAccessibilityElement = false
        stackView.axis = .horizontal
        stackView.alignment = .bottom
        stackView.distribution = .equalSpacing
        stackView.spacing = 6
        return stackView
    }()
    
    /// Reference to each bar displayed.
    private var barReferences: [IndicatorBar] = []
    
    //--------------------------------------------------
    // MARK: - Computed Properties
    //--------------------------------------------------
    
    /// Convenience to access the model.
    public var barsCarouselIndicatorModel: BarsCarouselIndicatorModel? {
        model as? BarsCarouselIndicatorModel
    }
    
    open override var isEnabled: Bool {
        didSet {
            for (i, indicatorBar) in barReferences.enumerated() {
                if i == currentIndex {
                    indicatorBar.backgroundColor = isEnabled ? currentIndicatorColor : disabledIndicatorColor
                } else {
                    indicatorBar.backgroundColor = isEnabled ? indicatorColor : disabledIndicatorColor
                }
            }
        }
    }

    /// Colors the currently selected index, unique from other indicators
    public var currentIndicatorColor: UIColor {
        get { barsCarouselIndicatorModel?.currentIndicatorColor.uiColor ?? indicatorColor }
        set {
            barsCarouselIndicatorModel?.currentIndicatorColor = Color(uiColor: newValue)
            
            if isEnabled && !barReferences.isEmpty {
                barReferences[currentIndex].backgroundColor = newValue
            }
        }
    }
    
    /// The color of the indicator bars.
    public override var indicatorColor: UIColor {
        get { super.indicatorColor }
        set {
            super.indicatorColor = newValue
            refreshBarColors(with: newValue)
        }
    }
    
    //--------------------------------------------------
    // MARK: - Setup
    //--------------------------------------------------
    
    open override func setupView() {
        super.setupView()
        
        addSubview(stackView)
        isUserInteractionEnabled = false
        isAccessibilityElement = false
        
        NSLayoutConstraint.activate([
            stackView.heightAnchor.constraint(equalToConstant: Padding.One),
            heightAnchor.constraint(equalTo: stackView.heightAnchor),
            stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
            stackView.topAnchor.constraint(equalTo: topAnchor),
            bottomAnchor.constraint(equalTo: stackView.bottomAnchor),
            trailingAnchor.constraint(equalTo: stackView.trailingAnchor)
        ])
    }
    
    //--------------------------------------------------
    // MARK: - Methods
    //--------------------------------------------------
    
    /// Updates the color of all indicator bars.
    private func refreshBarColors(with color: UIColor) {
        
        guard isEnabled else { return }
        
        for (i, indicatorBar) in barReferences.enumerated() {
            indicatorBar.backgroundColor = i == currentIndex ? currentIndicatorColor : color
        }
    }
    
    /// Creates all the indicato bars for display.
    func generateBars() {
        
        var bars = [IndicatorBar]()
        
        for i in 0..<numberOfPages {
            let indicatorBar = createIndicatorBar(index: i)
            stackView.addArrangedSubview(indicatorBar)
            bars.append(indicatorBar)
        }
        
        accessibilityElements = stackView.arrangedSubviews
        barReferences = bars
    }
    
    /// Removes an indicator bar from the view.
    private func removeBar() {
        let lastView = barReferences.removeLast()
        stackView.removeArrangedSubview(lastView)
        lastView.removeFromSuperview()
        accessibilityElements = stackView.arrangedSubviews
    }
    
    /// Based of the sign of the difference indicator bars will be removed or added.
    private func balanceBarCount(_ difference: Int) {
        
        if difference > 0 {
            // If positive, add n bars.
            var copyBars = barReferences
            
            for _ in 0..<difference {
                let indicatorBar = createIndicatorBar(index: barReferences.count)
                indicatorBar.constraint?.constant = IndicatorBar.unselectedHeight
                stackView.addArrangedSubview(indicatorBar)
                copyBars.append(indicatorBar)
            }
            
            accessibilityElements = stackView.arrangedSubviews
            barReferences = copyBars
            
        } else if difference < 0 {
            // If negative, remove n bars.
            for _ in 0..<(difference * -1) {
                removeBar()
            }
        }
    }
    
    private func createIndicatorBar(index: Int) -> IndicatorBar {
        
        let bar = IndicatorBar()
        setAccessibilityLabel(view: bar, index: index)
        bar.backgroundColor = isEnabled ? (index == currentIndex ? currentIndicatorColor : indicatorColor) : disabledIndicatorColor
        let barHeight = index == currentIndex ? BarsIndicatorView.IndicatorBar.selectedHeight : BarsIndicatorView.IndicatorBar.unselectedHeight
        let heightConstraint = bar.heightAnchor.constraint(equalToConstant: barHeight)
        heightConstraint.isActive = true
        bar.constraint = heightConstraint
        
        return bar
    }
    
    /// Refreshes the accessibility labels to read "x of n".
    private func refreshAccessibilityLabels() {
        for i in 0..<barReferences.count {
            setAccessibilityLabel(view: barReferences[i], index: i)
        }
    }
    
    private func setAccessibilityLabel(view: UIView, index: Int) {
        guard let accessibleValueFormat = accessibilityValueFormat,
              let accessibleIndex = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: index + 1))
        else { return }
        
        view.accessibilityLabel = String(format: accessibleValueFormat, accessibleIndex, numberOfPages)
    }
    
    public override func assessTouchOf(_ touchPoint_X: CGFloat) {
        
        currentIndex = barReferences.firstIndex { $0.frame.maxX >= touchPoint_X && $0.frame.minX <= touchPoint_X } ?? 0
        performAction()
    }
    
    open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
        super.set(with: model, delegateObject, additionalData)
        
        guard let model = model as? BarsCarouselIndicatorModel else { return }
        
        currentIndicatorColor = model.inverted ? model.indicatorColor_inverted.uiColor : model.currentIndicatorColor.uiColor
    }
    
    //--------------------------------------------------
    // MARK: - IndicatorViewProtocol
    //--------------------------------------------------
    
    public override func reset() {
        super.reset()
        barReferences.forEach { $0.removeFromSuperview() }
        barReferences = []
    }
    
    public override func updateUI(previousIndex: Int, newIndex: Int, totalCount: Int, isAnimated: Bool) {
        
        // Ensure that at least one bar exists.
        guard !barReferences.isEmpty else {
            generateBars()
            return
        }
        
        guard newIndex < totalCount else { return }
        
        // Ensure the number of pages matches the number of bar references.
        if (totalCount - barReferences.count) != 0 {
            barReferences.forEach {
                $0.backgroundColor = isEnabled ? indicatorColor : disabledIndicatorColor
                $0.constraint?.constant = IndicatorBar.unselectedHeight
            }
            
            balanceBarCount(numberOfPages - barReferences.count)
            refreshAccessibilityLabels()
        }
        
        let expression = { [self] in
            barReferences[previousIndex].backgroundColor = isEnabled ? indicatorColor : disabledIndicatorColor
            barReferences[previousIndex].constraint?.constant = IndicatorBar.unselectedHeight
            barReferences[newIndex].backgroundColor = isEnabled ? currentIndicatorColor : disabledIndicatorColor
            barReferences[newIndex].constraint?.constant = IndicatorBar.selectedHeight
            layoutIfNeeded()
        }
        
        // Perform the animation.
        isAnimated ? UIView.animate(withDuration: 0.25) { expression() } : expression()
    }
}
