import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit

private let dotDiameter: CGFloat = 13.0
private let dotSpacing: CGFloat = 24.0
private let fieldHeight: CGFloat = 38.0

private func generateDotImage(color: UIColor, filled: Bool) -> UIImage? {
    return generateImage(CGSize(width: dotDiameter, height: dotDiameter), contextGenerator: { size, context in
        let bounds = CGRect(origin: CGPoint(), size: size)
        context.clear(bounds)
        
        if filled {
            context.setFillColor(color.cgColor)
            context.fillEllipse(in: bounds)
        } else {
            context.setStrokeColor(color.cgColor)
            context.setLineWidth(1.0)
            context.strokeEllipse(in: bounds.insetBy(dx: 0.5, dy: 0.5))
        }
    })
}

private func generateFieldBackgroundImage(backgroundImage: UIImage?, backgroundSize: CGSize?, frame: CGRect) -> UIImage? {
    return generateImage(frame.size, contextGenerator: { size, context in
        let bounds = CGRect(origin: CGPoint(), size: size)
        context.clear(bounds)
        
        let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height), cornerRadius: 6.0)
        context.addPath(path.cgPath)
        context.clip()
        
        if let backgroundImage = backgroundImage, let backgroundSize = backgroundSize {
            let relativeFrame = CGRect(x: -frame.minX, y: frame.minY - backgroundSize.height + frame.size.height
                , width: backgroundSize.width, height: backgroundSize.height)
            context.draw(backgroundImage.cgImage!, in: relativeFrame)
        } else {
            context.setFillColor(UIColor(rgb: 0xffffff, alpha: 1.0).cgColor)
            context.fill(bounds)
        }
        context.setBlendMode(.clear)
        context.setFillColor(UIColor.clear.cgColor)
    
        let innerPath = UIBezierPath(roundedRect: CGRect(x: 1.0, y: 1.0, width: size.width - 2.0, height: size.height - 2.0), cornerRadius: 6.0)
        context.addPath(innerPath.cgPath)
        context.fillPath()
    })
}

private let validDigitsSet: CharacterSet = {
    return CharacterSet(charactersIn: "0".unicodeScalars.first! ... "9".unicodeScalars.first!)
}()

public enum PasscodeEntryFieldType {
    case digits6
    case digits4
    case alphanumeric
    
    public var maxLength: Int? {
        switch self {
            case .digits6:
                return 6
            case .digits4:
                return 4
            case .alphanumeric:
                return nil
        }
    }
    
    public var allowedCharacters: CharacterSet? {
        switch self {
            case .digits6, .digits4:
                return validDigitsSet
            case .alphanumeric:
                return nil
        }
    }
    
    public var keyboardType: UIKeyboardType {
        switch self {
            case .digits6, .digits4:
                if #available(iOS 10.0, *) {
                    return .asciiCapableNumberPad
                } else {
                    return .numberPad
                }
            case .alphanumeric:
                return .default
        }
    }
}

private class PasscodeEntryInputView: UIView {
    
}

private class PasscodeEntryDotNode: ASImageNode {
    private let regularImage: UIImage
    private let filledImage: UIImage
    private var currentImage: UIImage
    
    init(color: UIColor) {
        self.regularImage = generateDotImage(color: color, filled: false)!
        self.filledImage = generateDotImage(color: color, filled: true)!
        self.currentImage = self.regularImage
        
        super.init()
        
        self.image = self.currentImage
    }
    
    func updateState(filled: Bool, animated: Bool = false, delay: Double = 0.0) {
        let image = filled ? self.filledImage : self.regularImage
        if self.currentImage !== image {
            let currentContents = self.layer.contents
            self.layer.removeAnimation(forKey: "contents")
            if let currentContents = currentContents, animated {
                self.layer.animate(from: currentContents as AnyObject, to: image.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: image === self.regularImage ? 0.25 : 0.05, delay: delay, removeOnCompletion: false, completion: { finished in
                    if finished {
                        self.image = image
                    }
                })
            } else {
                self.image = image
            }
            self.currentImage = image
        }
    }
}

public final class PasscodeInputFieldNode: ASDisplayNode, UITextFieldDelegate {
    private var background: PasscodeBackground?
    private var color: UIColor
    private var accentColor: UIColor
    private var fieldType: PasscodeEntryFieldType
    private let useCustomNumpad: Bool
    
    private let textFieldNode: TextFieldNode
    private let borderNode: ASImageNode
    private let dotNodes: [PasscodeEntryDotNode]
    
    private var validLayout: (CGSize, CGFloat)?
    
    public var complete: ((String) -> Void)?
    
    public var text: String {
        return self.textFieldNode.textField.text ?? ""
    }
    
    public var keyboardAppearance: UIKeyboardAppearance {
        didSet {
            self.textFieldNode.textField.keyboardAppearance = self.keyboardAppearance
        }
    }
    
    public init(color: UIColor, accentColor: UIColor, fieldType: PasscodeEntryFieldType, keyboardAppearance: UIKeyboardAppearance, useCustomNumpad: Bool = false) {
        self.color = color
        self.accentColor = accentColor
        self.fieldType = fieldType
        self.keyboardAppearance = keyboardAppearance
        self.useCustomNumpad = useCustomNumpad
        
        self.textFieldNode = TextFieldNode()
        self.borderNode = ASImageNode()
        self.dotNodes = (0 ..< 6).map { _ in PasscodeEntryDotNode(color: color) }
        
        super.init()
        
        self.isUserInteractionEnabled = false
        
        for node in self.dotNodes {
            self.addSubnode(node)
        }
        self.addSubnode(self.textFieldNode)
        self.addSubnode(self.borderNode)
    }
    
    override public func didLoad() {
        super.didLoad()
        
        self.textFieldNode.textField.isSecureTextEntry = true
        self.textFieldNode.textField.textColor = self.color
        self.textFieldNode.textField.delegate = self
        self.textFieldNode.textField.returnKeyType = .done
        self.textFieldNode.textField.tintColor = self.accentColor
        self.textFieldNode.textField.keyboardAppearance = self.keyboardAppearance
        self.textFieldNode.textField.keyboardType = self.fieldType.keyboardType
        self.textFieldNode.textField.tintColor = self.accentColor
        
        if self.useCustomNumpad {
            switch self.fieldType {
                case .digits6, .digits4:
                    self.textFieldNode.textField.inputView = PasscodeEntryInputView()
                case .alphanumeric:
                    break
            }
        }
    }
    
    func updateFieldType(_ fieldType: PasscodeEntryFieldType, animated: Bool) {
        self.fieldType = fieldType
        
        self.textFieldNode.textField.keyboardType = self.fieldType.keyboardType
        
        if let (size, topOffset) = self.validLayout {
            let _ = self.updateLayout(size: size, topOffset: topOffset, transition: animated ? .animated(duration: 0.25, curve: .easeInOut) : .immediate)
        }
    }
    
    func updateBackground(_ background: PasscodeBackground) {
        self.background = background
        if let (size, topOffset) = self.validLayout {
            let _ = self.updateLayout(size: size, topOffset: topOffset, transition: .immediate)
        }
    }
    
    public func activateInput() {
        self.textFieldNode.textField.becomeFirstResponder()
    }
    
    func animateIn() {
        switch self.fieldType {
            case .digits6, .digits4:
                for node in self.dotNodes {
                    node.layer.animateScale(from: 0.0001, to: 1.0, duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
                }
            case .alphanumeric:
                self.textFieldNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
                self.borderNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
        }
    }
    
    func animateSuccess() {
        switch self.fieldType {
            case .digits6, .digits4:
                var delay: Double = 0.0
                for node in self.dotNodes {
                    node.updateState(filled: true, animated: true, delay: delay)
                    delay += 0.01
                }
            case .alphanumeric:
                if (self.textFieldNode.textField.text ?? "").isEmpty {
                    self.textFieldNode.textField.text = "passwordpassword"
                }
        }
    }
    
    public func reset(animated: Bool = true) {
        var delay: Double = 0.0
        for node in self.dotNodes.reversed() {
            if node.alpha < 1.0 {
                continue
            }
            
            node.updateState(filled: false, animated: animated, delay: delay)
            delay += 0.05
        }
        self.textFieldNode.textField.text = ""
    }
    
    func append(_ string: String) {
        var text = (self.textFieldNode.textField.text ?? "") + string
        let maxLength = self.fieldType.maxLength
        if let maxLength = maxLength, text.count > maxLength {
            return
        }
        self.textFieldNode.textField.text = text
        
        text = self.textFieldNode.textField.text ?? "" + string
        self.updateDots(count: text.count, animated: false)
        
        if let maxLength = maxLength, text.count == maxLength {
            Queue.mainQueue().after(0.2) {
                self.complete?(text)
            }
        }
    }
    
    func delete() -> Bool {
        var text = self.textFieldNode.textField.text ?? ""
        guard !text.isEmpty else {
            return false
        }
        text = String(text[text.startIndex ..< text.index(text.endIndex, offsetBy: -1)])
        self.textFieldNode.textField.text = text
        self.updateDots(count: text.count, animated: true)
        return true
    }
    
    func updateDots(count: Int, animated: Bool) {
        var i = -1
        for node in self.dotNodes {
            if node.alpha < 1.0 {
                continue
            }
            i += 1
            node.updateState(filled: i < count, animated: animated)
        }
    }
    
    public func update(fieldType: PasscodeEntryFieldType) {
        if fieldType != self.fieldType {
            self.textFieldNode.textField.text = ""
        }
        self.fieldType = fieldType
        if let (size, topOffset) = self.validLayout {
            let _ = self.updateLayout(size: size, topOffset: topOffset, transition: .immediate)
        }
    }
    
    public func updateLayout(size: CGSize, topOffset: CGFloat, transition: ContainedViewLayoutTransition) -> CGRect {
        self.validLayout = (size, topOffset)
        
        let fieldAlpha: CGFloat
        switch self.fieldType {
            case .digits6, .digits4:
                fieldAlpha = 0.0
            case .alphanumeric:
                fieldAlpha = 1.0
        }
        
        transition.updateAlpha(node: self.textFieldNode, alpha: fieldAlpha)
        transition.updateAlpha(node: self.borderNode, alpha: fieldAlpha)
        
        let origin = CGPoint(x: floor((size.width - dotDiameter * 6 - dotSpacing * 5) / 2.0), y: topOffset)
        for i in 0 ..< self.dotNodes.count {
            let node = self.dotNodes[i]
            let dotAlpha: CGFloat
            switch self.fieldType {
                case .digits6:
                    dotAlpha = 1.0
                case .digits4:
                    dotAlpha = (i > 0 && i < self.dotNodes.count - 1) ? 1.0 : 0.0
                case .alphanumeric:
                    dotAlpha = 0.0
            }
            transition.updateAlpha(node: node, alpha: dotAlpha)
            
            let dotFrame = CGRect(x: origin.x + CGFloat(i) * (dotDiameter + dotSpacing), y: origin.y, width: dotDiameter, height: dotDiameter)
            transition.updateFrame(node: node, frame: dotFrame)
        }
        
        var inset: CGFloat = 50.0
        if !self.useCustomNumpad {
            inset = 16.0
        }
        let fieldFrame = CGRect(x: inset, y: origin.y, width: size.width - inset * 2.0, height: fieldHeight)
        transition.updateFrame(node: self.borderNode, frame: fieldFrame)
        transition.updateFrame(node: self.textFieldNode, frame: fieldFrame.insetBy(dx: 13.0, dy: 0.0))
        
        self.borderNode.image = generateFieldBackgroundImage(backgroundImage: self.background?.foregroundImage, backgroundSize: self.background?.size, frame: fieldFrame)
        
        return fieldFrame
    }
    
    public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        let currentText = textField.text ?? ""
        let text = (currentText as NSString).replacingCharacters(in: range, with: string)
        if let maxLength = self.fieldType.maxLength, text.count > maxLength {
            return false
        }
        if let allowedCharacters = self.fieldType.allowedCharacters, let _ = text.rangeOfCharacter(from: allowedCharacters.inverted) {
            return false
        }
        self.updateDots(count: text.count, animated: text.count < currentText.count)
        
        if string == "\n" {
            Queue.mainQueue().after(0.2) {
                self.complete?(currentText)
            }
            return false
        }
        
        if let maxLength = self.fieldType.maxLength, text.count == maxLength {
            Queue.mainQueue().after(0.2) {
                self.complete?(text)
            }
        }
        return true
    }
}