import Foundation
import UIKit
import AsyncDisplayKit
import Display

private let iconImage = generateTintedImage(image: UIImage(bundleImageName: "Call/Pin"), color: .white)

private final class VoiceChatPinNodeDrawingState: NSObject {
    let color: UIColor
    let transition: CGFloat
    let reverse: Bool
    
    init(color: UIColor, transition: CGFloat, reverse: Bool) {
        self.color = color
        self.transition = transition
        self.reverse = reverse
        
        super.init()
    }
}

final class VoiceChatPinNode: ASDisplayNode {
    class State: Equatable {
        let pinned: Bool
        let color: UIColor
    
        init(pinned: Bool, color: UIColor) {
            self.pinned = pinned
            self.color = color
        }
        
        static func ==(lhs: State, rhs: State) -> Bool {
            if lhs.pinned != rhs.pinned {
                return false
            }
            if lhs.color.argb != rhs.color.argb {
                return false
            }
            return true
        }
    }
    
    private class TransitionContext {
        let startTime: Double
        let duration: Double
        let previousState: State
        
        init(startTime: Double, duration: Double, previousState: State) {
            self.startTime = startTime
            self.duration = duration
            self.previousState = previousState
        }
    }
    
    private var animator: ConstantDisplayLinkAnimator?
    
    private var hasState = false
    private var state: State = State(pinned: false, color: .black)
    private var transitionContext: TransitionContext?
    
    override init() {
        super.init()
        
        self.isOpaque = false
    }
    
    func update(state: State, animated: Bool) {
        var animated = animated
        if !self.hasState {
            self.hasState = true
            animated = false
        }
        
        if self.state != state {
            let previousState = self.state
            self.state = state
            
            if animated {
                self.transitionContext = TransitionContext(startTime: CACurrentMediaTime(), duration: 0.18, previousState: previousState)
            }
            
            self.updateAnimations()
            self.setNeedsDisplay()
        }
    }
    
    private func updateAnimations() {
        var animate = false
        let timestamp = CACurrentMediaTime()
        
        if let transitionContext = self.transitionContext {
            if transitionContext.startTime + transitionContext.duration < timestamp {
                self.transitionContext = nil
            } else {
                animate = true
            }
        }
        
        if animate {
            let animator: ConstantDisplayLinkAnimator
            if let current = self.animator {
                animator = current
            } else {
                animator = ConstantDisplayLinkAnimator(update: { [weak self] in
                    self?.updateAnimations()
                })
                self.animator = animator
            }
            animator.isPaused = false
        } else {
            self.animator?.isPaused = true
        }
        
        self.setNeedsDisplay()
    }
    
    override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
        var transitionFraction: CGFloat = self.state.pinned ? 1.0 : 0.0
        var color = self.state.color
        
        var reverse = false
        if let transitionContext = self.transitionContext {
            let timestamp = CACurrentMediaTime()
            var t = CGFloat((timestamp - transitionContext.startTime) / transitionContext.duration)
            t = min(1.0, max(0.0, t))
            
            if transitionContext.previousState.pinned != self.state.pinned {
                transitionFraction = self.state.pinned ? t : 1.0 - t
                
                reverse = transitionContext.previousState.pinned
            }
            
            if transitionContext.previousState.color.rgb != color.rgb {
                color = transitionContext.previousState.color.interpolateTo(color, fraction: t)!
            }
        }
        
        return VoiceChatPinNodeDrawingState(color: color, transition: transitionFraction, reverse: reverse)
    }
    
    @objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
        let context = UIGraphicsGetCurrentContext()!
        
        if !isRasterizing {
            context.setBlendMode(.copy)
            context.setFillColor(UIColor.clear.cgColor)
            context.fill(bounds)
        }
        
        guard let parameters = parameters as? VoiceChatPinNodeDrawingState else {
            return
        }

        context.setFillColor(parameters.color.cgColor)
        
        let clearLineWidth: CGFloat = 2.0
        let lineWidth: CGFloat = 1.0 + UIScreenPixel
        if let iconImage = iconImage?.cgImage {
            context.saveGState()
            context.translateBy(x: bounds.midX, y: bounds.midY)
            context.scaleBy(x: 1.0, y: -1.0)
            context.translateBy(x: -bounds.midX, y: -bounds.midY)
            context.draw(iconImage, in: CGRect(origin: CGPoint(), size: CGSize(width: 48.0, height: 48.0)))
            context.restoreGState()
        }
        
        if parameters.transition > 0.0 {
            let startPoint: CGPoint
            let endPoint: CGPoint
            
            let origin = CGPoint(x: 14.0, y: 16.0 - UIScreenPixel)
            let length: CGFloat = 17.0
        
            if parameters.reverse {
                startPoint = CGPoint(x: origin.x + length * (1.0 - parameters.transition), y: origin.y + length * (1.0 - parameters.transition)).offsetBy(dx: UIScreenPixel, dy: -UIScreenPixel)
                endPoint = CGPoint(x: origin.x + length, y: origin.y + length).offsetBy(dx: UIScreenPixel, dy: -UIScreenPixel)
            } else {
                startPoint = origin.offsetBy(dx: UIScreenPixel, dy: -UIScreenPixel)
                endPoint = CGPoint(x: origin.x + length * parameters.transition, y: origin.y + length * parameters.transition).offsetBy(dx: UIScreenPixel, dy: -UIScreenPixel)
            }
            
        
            context.setBlendMode(.clear)
            context.setLineWidth(clearLineWidth)
            
            context.move(to: startPoint.offsetBy(dx: 0.0, dy: 1.0 + UIScreenPixel))
            context.addLine(to: endPoint.offsetBy(dx: 0.0, dy: 1.0 + UIScreenPixel))
            context.strokePath()
        
            context.setBlendMode(.normal)
            context.setStrokeColor(parameters.color.cgColor)
            context.setLineWidth(lineWidth)
            context.setLineCap(.round)
            context.setLineJoin(.round)
            
            context.move(to: startPoint)
            context.addLine(to: endPoint)
            context.strokePath()
        }
    }
}