//
//  FlagPhoneNumberTextField.swift
//  FlagPhoneNumber
//
//  Created by Aurélien Grifasi on 06/08/2017.
//  Modified by Radu Ursache on 21/05/2020
//  Copyright (c) 2017 Aurélien Grifasi. All rights reserved.
//

import UIKit

open class FPNTextField: UITextField {

    /// The size of the flag button
    @objc open var flagButtonSize: CGSize = CGSize(width: 42, height: 32) {
        didSet {
            layoutIfNeeded()
        }
    }

    open var clearPhoneNrWhenChangingCountry: Bool = false
    
    private var flagWidthConstraint: NSLayoutConstraint?
    private var flagHeightConstraint: NSLayoutConstraint?

    /// The size of the leftView
    private var leftViewSize: CGSize {
        let width = flagButtonSize.width + getWidth(text: phoneCodeLabel.text!)
        let height = bounds.height

        return CGSize(width: width, height: height)
    }

    private var phoneCodeLabel: UILabel = UILabel()

    private lazy var phoneUtil: NBPhoneNumberUtil = NBPhoneNumberUtil()
    private var nbPhoneNumber: NBPhoneNumber?
    private var formatter: NBAsYouTypeFormatter?

    open var flagButton: UIButton = UIButton()

    open override var font: UIFont? {
        didSet {
            phoneCodeLabel.font = font
        }
    }

    open override var textColor: UIColor? {
        didSet {
            phoneCodeLabel.textColor = textColor
        }
    }

    /// Present in the placeholder an example of a phone number according to the selected country code.
    /// If false, you can set your own placeholder. Set to true by default.
    @objc open var hasPhoneNumberExample: Bool = true {
        didSet {
            if hasPhoneNumberExample == false {
                placeholder = nil
            }
            updatePlaceholder()
        }
    }

    open var countryRepository = FPNCountryRepository()

    open var selectedCountry: FPNCountry? {
        didSet {
            updateUI()
        }
    }

    /// Input Accessory View for the texfield
    @objc open var textFieldInputAccessoryView: UIView?

    open lazy var pickerView: FPNCountryPicker = FPNCountryPicker()

    @objc public enum FPNDisplayMode: Int {
        case picker
        case list
    }

    @objc open var displayMode: FPNDisplayMode = .picker

    init() {
        super.init(frame: .zero)

        setup()
    }

    public override init(frame: CGRect) {
        super.init(frame: frame)

        setup()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        setup()
    }

    private func setup() {
        leftViewMode = .always

        setupFlagButton()
        setupphoneCodeLabel()
        setupLeftView()

        keyboardType = .numberPad
        autocorrectionType = .no
        addTarget(self, action: #selector(didEditText), for: .editingChanged)
        addTarget(self, action: #selector(displayNumberKeyBoard), for: .touchDown)

        if let regionCode = Locale.current.regionCode, let countryCode = FPNCountryCode(rawValue: regionCode) {
            setFlag(countryCode: countryCode)
        } else {
            setFlag(countryCode: FPNCountryCode.FR)
        }
    }

    private func setupFlagButton() {
        flagButton.imageView?.contentMode = .scaleAspectFit
        flagButton.accessibilityLabel = "flagButton"
        flagButton.addTarget(self, action: #selector(displayCountries), for: .touchUpInside)
        flagButton.translatesAutoresizingMaskIntoConstraints = false
        flagButton.imageEdgeInsets = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 5)
    }

    private func setupphoneCodeLabel() {
        phoneCodeLabel.font = font
        phoneCodeLabel.isUserInteractionEnabled = true
        phoneCodeLabel.translatesAutoresizingMaskIntoConstraints = false
        
        let tap = UITapGestureRecognizer(target: self, action: #selector(displayCountries))
        phoneCodeLabel.addGestureRecognizer(tap)
    }

    private func setupLeftView() {
        leftView = UIView()
        leftViewMode = .always
        if #available(iOS 9.0, *) {
            phoneCodeLabel.semanticContentAttribute = .forceLeftToRight
        } else {
            // Fallback on earlier versions
        }

        leftView?.addSubview(flagButton)
        leftView?.addSubview(phoneCodeLabel)

        flagWidthConstraint = NSLayoutConstraint(item: flagButton, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0, constant: flagButtonSize.width)
        flagHeightConstraint = NSLayoutConstraint(item: flagButton, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0, constant: flagButtonSize.height)

        flagWidthConstraint?.isActive = true
        flagHeightConstraint?.isActive = true

        NSLayoutConstraint(item: flagButton, attribute: .centerY, relatedBy: .equal, toItem: leftView, attribute: .centerY, multiplier: 1, constant: 0).isActive = true

        NSLayoutConstraint(item: flagButton, attribute: .leading, relatedBy: .equal, toItem: leftView, attribute: .leading, multiplier: 1, constant: 0).isActive = true
        NSLayoutConstraint(item: phoneCodeLabel, attribute: .leading, relatedBy: .equal, toItem: flagButton, attribute: .trailing, multiplier: 1, constant: 0).isActive = true
        NSLayoutConstraint(item: phoneCodeLabel, attribute: .trailing, relatedBy: .equal, toItem: leftView, attribute: .trailing, multiplier: 1, constant: 0).isActive = true
        NSLayoutConstraint(item: phoneCodeLabel, attribute: .top, relatedBy: .equal, toItem: leftView, attribute: .top, multiplier: 1, constant: -1.5).isActive = true
        NSLayoutConstraint(item: phoneCodeLabel, attribute: .bottom, relatedBy: .equal, toItem: leftView, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
    }

    open override func updateConstraints() {
        super.updateConstraints()

        flagWidthConstraint?.constant = flagButtonSize.width
        flagHeightConstraint?.constant = flagButtonSize.height
    }

    open override func leftViewRect(forBounds bounds: CGRect) -> CGRect {
        let size = leftViewSize
        let width: CGFloat = min(bounds.size.width, size.width)
        let height: CGFloat = min(bounds.size.height, size.height)
        let newRect: CGRect = CGRect(x: bounds.minX, y: bounds.minY, width: width, height: height)

        return newRect
    }

    @objc private func displayNumberKeyBoard() {
        switch displayMode {
        case .picker:
            tintColor = .gray
            inputView = nil
            inputAccessoryView = textFieldInputAccessoryView
            reloadInputViews()
        default:
            break
        }
    }

    @objc private func displayCountries() {
        switch displayMode {
        case .picker:
            pickerView.setup(repository: countryRepository)

            tintColor = .clear
            inputView = pickerView
            inputAccessoryView = getToolBar(with: getCountryListBarButtonItems())
            reloadInputViews()
            if clearPhoneNrWhenChangingCountry {
                text = ""
            }
            becomeFirstResponder()

            pickerView.didSelect = { [weak self] country in
                self?.fpnDidSelect(country: country)
            }

            if let selectedCountry = selectedCountry {
                pickerView.setCountry(selectedCountry.code)
            } else if let regionCode = Locale.current.regionCode, let countryCode = FPNCountryCode(rawValue: regionCode) {
                pickerView.setCountry(countryCode)
            } else if let firstCountry = countryRepository.countries.first {
                pickerView.setCountry(firstCountry.code)
            }
        case .list:
            (delegate as? FPNTextFieldDelegate)?.fpnDisplayCountryList?()
        }
    }

    @objc private func dismissCountries() {
        resignFirstResponder()
        inputView = nil
        inputAccessoryView = nil
        reloadInputViews()
    }

    private func fpnDidSelect(country: FPNCountry) {
        (delegate as? FPNTextFieldDelegate)?.fpnDidSelectCountry(name: country.name, dialCode: country.phoneCode, code: country.code.rawValue)
        selectedCountry = country
    }

    // - Public

    /// Get the current formatted phone number
    open func getFormattedPhoneNumber(format: FPNFormat) -> String? {
        return try? phoneUtil.format(nbPhoneNumber, numberFormat: convert(format: format))
    }

    /// For Objective-C, Get the current formatted phone number
    @objc open func getFormattedPhoneNumber(format: Int) -> String? {
        if let formatCase = FPNFormat(rawValue: format) {
            return try? phoneUtil.format(nbPhoneNumber, numberFormat: convert(format: formatCase))
        }
        return nil
    }

    /// Get the current raw phone number
    @objc open func getRawPhoneNumber() -> String? {
        let phoneNumber = getFormattedPhoneNumber(format: .E164)
        var nationalNumber: NSString?

        phoneUtil.extractCountryCode(phoneNumber, nationalNumber: &nationalNumber)

        return nationalNumber as String?
    }

    /// Set directly the phone number. e.g "+33612345678"
    @objc open func set(phoneNumber: String) {
        let cleanedPhoneNumber: String = clean(string: phoneNumber)

        if let validPhoneNumber = getValidNumber(phoneNumber: cleanedPhoneNumber) {
            if validPhoneNumber.italianLeadingZero {
                text = "0\(validPhoneNumber.nationalNumber.stringValue)"
            } else {
                text = validPhoneNumber.nationalNumber.stringValue
            }
            setFlag(countryCode: FPNCountryCode(rawValue: phoneUtil.getRegionCode(for: validPhoneNumber))!)
        }
    }

    /// Set the country image according to country code. Example "FR"
    open func setFlag(countryCode: FPNCountryCode) {
        let countries = countryRepository.countries

        for country in countries {
            if country.code == countryCode {
                self.fpnDidSelect(country: country)
                return
            }
        }
    }

    /// Set the country image according to country code. Example "FR"
    @objc open func setFlag(key: FPNOBJCCountryKey) {
        if let code = FPNOBJCCountryCode[key], let countryCode = FPNCountryCode(rawValue: code) {

            setFlag(countryCode: countryCode)
        }
    }

    /// Set the country list excluding the provided countries
    open func setCountries(excluding countries: [FPNCountryCode]) {
        countryRepository.setup(without: countries)

        if let selectedCountry = selectedCountry, countryRepository.countries.contains(selectedCountry) {
            fpnDidSelect(country: selectedCountry)
        } else if let country = countryRepository.countries.first {
            fpnDidSelect(country: country)
        }
    }

    /// Set the country list including the provided countries
    open func setCountries(including countries: [FPNCountryCode]) {
        countryRepository.setup(with: countries)

        if let selectedCountry = selectedCountry, countryRepository.countries.contains(selectedCountry) {
            fpnDidSelect(country: selectedCountry)
        } else if let country = countryRepository.countries.first {
            fpnDidSelect(country: country)
        }
    }

    /// Set the country list excluding the provided countries
    @objc open func setCountries(excluding countries: [Int]) {
        let countryCodes: [FPNCountryCode] = countries.compactMap({ index in
            if let key = FPNOBJCCountryKey(rawValue: index), let code = FPNOBJCCountryCode[key], let countryCode = FPNCountryCode(rawValue: code) {
                return countryCode
            }
            return nil
        })

        countryRepository.setup(without: countryCodes)
    }

    /// Set the country list including the provided countries
    @objc open func setCountries(including countries: [Int]) {
        let countryCodes: [FPNCountryCode] = countries.compactMap({ index in
            if let key = FPNOBJCCountryKey(rawValue: index), let code = FPNOBJCCountryCode[key], let countryCode = FPNCountryCode(rawValue: code) {
                return countryCode
            }
            return nil
        })

        countryRepository.setup(with: countryCodes)
    }

    // Private

    @objc private func didEditText() {
        if let phoneCode = selectedCountry?.phoneCode, let number = text {
            var cleanedPhoneNumber = clean(string: "\(phoneCode) \(number)")

            if let validPhoneNumber = getValidNumber(phoneNumber: cleanedPhoneNumber) {
                nbPhoneNumber = validPhoneNumber

                cleanedPhoneNumber = "+\(validPhoneNumber.countryCode.stringValue)\(validPhoneNumber.nationalNumber.stringValue)"

                if let inputString = formatter?.inputString(cleanedPhoneNumber) {
                    text = remove(dialCode: phoneCode, in: inputString)
                }
                (delegate as? FPNTextFieldDelegate)?.fpnDidValidatePhoneNumber(textField: self, isValid: true)
            } else {
                nbPhoneNumber = nil

                if let dialCode = selectedCountry?.phoneCode {
                    if let inputString = formatter?.inputString(cleanedPhoneNumber) {
                        text = remove(dialCode: dialCode, in: inputString)
                    }
                }
                (delegate as? FPNTextFieldDelegate)?.fpnDidValidatePhoneNumber(textField: self, isValid: false)
            }
        }
    }

    private func convert(format: FPNFormat) -> NBEPhoneNumberFormat {
        switch format {
        case .E164:
            return NBEPhoneNumberFormat.E164
        case .International:
            return NBEPhoneNumberFormat.INTERNATIONAL
        case .National:
            return NBEPhoneNumberFormat.NATIONAL
        case .RFC3966:
            return NBEPhoneNumberFormat.RFC3966
        }
    }

    private func updateUI() {
        if let countryCode = selectedCountry?.code {
            formatter = NBAsYouTypeFormatter(regionCode: countryCode.rawValue)
        }

        flagButton.setImage(selectedCountry?.flag, for: .normal)

        if let phoneCode = selectedCountry?.phoneCode {
            phoneCodeLabel.text = phoneCode
        }

        if hasPhoneNumberExample == true {
            updatePlaceholder()
        }
        didEditText()
    }

    private func clean(string: String) -> String {
        var allowedCharactersSet = CharacterSet.decimalDigits

        allowedCharactersSet.insert("+")

        return string.components(separatedBy: allowedCharactersSet.inverted).joined(separator: "")
    }

    private func getWidth(text: String) -> CGFloat {
        if let font = phoneCodeLabel.font {
            let fontAttributes = [NSAttributedString.Key.font: font]
            let size = (text as NSString).size(withAttributes: fontAttributes)

            return size.width.rounded(.up)
        } else {
            phoneCodeLabel.sizeToFit()

            return phoneCodeLabel.frame.size.width.rounded(.up)
        }
    }

    private func getValidNumber(phoneNumber: String) -> NBPhoneNumber? {
        guard let countryCode = selectedCountry?.code else { return nil }

        do {
            let parsedPhoneNumber: NBPhoneNumber = try phoneUtil.parse(phoneNumber, defaultRegion: countryCode.rawValue)
            let isValid = phoneUtil.isValidNumber(parsedPhoneNumber)

            return isValid ? parsedPhoneNumber : nil
        } catch _ {
            return nil
        }
    }

    private func remove(dialCode: String, in phoneNumber: String) -> String {
        return phoneNumber.replacingOccurrences(of: "\(dialCode) ", with: "").replacingOccurrences(of: "\(dialCode)", with: "")
    }

    private func getToolBar(with items: [UIBarButtonItem]) -> UIToolbar {
        let toolbar: UIToolbar = UIToolbar()

        toolbar.barStyle = UIBarStyle.default
        toolbar.items = items
        toolbar.sizeToFit()

        return toolbar
    }

    private func getCountryListBarButtonItems() -> [UIBarButtonItem] {
        let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
        let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissCountries))

        doneButton.accessibilityLabel = "doneButton"

        return [space, doneButton]
    }

    private func updatePlaceholder() {
        if let countryCode = selectedCountry?.code {
            do {
                let example = try phoneUtil.getExampleNumber(countryCode.rawValue)
                let phoneNumber = "+\(example.countryCode.stringValue)\(example.nationalNumber.stringValue)"

                if let inputString = formatter?.inputString(phoneNumber) {
                    placeholder = remove(dialCode: "+\(example.countryCode.stringValue)", in: inputString)
                } else {
                    placeholder = nil
                }
            } catch _ {
                placeholder = nil
            }
        } else {
            placeholder = nil
        }
    }
}
