import Foundation
import UIKit
import AsyncDisplayKit
import Display

private final class MultiScaleTextStateNode: ASDisplayNode {
    let tintTextNode: ImmediateTextNode
    let noTintTextNode: ImmediateTextNode
    
    var currentLayout: MultiScaleTextLayout?
    
    override init() {
        self.tintTextNode = ImmediateTextNode()
        self.tintTextNode.displaysAsynchronously = false
        self.tintTextNode.renderContentTypes = TextNode.RenderContentTypes.all.subtracting(TextNode.RenderContentTypes.emoji)
        
        self.noTintTextNode = ImmediateTextNode()
        self.noTintTextNode.displaysAsynchronously = false
        self.noTintTextNode.renderContentTypes = .emoji
        
        super.init()
        
        self.addSubnode(self.tintTextNode)
        self.addSubnode(self.noTintTextNode)
    }
}

public final class MultiScaleTextState {
    public struct Attributes {
        public var font: UIFont
        public var color: UIColor
        public var shadowColor: UIColor?

        public init(font: UIFont, color: UIColor, shadowColor: UIColor? = nil) {
            self.font = font
            self.color = color
            self.shadowColor = shadowColor
        }
    }
    
    public let attributes: Attributes
    public let constrainedSize: CGSize
    
    public init(attributes: Attributes, constrainedSize: CGSize) {
        self.attributes = attributes
        self.constrainedSize = constrainedSize
    }
}

public struct MultiScaleTextLayout {
    public var size: CGSize

    public init(size: CGSize) {
        self.size = size
    }
}

public final class MultiScaleTextNode: ASDisplayNode {
    private let stateNodes: [AnyHashable: MultiScaleTextStateNode]
    
    public init(stateKeys: [AnyHashable]) {
        self.stateNodes = Dictionary(stateKeys.map { ($0, MultiScaleTextStateNode()) }, uniquingKeysWith: { lhs, _ in lhs })
        
        super.init()
        
        for (_, node) in self.stateNodes {
            self.addSubnode(node)
        }
    }
    
    public func stateNode(forKey key: AnyHashable) -> ASDisplayNode? {
        return self.stateNodes[key]?.tintTextNode
    }
    
    public func updateTintColor(color: UIColor, transition: ContainedViewLayoutTransition) {
        for (_, node) in self.stateNodes {
            transition.updateTintColor(layer: node.tintTextNode.layer, color: color)
        }
    }
    
    public func updateLayout(text: String, states: [AnyHashable: MultiScaleTextState], mainState: AnyHashable) -> [AnyHashable: MultiScaleTextLayout] {
        assert(Set(states.keys) == Set(self.stateNodes.keys))
        assert(states[mainState] != nil)
        
        var result: [AnyHashable: MultiScaleTextLayout] = [:]
        var mainLayout: MultiScaleTextLayout?
        for (key, state) in states {
            if let node = self.stateNodes[key] {
                node.tintTextNode.attributedText = NSAttributedString(string: text, attributes: [
                    .font: state.attributes.font,
                    .foregroundColor: state.attributes.color
                ])
                node.noTintTextNode.attributedText = NSAttributedString(string: text, attributes: [
                    .font: state.attributes.font,
                    .foregroundColor: state.attributes.color
                ])
                if let shadowColor = state.attributes.shadowColor {
                    node.tintTextNode.textShadowColor = shadowColor
                    node.tintTextNode.textShadowBlur = 3.0
                    node.noTintTextNode.textShadowColor = shadowColor
                    node.noTintTextNode.textShadowBlur = 3.0
                } else {
                    node.tintTextNode.shadowColor = nil
                    node.noTintTextNode.shadowColor = nil
                }
                node.tintTextNode.isAccessibilityElement = true
                node.tintTextNode.accessibilityLabel = text
                node.noTintTextNode.isAccessibilityElement = false
                let nodeSize = node.tintTextNode.updateLayout(state.constrainedSize)
                let _ = node.noTintTextNode.updateLayout(state.constrainedSize)
                let nodeLayout = MultiScaleTextLayout(size: nodeSize)
                if key == mainState {
                    mainLayout = nodeLayout
                }
                node.currentLayout = nodeLayout
                result[key] = nodeLayout
            }
        }
        if let mainLayout = mainLayout {
            let mainBounds = CGRect(origin: CGPoint(x: -mainLayout.size.width / 2.0, y: -mainLayout.size.height / 2.0), size: mainLayout.size)
            for (key, _) in states {
                if let node = self.stateNodes[key], let nodeLayout = result[key] {
                    let textFrame = CGRect(origin: CGPoint(x: mainBounds.minX, y: mainBounds.minY + floor((mainBounds.height - nodeLayout.size.height) / 2.0)), size: nodeLayout.size)
                    node.tintTextNode.frame = textFrame
                    node.noTintTextNode.frame = textFrame
                }
            }
        }
        return result
    }
    
    public func update(stateFractions: [AnyHashable: CGFloat], alpha: CGFloat = 1.0, transition: ContainedViewLayoutTransition) {
        var fractionSum: CGFloat = 0.0
        for (_, fraction) in stateFractions {
            fractionSum += fraction
        }
        for (key, fraction) in stateFractions {
            if let node = self.stateNodes[key], let _ = node.currentLayout {
                if !transition.isAnimated {
                    node.layer.removeAllAnimations()
                }
                transition.updateAlpha(node: node, alpha: fraction / fractionSum * alpha)
            }
        }
    }
}