import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import PhoneNumberFormat

public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
    public struct Theme: Equatable {
        public var inactiveBorder: UInt32
        public var activeBorder: UInt32
        public var succeedBorder: UInt32
        public var failedBorder: UInt32
        public var foreground: UInt32
        public var isDark: Bool
        
        public init(
            inactiveBorder: UInt32,
            activeBorder: UInt32,
            succeedBorder: UInt32,
            failedBorder: UInt32,
            foreground: UInt32,
            isDark: Bool
        ) {
            self.inactiveBorder = inactiveBorder
            self.activeBorder = activeBorder
            self.succeedBorder = succeedBorder
            self.failedBorder = failedBorder
            self.foreground = foreground
            self.isDark = isDark
        }
    }
    
    private final class ItemView: ASDisplayNode {
        private let backgroundView: UIView
        private let textNode: ImmediateTextNode
        
        private var borderColorValue: UInt32?
        
        private var text: String = ""
        
        override init() {
            self.backgroundView = UIView()
            self.textNode = ImmediateTextNode()
            
            super.init()
            
            self.addSubnode(self.textNode)
            self.view.addSubview(self.backgroundView)
            
            self.clipsToBounds = true
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        override func didLoad() {
            super.didLoad()
            
            self.textNode.layer.anchorPoint = CGPoint(x: 0.5, y: 1.0)
        }
        
        func update(borderColor: UInt32, isHighlighted: Bool) {
            if self.borderColorValue != borderColor {
                self.borderColorValue = borderColor
                
                let previousColor = self.backgroundView.layer.borderColor
                self.backgroundView.layer.borderColor = UIColor(argb: borderColor).cgColor
                self.backgroundView.layer.borderWidth = 1.0 + UIScreenPixel
                if let previousColor = previousColor {
                    self.backgroundView.layer.animate(from: previousColor, to: UIColor(argb: borderColor).cgColor, keyPath: "borderColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.15)
                }
            }
        }
        
        func update(textColor: UInt32, text: String, size: CGSize, fontSize: CGFloat, animated: Bool, delay: Double? = nil) {
            let previousText = self.text
            self.text = text
            
            if animated && previousText.isEmpty != text.isEmpty {
                if !text.isEmpty {
                    self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
                    self.textNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: 0.0, y: size.height * 0.35)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, damping: 70.0, additive: true)
                    self.textNode.layer.animateScaleY(from: 0.01, to: 1.0, duration: 0.25)
                } else {
                    if let copyView = self.textNode.view.snapshotContentTree() {
                        self.view.insertSubview(copyView, at: 0)
                        copyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: delay ?? 0.0, removeOnCompletion: false, completion: { [weak copyView] _ in
                            copyView?.removeFromSuperview()
                        })
                        copyView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: size.height / 2.0), duration: 0.2, delay: delay ?? 0.0, removeOnCompletion: false, additive: true)
                    }
                }
            }
            
            self.backgroundView.layer.cornerRadius = size.height == 28.0 ? 12.0 : 15.0
            if #available(iOS 13.0, *) {
                self.backgroundView.layer.cornerCurve = .continuous
            }
            
            if #available(iOS 13.0, *) {
                self.textNode.attributedText = NSAttributedString(string: text, font: UIFont.monospacedSystemFont(ofSize: fontSize, weight: .regular), textColor: UIColor(argb: textColor))
            } else {
                self.textNode.attributedText = NSAttributedString(string: text, font: Font.monospace(fontSize), textColor: UIColor(argb: textColor))
            }
            let textSize = self.textNode.updateLayout(size)
            self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
            
            self.backgroundView.frame = CGRect(origin: CGPoint(), size: size)
        }
    }
    
    private let prefixLabel: ImmediateTextNode
    public let textField: UITextField
    
    private var focusIndex: Int? = 0
    private var itemViews: [ItemView] = []
    
    public var updated: (() -> Void)?
    public var longPressed: (() -> Void)?
    
    private var theme: Theme?
    private var count: Int?
    private var prefix: String = ""
    private var compact = false
    
    private var textValue: String = ""
    public var text: String {
        get {
            return self.textValue
        } set(value) {
            self.textValue = value
            self.textField.text = value
        }
    }
    
    override public init() {
        self.prefixLabel = ImmediateTextNode()
        self.textField = UITextField()
        
        if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
            self.textField.keyboardType = .asciiCapableNumberPad
        } else {
            self.textField.keyboardType = .numberPad
        }
        if #available(iOSApplicationExtension 12.0, iOS 12.0, *) {
            self.textField.textContentType = .oneTimeCode
        }
        self.textField.returnKeyType = .done
        self.textField.disableAutomaticKeyboardHandling = [.forward, .backward]
        
        super.init()
        
        self.addSubnode(self.prefixLabel)
        self.view.addSubview(self.textField)
        
        self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
        self.textField.delegate = self
        self.textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
    }
    
    required public init?(coder: NSCoder) {
        preconditionFailure()
    }
    
    @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
        if case .ended = recognizer.state {
            self.textField.becomeFirstResponder()
        }
    }
    
    public override func didLoad() {
        super.didLoad()
        
        self.view.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.handleLongPress(_:))))
    }
    
    @objc func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
        if case .ended = gestureRecognizer.state {
            self.longPressed?()
        }
    }
    
    private var isSucceed = false
    private var isFailed = false
    private var isResetting = false
    public func animateError() {
        self.isFailed = true
        self.updateItemViews(animated: true)
        Queue.mainQueue().after(0.85, {
            self.textValue = ""
            self.isResetting = true
            self.updateItemViews(animated: true)
            self.isResetting = false
            self.textField.text = ""
            self.isFailed = false
            self.updateItemViews(animated: true)
        })
    }
    
    public func animateSuccess() {
        self.isSucceed = true
        self.updateItemViews(animated: true)
    }
    
    @objc func textFieldChanged(_ textField: UITextField) {
        self.textValue = textField.text ?? ""
        self.updateItemViews(animated: true)
        self.updated?()
    }
    
    public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        guard let count = self.count else {
            return false
        }
        
        guard !self.isFailed else {
            return false
        }
        
        var text = textField.text ?? ""
        guard let stringRange = Range(range, in: text) else {
            return false
        }
        text.replaceSubrange(stringRange, with: string)
        
        if !text.allSatisfy({ $0.isNumber && $0.isASCII }) {
            return false
        }
        
        if text.count > count {
            return false
        }
        
        return true
    }
    
    private func currentCaretIndex() -> Int? {
        if let selectedTextRange = self.textField.selectedTextRange {
            let index = self.textField.offset(from: self.textField.beginningOfDocument, to: selectedTextRange.end)
            return index
        } else {
            return nil
        }
    }
    
    public func textFieldDidBeginEditing(_ textField: UITextField) {
        self.focusIndex = self.currentCaretIndex()
        self.updateItemViews(animated: true)
    }
    
    public func textFieldDidEndEditing(_ textField: UITextField) {
        self.focusIndex = textField.text?.count ?? 0
        self.updateItemViews(animated: true)
    }
    
    public func textFieldDidChangeSelection(_ textField: UITextField) {
        self.focusIndex = self.currentCaretIndex()
        self.updateItemViews(animated: true)
    }
    
    public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        return false
    }
    
    private func updateItemViews(animated: Bool) {
        guard let theme = self.theme else {
            return
        }
        
        var delay: Double = 0.0
        for i in 0 ..< self.itemViews.count {
            let itemView = self.itemViews[i]
            let itemSize = itemView.bounds.size
            
            let fontSize: CGFloat
            if self.prefix.isEmpty {
                let height: CGFloat = self.compact ? 44.0 : 51.0
                fontSize = floor(13.0 * height / 28.0)
            } else {
                let height: CGFloat = 28.0
                fontSize = floor(21.0 * height / 28.0)
            }
            
            let borderColor: UInt32
            if self.isSucceed {
                borderColor = theme.succeedBorder
            } else if self.isFailed {
                borderColor = theme.failedBorder
            } else {
                borderColor = self.focusIndex == i ? theme.activeBorder : theme.inactiveBorder
            }
            
            itemView.update(borderColor: borderColor, isHighlighted: self.focusIndex == i)
            let itemText: String
            if i < self.textValue.count {
                itemText = String(self.textValue[self.textValue.index(self.textValue.startIndex, offsetBy: i)])
            } else {
                itemText = ""
            }
            itemView.update(textColor: theme.foreground, text: itemText, size: itemSize, fontSize: fontSize, animated: animated, delay: delay)
            if self.isResetting {
                delay += 0.05
            }
        }
    }
    
    public func update(theme: Theme, prefix: String, count: Int, width: CGFloat, compact: Bool) -> CGSize {
        self.theme = theme
        self.count = count
        self.prefix = prefix
        self.compact = compact
        
        if theme.isDark {
            self.textField.keyboardAppearance = .dark
        } else {
            self.textField.keyboardAppearance = .light
        }
        
        let fontSize: CGFloat
        let height: CGFloat
        if prefix.isEmpty {
            height = compact ? 44.0 : 51.0
            fontSize = floor(13.0 * height / 28.0)
        } else {
            height = 28.0
            fontSize = floor(21.0 * height / 28.0)
        }
        
        if #available(iOS 13.0, *) {
            self.prefixLabel.attributedText = NSAttributedString(string: prefix, font: UIFont.monospacedSystemFont(ofSize: 21.0, weight: .regular), textColor: UIColor(argb: theme.foreground))
        } else {
            self.prefixLabel.attributedText = NSAttributedString(string: prefix, font: Font.monospace(21.0), textColor: UIColor(argb: theme.foreground))
        }
        let prefixSize = self.prefixLabel.updateLayout(CGSize(width: width, height: 100.0))
        let prefixSpacing: CGFloat = prefix.isEmpty ? 0.0 : 8.0
        
        let itemSize = CGSize(width: floor(24.0 * height / 28.0), height: height)
        let itemSpacing: CGFloat = prefix.isEmpty ? 15.0 : 5.0
        let itemsWidth: CGFloat = itemSize.width * CGFloat(count) + itemSpacing * CGFloat(count - 1)
        
        let contentWidth: CGFloat = prefixSize.width + prefixSpacing + itemsWidth
        let contentOriginX: CGFloat = floor((width - contentWidth) / 2.0)
        
        self.prefixLabel.frame = CGRect(origin: CGPoint(x: contentOriginX, y: floorToScreenPixels((height - prefixSize.height) / 2.0)), size: prefixSize)
        
        for i in 0 ..< count {
            let itemView: ItemView
            if self.itemViews.count > i {
                itemView = self.itemViews[i]
            } else {
                itemView = ItemView()
                self.itemViews.append(itemView)
                self.addSubnode(itemView)
            }
            
            let borderColor: UInt32
            if self.isSucceed {
                borderColor = theme.succeedBorder
            } else if self.isFailed {
                borderColor = theme.failedBorder
            } else {
                borderColor = self.focusIndex == i ? theme.activeBorder : theme.inactiveBorder
            }
            
            itemView.update(borderColor: borderColor, isHighlighted: self.focusIndex == i)
            let itemText: String
            if i < self.textValue.count {
                itemText = String(self.textValue[self.textValue.index(self.textValue.startIndex, offsetBy: i)])
            } else {
                itemText = ""
            }
            itemView.update(textColor: theme.foreground, text: itemText, size: itemSize, fontSize: fontSize, animated: false)
            itemView.frame = CGRect(origin: CGPoint(x: contentOriginX + prefixSize.width + prefixSpacing + CGFloat(i) * (itemSize.width + itemSpacing), y: 0.0), size: itemSize)
        }
        if self.itemViews.count > count {
            for i in count ..< self.itemViews.count {
                self.itemViews[i].removeFromSupernode()
            }
            self.itemViews.removeSubrange(count...)
        }
        
        return CGSize(width: width, height: height)
    }
    
    public override func becomeFirstResponder() -> Bool {
        return self.textField.becomeFirstResponder()
    }
    
    public override func canBecomeFirstResponder() -> Bool {
        return self.textField.canBecomeFirstResponder
    }
    
    public override func resignFirstResponder() -> Bool {
        return self.textField.resignFirstResponder()
    }
    
    public override func canResignFirstResponder() -> Bool {
        return self.textField.canResignFirstResponder
    }
    
    public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        if self.bounds.contains(point) {
            return self.view
        } else {
            return nil
        }
    }
}