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

import UIKit


@objc public protocol ObservingTextFieldDelegate {
    /// Called when the entered text becomes valid based on the validation block
    @objc optional func isValid(textfield: TextEntryField?)
    /// Called when the entered text becomes invalid based on the validation block
    @objc optional func isInvalid(textfield: TextEntryField?)
    /// Dismisses the keyboard.
    @objc optional func dismissFieldInput(_ sender: Any?)
}


@objcMembers open class TextEntryField: EntryField, UITextFieldDelegate, ObservingTextFieldDelegate {
    //--------------------------------------------------
    // MARK: - Outlets
    //--------------------------------------------------
    
    open private(set) var textField: TextField = {
        let textField = TextField()
        textField.isAccessibilityElement = true
        textField.setContentCompressionResistancePriority(.required, for: .vertical)
        textField.font = Styler.Font.RegularBodyLarge.getFont()
        textField.textColor = .mvmBlack
        textField.smartQuotesType = .no
        textField.smartDashesType = .no
        textField.smartInsertDeleteType = .no
        return textField
    }()
    
    public lazy var errorImage: UIImageView = {
        let image = MVMCoreUIUtility.imageNamed("alert_standard")
        let imageView = UIImageView(image: image)
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.heightAnchor.constraint(equalToConstant: 20).isActive = true
        imageView.widthAnchor.constraint(equalToConstant: 20).isActive = true
        return imageView
    }()
    
    //--------------------------------------------------
    // MARK: - Stored Properties
    //--------------------------------------------------
    
    private var observingForChange: Bool = false
    
    /// Validate when user resigns editing. Default: true
    public var validateWhenDoneEditing: Bool = true
    
    public var textEntryFieldModel: TextEntryFieldModel? { model as? TextEntryFieldModel }
    
    //--------------------------------------------------
    // MARK: - Computed Properties
    //--------------------------------------------------
    
    public override var isEnabled: Bool {
        get { super.isEnabled }
        set (enabled) {
            super.isEnabled = enabled
            
            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                
                self.textField.isEnabled = enabled
                self.textField.textColor = enabled ? self.textEntryFieldModel?.enabledTextColor.uiColor : self.textEntryFieldModel?.disabledTextColor.uiColor
            }
        }
    }
    
    public override var showError: Bool {
        get { super.showError }
        set (error) {
            
            if error {
                textField.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message") ?? "", textField.text ?? "", entryFieldModel?.errorMessage ?? "")
            } else {
                textField.accessibilityValue = nil
            }
            
            if textField.isSecureTextEntry {
                showErrorView(error)
            }
            
            super.showError = error
        }
    }
    
    /// The text of this TextField.
    open override var text: String? {
        get { textField.text }
        set {
            textEntryFieldModel?.text = newValue
            textField.text = newValue
        }
    }
    
    /// Placeholder access for the TextField.
    public var placeholder: String? {
        get { textField.placeholder }
        set { textField.placeholder = newValue }
    }
    
    //--------------------------------------------------
    // MARK: - Delegate Properties
    //--------------------------------------------------
    
    /// The delegate and block for validation. Validates if the text that the user has entered.
    public weak var observingTextFieldDelegate: ObservingTextFieldDelegate? {
        didSet {
            if observingTextFieldDelegate != nil && !observingForChange {
                observingForChange = true
                NotificationCenter.default.addObserver(self, selector: #selector(valueChanged), name: UITextField.textDidChangeNotification, object: textField)
                NotificationCenter.default.addObserver(self, selector: #selector(endInputing), name: UITextField.textDidEndEditingNotification, object: textField)
                NotificationCenter.default.addObserver(self, selector: #selector(startEditing), name: UITextField.textDidBeginEditingNotification, object: textField)
                
            } else if observingTextFieldDelegate == nil && observingForChange {
                observingForChange = false
                NotificationCenter.default.removeObserver(self, name: UITextField.textDidChangeNotification, object: textField)
                NotificationCenter.default.removeObserver(self, name: UITextField.textDidEndEditingNotification, object: textField)
                NotificationCenter.default.removeObserver(self, name: UITextField.textDidBeginEditingNotification, object: textField)
            }
        }
    }
    
    /// If you're using a ViewController, you must set this to it
    open weak var uiTextFieldDelegate: UITextFieldDelegate? {
        get { textField.delegate }
        set { textField.delegate = newValue }
    }
    
    //--------------------------------------------------
    // MARK: - Constraints
    //--------------------------------------------------
    
    public var textFieldTrailingConstraint: NSLayoutConstraint?
    
    //--------------------------------------------------
    // MARK: - Initializers
    //--------------------------------------------------
    
    @objc public override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    @objc public convenience init() {
        self.init(frame: .zero)
    }
    
    @objc required public init?(coder: NSCoder) {
        super.init(coder: coder)
        fatalError("TextEntryField does not support xib.")
    }
    
    required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
        super.init(model: model, delegateObject, additionalData)
    }
    
    //--------------------------------------------------
    // MARK: - Lifecycle
    //--------------------------------------------------
    
    @objc open override func setupFieldContainerContent(_ container: UIView) {
        
        textField.font = Styler.Font.RegularBodyLarge.getFont()
        container.addSubview(textField)
        
        NSLayoutConstraint.activate([
            textField.heightAnchor.constraint(equalToConstant: Padding.Five),
            textField.topAnchor.constraint(equalTo: container.topAnchor, constant: Padding.Three),
            textField.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: Padding.Three),
            container.bottomAnchor.constraint(equalTo: textField.bottomAnchor, constant: Padding.Three)
        ])
        
        textFieldTrailingConstraint = container.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: Padding.Three)
        textFieldTrailingConstraint?.isActive = true
        
        textField.addTarget(self, action: #selector(startEditing), for: .editingDidBegin)
        textField.addTarget(self, action: #selector(dismissFieldInput), for: .editingDidEnd)
        
        let tap = UITapGestureRecognizer(target: self, action: #selector(startEditing))
        entryFieldContainer.addGestureRecognizer(tap)
        
        accessibilityElements = [titleLabel, textField, feedbackLabel]
    }
    
    @objc open override func updateView(_ size: CGFloat) {
        super.updateView(size)
        
        textField.font = Styler.Font.RegularBodyLarge.getFont()
        layoutIfNeeded()
    }
    
    open override func reset() {
        super.reset()
        
        textField.isSecureTextEntry = false
        textField.font = Styler.Font.RegularBodyLarge.getFont()
    }
    
    @objc public func setBothTextDelegates(to delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?) {
        observingTextFieldDelegate = delegate
        uiTextFieldDelegate = delegate
    }
    
    open func setupTextFieldToolbar() {
        let observingDelegate = observingTextFieldDelegate ?? self
        textField.inputAccessoryView = UIToolbar.getToolbarWithDoneButton(delegate: observingDelegate,
                                                                          action: #selector(observingDelegate.dismissFieldInput))
    }
    
    //--------------------------------------------------
    // MARK: - Observing for Change (TextFieldDelegate)
    //--------------------------------------------------
    
    @discardableResult
    @objc override open func resignFirstResponder() -> Bool {
        if validateWhenDoneEditing { validateText() }
        textField.resignFirstResponder()
        isSelected = false
        return true
    }
    
    /// Validates the text of the entry field.
    @objc public override func validateText() {
        text = textField.text
        super.validateText()
    }
    
    /// Executes on UITextField.textDidBeginEditingNotification
    @objc override func startEditing() {
        super.startEditing()
        
        if textEntryFieldModel?.clearTextOnTap ?? false {
            text = ""
        }
        
        textField.becomeFirstResponder()
    }
    
    /// Executes on UITextField.textDidChangeNotification (each character entry)
    @objc override func valueChanged() {
        super.valueChanged()
        validateText()
    }
    
    /// Executes on UITextField.textDidEndEditingNotification
    @objc override func endInputing() {
        super.endInputing()
        
        // Don't show error till user starts typing.
        guard text?.count ?? 0 != 0 else {
            showError = false
            return
        }
        
        if let isValid = textEntryFieldModel?.isValid {
            self.isValid = isValid
        }
        
        regexTextFieldOutputIfAvailable()
        
        shouldShowError(!isValid)
    }
    
    func regexTextFieldOutputIfAvailable() {
        
        if let regex = textEntryFieldModel?.displayFormat,
           let mask = textEntryFieldModel?.displayMask,
           let finalText = text {
            
            let range = NSRange(finalText.startIndex..., in: finalText)
            
            if let regex = try? NSRegularExpression(pattern: regex) {
                let maskedText = regex.stringByReplacingMatches(in: finalText,
                                                                range: range,
                                                                withTemplate: mask)
                textField.text = maskedText
            }
        }
    }
    
    @objc public func dismissFieldInput(_ sender: Any?) {
        resignFirstResponder()
    }
    
    private func showErrorView(_ show: Bool) {
        
        if show {
            entryFieldContainer.addSubview(errorImage)
            
            textFieldTrailingConstraint?.isActive = false
            textFieldTrailingConstraint = errorImage.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: Padding.Two)
            textFieldTrailingConstraint?.isActive = true
            
            entryFieldContainer.trailingAnchor.constraint(equalTo: errorImage.trailingAnchor, constant: Padding.Three).isActive = true
            errorImage.centerYAnchor.constraint(equalTo: entryFieldContainer.centerYAnchor).isActive = true
            
        } else {
            errorImage.removeFromSuperview()
            textFieldTrailingConstraint?.isActive = false
            textFieldTrailingConstraint = entryFieldContainer.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: Padding.Two)
            textFieldTrailingConstraint?.isActive = true
        }
    }
    
    override func shouldShowError(_ showError: Bool) {
        super.shouldShowError(showError)
        
        if showError {
            observingTextFieldDelegate?.isValid?(textfield: self)
        } else {
            observingTextFieldDelegate?.isInvalid?(textfield: self)
        }
    }
    
    //--------------------------------------------------
    // MARK: - MoleculeViewProtocol
    //--------------------------------------------------
    
    public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
        super.set(with: model, delegateObject, additionalData)
        
        guard let model = model as? TextEntryFieldModel else { return }
        
        self.delegateObject = delegateObject
        FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate)
        text = model.text
        placeholder = model.placeholder
        
        switch model.type {
        case .password, .secure:
            textField.isSecureTextEntry = true
            
        case .numberSecure:
            textField.isSecureTextEntry = true
            textField.keyboardType = .numberPad
            
        case .number:
            textField.keyboardType = .numberPad
            
        case .email:
            textField.keyboardType = .emailAddress
            
        case .phone:
            textField.keyboardType = .phonePad
            
        default:
            textField.keyboardType = .default
        }
        
        // Override the preset keyboard set in type.
        if let keyboardType = model.assignKeyboardType() {
            textField.keyboardType = keyboardType
        }
        
        textField.accessibilityIdentifier = model.accessibilityIdentifier
        uiTextFieldDelegate = delegateObject?.uiTextFieldDelegate
        observingTextFieldDelegate = delegateObject?.observingTextFieldDelegate
        setupTextFieldToolbar()
        
        if isSelected { startEditing() }
        
        //Added to override text when view is reloaded.
        if let text = model.text, !text.isEmpty {
            regexTextFieldOutputIfAvailable()
        }
        
    }
}

// MARK: - Accessibility
extension TextEntryField {
    
    @objc open override func pushAccessibilityNotification() {
        
        DispatchQueue.main.async { [weak self] in
            guard let self = self else { return }
            
            UIAccessibility.post(notification: .layoutChanged, argument: self.textField)
        }
    }
    
    @objc open override func setAccessibilityString(_ accessibilityString: String?) {
        
        var accessibilityString = accessibilityString ?? ""
        
        if let txtRegular = MVMCoreUIUtility.hardcodedString(withKey: "textfield_regular") {
            accessibilityString += txtRegular
        }
        
        textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")"
    }
}
