import Foundation
import UIKit
import SwiftSignalKit
import AsyncDisplayKit
import Display
import AppBundle
import LegacyComponents

public class MediaDustLayer: CALayer {
    private var emitter: CAEmitterCell?
    private var emitterLayer: CAEmitterLayer?
    
    private var size: CGSize?
    
    override public init() {
        super.init()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupEmitterLayerIfNeeded() {
        guard self.emitterLayer == nil else {
            return
        }
        
        let emitter = CAEmitterCell()
        emitter.color = UIColor(rgb: 0xffffff, alpha: 0.0).cgColor
        emitter.contents = UIImage(bundleImageName: "Components/TextSpeckle")?.cgImage
        emitter.contentsScale = 1.8
        emitter.emissionRange = .pi * 2.0
        emitter.lifetime = 8.0
        emitter.scale = 0.5
        emitter.velocityRange = 0.0
        emitter.name = "dustCell"
        emitter.alphaRange = 1.0
        emitter.setValue("point", forKey: "particleType")
        emitter.setValue(1.0, forKey: "mass")
        emitter.setValue(0.01, forKey: "massRange")
        self.emitter = emitter
        
        let alphaBehavior = CAEmitterCell.createEmitterBehavior(type: "valueOverLife")
        alphaBehavior.setValue("color.alpha", forKey: "keyPath")
        alphaBehavior.setValue([0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1], forKey: "values")
        alphaBehavior.setValue(true, forKey: "additive")
        
        let scaleBehavior = CAEmitterCell.createEmitterBehavior(type: "valueOverLife")
        scaleBehavior.setValue("scale", forKey: "keyPath")
        scaleBehavior.setValue([0.0, 0.5], forKey: "values")
        scaleBehavior.setValue([0.0, 0.05], forKey: "locations")
                
        let behaviors = [alphaBehavior, scaleBehavior]
    
        let emitterLayer = CAEmitterLayer()
        emitterLayer.masksToBounds = true
        emitterLayer.allowsGroupOpacity = true
        emitterLayer.lifetime = 1
        emitterLayer.emitterCells = [emitter]
        emitterLayer.seed = arc4random()
        emitterLayer.emitterShape = .rectangle
        emitterLayer.setValue(behaviors, forKey: "emitterBehaviors")
        self.addSublayer(emitterLayer)
                
        self.emitterLayer = emitterLayer
    }
    
    private func updateEmitter() {
        guard let size = self.size else {
            return
        }
        
        self.setupEmitterLayerIfNeeded()
        
        self.emitterLayer?.frame = CGRect(origin: CGPoint(), size: size)
        self.emitterLayer?.emitterSize = size
        self.emitterLayer?.emitterPosition = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
        
        let square = Float(size.width * size.height)
        Queue.mainQueue().async {
            self.emitter?.birthRate = min(100000.0, square * 0.02)
        }
    }
    
    public func updateLayout(size: CGSize) {
        self.size = size
        
        self.updateEmitter()
    }
}

public class MediaDustNode: ASDisplayNode {
    private var currentParams: (size: CGSize, color: UIColor)?
    private var animColor: CGColor?
    private let enableAnimations: Bool
        
    private var emitterNode: ASDisplayNode
    private var emitter: CAEmitterCell?
    private var emitterLayer: CAEmitterLayer?
    
    private let emitterMaskNode: ASDisplayNode
    private let emitterSpotNode: ASImageNode
    private let emitterMaskFillNode: ASDisplayNode
    
    private var staticNode: ASImageNode?
    private var staticParams: CGSize?
    
    public var revealOnTap = true
    public var isRevealed = false
    private var isExploding = false
    
    public var revealed: () -> Void = {}
    public var tapped: () -> Void = {}
    
    public init(enableAnimations: Bool) {
        self.enableAnimations = enableAnimations
        
        self.emitterNode = ASDisplayNode()
        self.emitterNode.isUserInteractionEnabled = false
        self.emitterNode.clipsToBounds = true
        
        self.emitterMaskNode = ASDisplayNode()
        self.emitterSpotNode = ASImageNode()
        self.emitterSpotNode.contentMode = .scaleToFill
        self.emitterSpotNode.isUserInteractionEnabled = false
        
        self.emitterMaskFillNode = ASDisplayNode()
        self.emitterMaskFillNode.backgroundColor = .white
        self.emitterMaskFillNode.isUserInteractionEnabled = false
                
        super.init()
        
        self.addSubnode(self.emitterNode)
        
        self.emitterMaskNode.addSubnode(self.emitterSpotNode)
        self.emitterMaskNode.addSubnode(self.emitterMaskFillNode)
    }
    
    public override func didLoad() {
        super.didLoad()
        
        if self.enableAnimations {
            let emitter = CAEmitterCell()
            emitter.color = UIColor(rgb: 0xffffff, alpha: 0.0).cgColor
            emitter.contents = UIImage(bundleImageName: "Components/TextSpeckle")?.cgImage
            emitter.contentsScale = 1.8
            emitter.emissionRange = .pi * 2.0
            emitter.lifetime = 8.0
            emitter.scale = 0.5
            emitter.velocityRange = 0.0
            emitter.name = "dustCell"
            emitter.alphaRange = 1.0
            emitter.setValue("point", forKey: "particleType")
            emitter.setValue(1.0, forKey: "mass")
            emitter.setValue(0.01, forKey: "massRange")
            self.emitter = emitter
            
            let alphaBehavior = CAEmitterCell.createEmitterBehavior(type: "valueOverLife")
            alphaBehavior.setValue("color.alpha", forKey: "keyPath")
            alphaBehavior.setValue([0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1], forKey: "values")
            alphaBehavior.setValue(true, forKey: "additive")
            
            let scaleBehavior = CAEmitterCell.createEmitterBehavior(type: "valueOverLife")
            scaleBehavior.setValue("scale", forKey: "keyPath")
            scaleBehavior.setValue([0.0, 0.5], forKey: "values")
            scaleBehavior.setValue([0.0, 0.05], forKey: "locations")
            
            let randomAttractor0 = CAEmitterCell.createEmitterBehavior(type: "simpleAttractor")
            randomAttractor0.setValue("randomAttractor0", forKey: "name")
            randomAttractor0.setValue(20, forKey: "falloff")
            randomAttractor0.setValue(35, forKey: "radius")
            randomAttractor0.setValue(5, forKey: "stiffness")
            randomAttractor0.setValue(NSValue(cgPoint: .zero), forKey: "position")
            
            let randomAttractor1 = CAEmitterCell.createEmitterBehavior(type: "simpleAttractor")
            randomAttractor1.setValue("randomAttractor1", forKey: "name")
            randomAttractor1.setValue(20, forKey: "falloff")
            randomAttractor1.setValue(35, forKey: "radius")
            randomAttractor1.setValue(5, forKey: "stiffness")
            randomAttractor1.setValue(NSValue(cgPoint: .zero), forKey: "position")
            
            let fingerAttractor = CAEmitterCell.createEmitterBehavior(type: "simpleAttractor")
            fingerAttractor.setValue("fingerAttractor", forKey: "name")
            
            let behaviors = [randomAttractor0, randomAttractor1, fingerAttractor, alphaBehavior, scaleBehavior]
            
            let emitterLayer = CAEmitterLayer()
            emitterLayer.masksToBounds = true
            emitterLayer.allowsGroupOpacity = true
            emitterLayer.lifetime = 1
            emitterLayer.emitterCells = [emitter]
            emitterLayer.seed = arc4random()
            emitterLayer.emitterShape = .rectangle
            emitterLayer.setValue(behaviors, forKey: "emitterBehaviors")
            
            emitterLayer.setValue(4.0, forKeyPath: "emitterBehaviors.fingerAttractor.stiffness")
            emitterLayer.setValue(false, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
            
            self.emitterLayer = emitterLayer
            
            self.emitterNode.layer.addSublayer(emitterLayer)
        } else {
            let staticNode = ASImageNode()
            self.staticNode = staticNode
            self.addSubnode(staticNode)
        }
        
        self.updateEmitter()
        
        self.setupRandomAnimations()
        
        self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap(_:))))
    }
    
    public func tap(at location: CGPoint) {
        guard !self.isRevealed else {
            return
        }
        
        self.tapped()
        
        guard self.revealOnTap else {
            return
        }
        
        self.isRevealed = true
        
        if self.enableAnimations {
            self.isExploding = true
            
            self.emitterLayer?.setValue(true, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
            self.emitterLayer?.setValue(location, forKeyPath: "emitterBehaviors.fingerAttractor.position")
            
            let maskSize = self.emitterNode.frame.size
            Queue.concurrentDefaultQueue().async {
                let emitterMaskImage = generateMaskImage(size: maskSize, position: location, inverse: true)
                
                Queue.mainQueue().async {
                    self.emitterSpotNode.image = emitterMaskImage
                }
            }
            
            Queue.mainQueue().after(0.1 * UIView.animationDurationFactor()) {
                let xFactor = (location.x / self.emitterNode.frame.width - 0.5) * 2.0
                let yFactor = (location.y / self.emitterNode.frame.height - 0.5) * 2.0
                let maxFactor = max(abs(xFactor), abs(yFactor))
                
                let scaleAddition = maxFactor * 4.0
                let durationAddition = -maxFactor * 0.2
                
                self.supernode?.view.mask = self.emitterMaskNode.view
                self.emitterSpotNode.frame = CGRect(x: 0.0, y: 0.0, width: self.emitterMaskNode.frame.width * 3.0, height: self.emitterMaskNode.frame.height * 3.0)
                
                self.emitterSpotNode.layer.anchorPoint = CGPoint(x: location.x / self.emitterMaskNode.frame.width, y: location.y / self.emitterMaskNode.frame.height)
                self.emitterSpotNode.position = location
                self.emitterSpotNode.layer.animateScale(from: 0.3333, to: 10.5 + scaleAddition, duration: 0.45 + durationAddition, removeOnCompletion: false, completion: { [weak self] _ in
                    self?.revealed()
                    self?.alpha = 0.0
                    self?.supernode?.view.mask = nil
                    
                })
                self.emitterMaskFillNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
            }
            
            Queue.mainQueue().after(0.8 * UIView.animationDurationFactor()) {
                self.isExploding = false
                self.emitterLayer?.setValue(false, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
                
                self.emitterSpotNode.layer.removeAllAnimations()
                self.emitterMaskFillNode.layer.removeAllAnimations()
            }
        } else {
            self.supernode?.alpha = 0.0
            self.supernode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, completion: { [weak self] _ in
                self?.revealed()
            })
        }
    }
    
    @objc private func tap(_ gestureRecognizer: UITapGestureRecognizer) {
        let location = gestureRecognizer.location(in: self.view)
        self.tap(at: location)
    }
        
    private var didSetupAnimations = false
    private func setupRandomAnimations() {
        guard self.frame.width > 0.0, self.emitterLayer != nil, !self.didSetupAnimations else {
            return
        }
        self.didSetupAnimations = true
        
        let falloffAnimation1 = CABasicAnimation(keyPath: "emitterBehaviors.randomAttractor0.falloff")
        falloffAnimation1.beginTime = 0.0
        falloffAnimation1.fillMode = .both
        falloffAnimation1.isRemovedOnCompletion = false
        falloffAnimation1.autoreverses = true
        falloffAnimation1.repeatCount = .infinity
        falloffAnimation1.duration = 2.0
        falloffAnimation1.fromValue = -20.0 as NSNumber
        falloffAnimation1.toValue = 60.0 as NSNumber
        falloffAnimation1.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
        self.emitterLayer?.add(falloffAnimation1, forKey: "emitterBehaviors.randomAttractor0.falloff")
        
        let positionAnimation1 = CAKeyframeAnimation(keyPath: "emitterBehaviors.randomAttractor0.position")
        positionAnimation1.beginTime = 0.0
        positionAnimation1.fillMode = .both
        positionAnimation1.isRemovedOnCompletion = false
        positionAnimation1.autoreverses = true
        positionAnimation1.repeatCount = .infinity
        positionAnimation1.duration = 3.0
        positionAnimation1.calculationMode = .discrete
        
        let xInset1: CGFloat = self.frame.width * 0.2
        let yInset1: CGFloat = self.frame.height * 0.2
        var positionValues1: [CGPoint] = []
        for _ in 0 ..< 35 {
            positionValues1.append(CGPoint(x: CGFloat.random(in: xInset1 ..< self.frame.width - xInset1), y: CGFloat.random(in: yInset1 ..< self.frame.height - yInset1)))
        }
        positionAnimation1.values = positionValues1
        
        self.emitterLayer?.add(positionAnimation1, forKey: "emitterBehaviors.randomAttractor0.position")
        
        let falloffAnimation2 = CABasicAnimation(keyPath: "emitterBehaviors.randomAttractor1.falloff")
        falloffAnimation2.beginTime = 0.0
        falloffAnimation2.fillMode = .both
        falloffAnimation2.isRemovedOnCompletion = false
        falloffAnimation2.autoreverses = true
        falloffAnimation2.repeatCount = .infinity
        falloffAnimation2.duration = 2.0
        falloffAnimation2.fromValue = -20.0 as NSNumber
        falloffAnimation2.toValue = 60.0 as NSNumber
        falloffAnimation2.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
        self.emitterLayer?.add(falloffAnimation2, forKey: "emitterBehaviors.randomAttractor1.falloff")
                
        let positionAnimation2 = CAKeyframeAnimation(keyPath: "emitterBehaviors.randomAttractor1.position")
        positionAnimation2.beginTime = 0.0
        positionAnimation2.fillMode = .both
        positionAnimation2.isRemovedOnCompletion = false
        positionAnimation2.autoreverses = true
        positionAnimation2.repeatCount = .infinity
        positionAnimation2.duration = 3.0
        positionAnimation2.calculationMode = .discrete
        
        let xInset2: CGFloat = self.frame.width * 0.1
        let yInset2: CGFloat = self.frame.height * 0.1
        var positionValues2: [CGPoint] = []
        for _ in 0 ..< 35 {
            positionValues2.append(CGPoint(x: CGFloat.random(in: xInset2 ..< self.frame.width - xInset2), y: CGFloat.random(in: yInset2 ..< self.frame.height - yInset2)))
        }
        positionAnimation2.values = positionValues2
        
        self.emitterLayer?.add(positionAnimation2, forKey: "emitterBehaviors.randomAttractor1.position")
    }
    
    private func updateEmitter() {
        guard let (size, _) = self.currentParams else {
            return
        }
        if self.enableAnimations {
            self.emitterLayer?.frame = CGRect(origin: CGPoint(), size: size)
            self.emitterLayer?.emitterSize = size
            self.emitterLayer?.emitterPosition = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
            
            let radius = max(size.width, size.height)
            self.emitterLayer?.setValue(max(size.width, size.height), forKeyPath: "emitterBehaviors.fingerAttractor.radius")
            self.emitterLayer?.setValue(radius * -0.5, forKeyPath: "emitterBehaviors.fingerAttractor.falloff")
            
            let square = Float(size.width * size.height)
            Queue.mainQueue().async {
                self.emitter?.birthRate = min(100000.0, square * 0.02)
            }
        } else {
            if let staticParams = self.staticParams, staticParams == size && self.staticNode?.image != nil {
                return
            }
            self.staticParams = size
            
            let start = CACurrentMediaTime()
            
            Queue.concurrentDefaultQueue().async {
                var generator = ArbitraryRandomNumberGenerator(seed: 1)
                let image = generateImage(size, rotatedContext: { size, context in
                    let bounds = CGRect(origin: .zero, size: size)
                    context.clear(bounds)
                    
                    context.setFillColor(UIColor.white.cgColor)
                    let rect = CGRect(origin: .zero, size: size)
                    
                    let rate = Int(rect.width * rect.height * 0.04)
                    for _ in 0 ..< rate {
                        let location = CGPoint(x: .random(in: rect.minX ..< rect.maxX, using: &generator), y: .random(in: rect.minY ..< rect.maxY, using: &generator))
                        context.fillEllipse(in: CGRect(origin: location, size: CGSize(width: 1.0, height: 1.0)))
                    }
                })
                Queue.mainQueue().async {
                    self.staticNode?.image = image
                }
            }
            self.staticNode?.frame = CGRect(origin: CGPoint(), size: size)
            
            print("total draw \(CACurrentMediaTime() - start)")
        }
    }
        
    public func update(size: CGSize, color: UIColor, transition: ContainedViewLayoutTransition) {
        self.currentParams = (size, color)
        
        let bounds = CGRect(origin: .zero, size: size)
        transition.updateFrame(node: self.emitterNode, frame: bounds)
        
        self.emitterMaskNode.frame = bounds
        self.emitterMaskFillNode.frame = bounds
        
        self.staticNode?.frame = bounds
        
        if self.isNodeLoaded {
            self.updateEmitter()
            self.setupRandomAnimations()
        }
    }
    
    public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        if !self.isRevealed {
            return super.point(inside: point, with: event)
        } else {
            return false
        }
    }
}