//
//  DigitEntryField.swift
//  MVMCoreUI
//
//  Created by Kevin Christiano on 10/21/19.
//  Copyright © 2019 Verizon Wireless. All rights reserved.
//

import UIKit

/**
 * Subclass of TextEntryField as it is to use similar logic as a singular textField but appear separate..
 */
@objcMembers open class DigitEntryField: TextEntryField, DigitBoxProtocol {
    //--------------------------------------------------
    // MARK: - Stored Properties
    //--------------------------------------------------
    
    private(set) var numberOfDigits = 4 {
        didSet {
            guard entryFieldContainer.subviews.isEmpty || oldValue != numberOfDigits else { return }
            
            var accessibleElements: [Any] = [titleLabel]
            
            if numberOfDigits > 0 {
                var digitBoxes = [DigitBox]()
                
                for i in 0..<numberOfDigits {
                    let newDigitBox = createDigitField()
                    let accessibileLabel = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: i + 1)) ?? ""
                    newDigitBox.digitField.accessibilityLabel = "\(accessibileLabel) field of \(numberOfDigits) digit fields"
                    digitBoxes.append(newDigitBox)
                }
                
                self.digitBoxes.forEach { $0.removeFromSuperview() }
                self.digitBoxes = digitBoxes
                guard let space = MFSizeObject(standardSize: 5, smalliPhoneSize: 3)?.getValueBasedOnScreenSize() else { return }
                
                var prevBox: DigitBox?
                
                for (index, box) in digitBoxes.enumerated() {
                    accessibleElements.append(box.digitField)
                    entryFieldContainer.addSubview(box)
                    
                    box.topAnchor.constraint(equalTo: entryFieldContainer.topAnchor).isActive = true
                    entryFieldContainer.bottomAnchor.constraint(equalTo: box.bottomAnchor).isActive = true
                    
                    if index == 0 {
                        box.leadingAnchor.constraint(equalTo: entryFieldContainer.leadingAnchor).isActive = true
                        
                    } else if index == digitBoxes.count - 1 {
                        if let prevTrailingAnchor = prevBox?.trailingAnchor {
                            box.leadingAnchor.constraint(equalTo: prevTrailingAnchor, constant: space).isActive = true
                        }
                        entryFieldContainer.trailingAnchor.constraint(equalTo: box.trailingAnchor).isActive = true
                        
                    } else {
                        if let prevTrailingAnchor = prevBox?.trailingAnchor {
                            box.leadingAnchor.constraint(equalTo: prevTrailingAnchor, constant: space).isActive = true
                        }
                    }
                    
                    prevBox = box
                }
            }
            
            accessibilityElements = accessibleElements + [feedbackLabel]
        }
    }
    
    /// Monitors if fields are being selected internally.
    private var switchFieldsAutomatically = false
    
    public var digitBoxes: [DigitBox] = []
    private var selectedDigitBox: DigitBox?
    
    public var digitEntryModel: DigitEntryFieldModel? {
        model as? DigitEntryFieldModel
    }
    
    //--------------------------------------------------
    // MARK: - Computed Properties
    //--------------------------------------------------
    
    public override var isEnabled: Bool {
        get { super.isEnabled }
        set (enabled) {
            digitBoxes.forEach { $0.isEnabled = enabled }
            super.isEnabled = enabled
        }
    }
    
    public override var showError: Bool {
        get { super.showError }
        set (error) {
            digitBoxes.forEach { $0.showError = error }
            super.showError = error
        }
    }
    
    public override var isLocked: Bool {
        get { super.isLocked }
        set (locked) {
            digitBoxes.forEach { $0.isLocked = locked }
            super.isLocked = locked
        }
    }
    
    public override var placeholder: String? {
        get {
            var string = ""
            digitBoxes.forEach { string += $0.digitField.attributedPlaceholder?.string ?? "" }
            return !string.isEmpty ? string : nil
        }
        set {
            guard let newValue = newValue else { return }
            
            for (index, field) in digitBoxes.enumerated() {
                if index < newValue.count {
                    let indexChar = newValue.index(newValue.startIndex, offsetBy: index)
                    field.digitField.attributedPlaceholder = NSAttributedString(string: String(newValue[indexChar]), attributes: [NSAttributedString.Key.foregroundColor: UIColor.mvmCoolGray6])
                }
            }
            
            // If there is already text in the textfield, set the placeholder label below.
            if let text = text, !text.isEmpty && !showError {
                feedback = placeholder
                
            } else if !showError {
                feedback = ""
            }
            
            textField.accessibilityLabel = newValue + (MVMCoreUIUtility.hardcodedString(withKey: "mfdigittextfield_regular") ?? "")
        }
    }
    
    /// Traverses each digitbox to retrieve the held value.
    public override var text: String? {
        get {
            var string = ""
            digitBoxes.forEach { string += $0.digitField.text ?? "" }
            return string
        }
        set {
            guard let newValue = newValue else { return }
            
            for (index, field) in digitBoxes.enumerated() {
                if index < newValue.count {
                    let indexChar = newValue.index(newValue.startIndex, offsetBy: index)
                    field.digitField.text = String(newValue[indexChar])
                }
            }
        }
    }
    
    //--------------------------------------------------
    // MARK: - Delegate
    //--------------------------------------------------
    
    /// Holds a reference to the 'delegating' class so this class can internally influence the TextField behavior as well.
    private weak var proprietorTextDelegate: UITextFieldDelegate?
    
    /// If you're using a MFViewController, you must set this to it
    public override weak var uiTextFieldDelegate: UITextFieldDelegate? {
        get { textField.delegate }
        set {
            textField.delegate = self
            proprietorTextDelegate = newValue
        }
    }
    
    //--------------------------------------------------
    // MARK: - Initializers
    //--------------------------------------------------
    
    @objc public override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    @objc public convenience init() {
        self.init(frame: .zero)
    }
    
    @objc public convenience init(numberOfDigits: Int, secureDigits: Bool = false) {
        self.init(frame: .zero)
        self.numberOfDigits = numberOfDigits
        
        if secureDigits {
            setAsSecureTextEntry(true)
        }
    }
    
    @objc required public init?(coder: NSCoder) {
        super.init(coder: coder)
        fatalError("DigitEntryField xib has not been implemented")
    }
    
    required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
        super.init(model: model, delegateObject, additionalData)
    }
    
    //--------------------------------------------------
    // MARK: - Setup
    //--------------------------------------------------
    
    @objc public override func setupFieldContainerContent(_ container: UIView) {
        
        isAccessibilityElement = false
        entryFieldContainer.disableAllBorders = true
    }
    
    @objc private func createDigitField() -> DigitBox {
        
        let digitBox = DigitBox()
        digitBox.isAccessibilityElement = true
        let observingDelegate = delegateObject?.observingTextFieldDelegate ?? self
        digitBox.digitField.inputAccessoryView = UIToolbar.getToolbarWithDoneButton(delegate: observingDelegate,
                                                                                    action: #selector(observingDelegate.dismissFieldInput))
        digitBox.digitField.delegate = self
        digitBox.digitBoxDelegate = self
        return digitBox
    }
    
    //--------------------------------------------------
    // MARK: - Lifecycle
    //--------------------------------------------------
    
    @objc open override func updateView(_ size: CGFloat) {
        
        entryFieldContainer.disableAllBorders = true
        
        if !self.digitBoxes.isEmpty {
            self.digitBoxes.forEach { $0.updateView(size) }
        }
        
        super.updateView(size)
    }
    
    public override func reset() {
        super.reset()
        
        accessibilityElements = nil
        switchFieldsAutomatically = false
        selectedDigitBox = nil
        text = ""
        digitBoxes.forEach { $0.digitField.text = "" }
    }
    
    //--------------------------------------------------
    // MARK: - Methods
    //--------------------------------------------------
    
    @objc public func setAsSecureTextEntry(_ secureEntry: Bool) {
        
        for (index, field) in self.digitBoxes.enumerated() {
            field.digitField.isSecureTextEntry = secureEntry
            
            // Accessibility - 33704 fix voice over will read what pin user is filling
            field.accessibilityLabel = String(format: "PIN %lu of %lu", UInt(index) + 1, UInt(self.digitBoxes.count))
        }
    }
    
    @objc public func selectPreviousDigitField(_ currentTextField: UITextField?, clear: Bool) {
        
        var selectPreviousField = false
        
        for (index, box) in Array(digitBoxes.reversed()).enumerated() {
            if box.digitField === currentTextField {
                if index == digitBoxes.count - 1 {
                    return
                } else {
                    selectPreviousField = true
                }
            } else if selectPreviousField {
                if !clear {
                    switchFieldsAutomatically = true
                }
                box.digitField.becomeFirstResponder()
                switchFieldsAutomatically = false
                UIAccessibility.post(notification: .layoutChanged, argument: box.digitField)
                return
            }
        }
    }
    
    @objc public func selectNextDigitField(_ currentTextField: UITextField?, clear: Bool) {
        
        var selectNextField = false
        
        for (index, box) in digitBoxes.enumerated() {
            if box.digitField === currentTextField {
                if index == digitBoxes.count - 1 {
                    return
                } else {
                    selectNextField = true
                }
            } else if selectNextField {
                if !clear {
                    switchFieldsAutomatically = true
                }
                box.digitField.becomeFirstResponder()
                switchFieldsAutomatically = false
                UIAccessibility.post(notification: .layoutChanged, argument: box.digitField)
                return
            }
        }
    }
    
    //--------------------------------------------------
    // MARK: - Observing TextField Changes
    //--------------------------------------------------
    
    @objc override func startEditing() {
        
        selectedDigitBox?.isSelected = true
        selectedDigitBox?.digitField.becomeFirstResponder()
    }
    
    @objc override open func resignFirstResponder() -> Bool {
        
        if validateWhenDoneEditing {
            validateText()
        }
        
        selectedDigitBox?.isSelected = false
        selectedDigitBox?.digitField.resignFirstResponder()
        return true
    }
    
    @objc public override func dismissFieldInput(_ sender: Any?) {
        
        digitBoxes.forEach {
            if $0.isSelected {
                $0.digitField.resignFirstResponder()
                $0.isSelected = false
            }
        }
    }
    
    public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
        
        guard let model = model as? DigitEntryFieldModel else { return }
        
        numberOfDigits = model.digits
        
        if let entryType = model.type  {
            setAsSecureTextEntry(entryType == .secure || entryType == .password)
        }
        
        let observingDelegate = delegateObject?.observingTextFieldDelegate ?? self
        
        digitBoxes.forEach {
            $0.digitField.inputAccessoryView = UIToolbar.getToolbarWithDoneButton(delegate: observingDelegate,
                                                                                  action: #selector(observingDelegate.dismissFieldInput))
        }
        
        super.set(with: model, delegateObject, additionalData)
    }
    
    public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { 115 }
}

// MARK: - TextField Delegate
extension DigitEntryField {
    
    @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        
        textField.resignFirstResponder()
        
        return proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? true
    }
    
    @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        
        if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) {
            return false
        }
        
        if (textField.text?.count ?? 0) >= range.length + range.location {
            
            let oldLength = textField.text?.count ?? 0
            let replacementLength = string.count
            if replacementLength > 1 {
                // Too long (Check with AKQA if they want to allow pasting the digits.
                return false
                
            } else if replacementLength == 1 && (oldLength == 1 || oldLength == 0) {
                
                // One character, switch old value with new, select next textfield
                textField.text = string
                digitEntryModel?.text = text
                selectNextDigitField(textField, clear: false)
                return false
                
            } else if replacementLength == 0 && oldLength == 1 {
                // Non empty cell, clear and stay.
                textField.text = ""
                digitEntryModel?.text = text
                return false
            }
            
            return true
        }
        
        return false
    }
    
    @objc func digitFieldDidDelete(_ textField: TextField?) {
        
        selectPreviousDigitField(textField, clear: false)
    }
    
    @objc public func textFieldDidBeginEditing(_ textField: UITextField) {
        
        digitBoxes.forEach {
            if $0.digitField === textField {
                selectedDigitBox = $0
                $0.isSelected = true
                return
            }
        }
        
        if !switchFieldsAutomatically {
            textField.text = ""
            digitEntryModel?.text = text
        }
        
        proprietorTextDelegate?.textFieldDidBeginEditing?(textField)
    }
    
    @objc public func textFieldDidEndEditing(_ textField: UITextField) {
        
        // There should only be one digitBox to deselect.
        selectedDigitBox?.isSelected = false
        selectedDigitBox = nil
        
        if !switchFieldsAutomatically && validateWhenDoneEditing {
            validateText()
        }
        
        proprietorTextDelegate?.textFieldDidEndEditing?(textField)
    }
    
    @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool {
        
        selectPreviousDigitField(textField, clear: true)
        
        return proprietorTextDelegate?.textFieldShouldClear?(textField) ?? true
    }
    
    @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
        
        proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true
    }
    
    @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
        
        proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true
    }
}
