//
//  PollOptionsView.swift
//  Tusker
//
//  Created by Shadowfacts on 4/26/21.
//  Copyright © 2021 Shadowfacts. All rights reserved.
//

import UIKit
import Pachyderm

class PollOptionsView: UIControl {
    
    var mastodonController: MastodonController!

    var checkedOptionIndices: [Int] {
        options.enumerated().filter { $0.element.checkboxIfInitialized?.isChecked == true }.map(\.offset)
    }
    var checkedOptionsChanged: (() -> Void)?
    
    private let stack: UIStackView
    private var options: [PollOptionView] = []
    
    private var poll: Poll!
    private var animator: UIViewPropertyAnimator!
    private var currentSelectedOptionIndex: Int?
    
    private let animationDuration: TimeInterval = 0.1
    private let scaledTransform = CGAffineTransform(scaleX: 0.95, y: 0.95)
    
    private let generator = UISelectionFeedbackGenerator()
    
    override var isEnabled: Bool {
        didSet {
            options.forEach { $0.checkboxIfInitialized?.readOnly = !isEnabled }
        }
    }
    
    override var accessibilityElements: [Any]? {
        get { options }
        set {}
    }
    
    override init(frame: CGRect) {
        stack = UIStackView()
        
        super.init(frame: frame)
        
        stack.translatesAutoresizingMaskIntoConstraints = false
        stack.axis = .vertical
        stack.spacing = 4
        addSubview(stack)
        
        NSLayoutConstraint.activate([
            stack.leadingAnchor.constraint(equalTo: leadingAnchor),
            stack.trailingAnchor.constraint(equalTo: trailingAnchor),
            stack.topAnchor.constraint(equalTo: topAnchor),
            stack.bottomAnchor.constraint(equalTo: bottomAnchor),
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func updateUI(poll: Poll) {
        self.poll = poll
        
        if poll.options.count > options.count {
            for _ in 0..<(poll.options.count - options.count) {
                let optView = PollOptionView()
                options.append(optView)
                stack.addArrangedSubview(optView)
            }
        } else if poll.options.count < options.count {
            for _ in 0..<(options.count - poll.options.count) {
                options.removeLast().removeFromSuperview()
            }
        }
        
        for (index, (view, opt)) in zip(options, poll.options).enumerated() {
            view.updateUI(poll: poll, option: opt, ownVoted: poll.ownVotes?.contains(index) ?? false, mastodonController: mastodonController)
            view.checkboxIfInitialized?.readOnly = !isEnabled
        }
    }
    
    func estimateHeight(effectiveWidth: CGFloat) -> CGFloat {
        var height: CGFloat = 0
        height += CGFloat(options.count - 1) * stack.spacing
        for option in options {
            // this isn't the actual width, but it's close enough for the estimate
            height += option.label.sizeThatFits(CGSize(width: effectiveWidth, height: UIView.layoutFittingCompressedSize.height)).height
        }
        return height
    }
    
    private func selectOption(_ option: PollOptionView) {
        if poll.multiple {
            option.checkboxIfInitialized?.isChecked.toggle()
        } else {
            for opt in options {
                if opt === option {
                    opt.checkboxIfInitialized?.isChecked = true
                } else {
                    opt.checkboxIfInitialized?.isChecked = false
                }
            }
        }
        
        checkedOptionsChanged?()
    }
    
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // don't let subviews receive touch events
        if isEnabled {
            return self
        } else {
            return nil
        }
    }
    
    // MARK: - UIControl
    
    override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
        guard isEnabled else {
            return false
        }
        
        for (index, view) in options.enumerated() {
            if view.point(inside: touch.location(in: view), with: event) {
                currentSelectedOptionIndex = index
                
                animator = UIViewPropertyAnimator(duration: animationDuration, curve: .easeInOut) {
                    view.transform = self.scaledTransform
                }
                animator.startAnimation()
                
                generator.selectionChanged()
                generator.prepare()
                
                return true
            }
        }
        
        return false
    }
    
    override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
        let location = touch.location(in: self)
        var newIndex: Int? = nil
        for (index, view) in options.enumerated() {
            var frame = view.frame
            if index != options.count - 1 {
                frame = frame.inset(by: UIEdgeInsets(top: 0, left: 0, bottom: -stack.spacing, right: 0))
            }
            if frame.contains(location) {
                newIndex = index
                break
            }
        }
        
        if newIndex != currentSelectedOptionIndex {
            currentSelectedOptionIndex = newIndex
            
            UIView.animate(withDuration: animationDuration, delay: 0, options: .curveEaseInOut) {
                for (index, view) in self.options.enumerated() {
                    view.transform = index == newIndex ? self.scaledTransform : .identity
                }
            }
            
            if newIndex != nil {
                generator.selectionChanged()
                generator.prepare()
            }
        }
        
        return true
    }
    
    override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
        super.endTracking(touch, with: event)
        
        func selectOption() {
            guard let index = currentSelectedOptionIndex else { return }
            let option = options[index]
            animator = UIViewPropertyAnimator(duration: animationDuration, curve: .easeInOut) {
                option.transform = .identity
                self.selectOption(option)
            }
            animator.startAnimation()
        }
        
        if animator.isRunning {
            animator.addCompletion { (_) in
                selectOption()
            }
        } else {
            selectOption()
        }
    }
    
    override func cancelTracking(with event: UIEvent?) {
        super.cancelTracking(with: event)
        UIView.animate(withDuration: animationDuration, delay: 0, options: .curveEaseInOut) {
            for view in self.options {
                view.transform = .identity
            }
        }

    }

}
