import AVFoundation
import Foundation
import UIKit

public class LyricOverlay {
    let text: String
    let frame: CGRect
    let alignmentMode: CATextLayerAlignmentMode
    let startTime: Double
    let showTime: Double
    let endTime: Double
    let font: UIFont

    var positionYUpNext: CGFloat {
        return frame.origin.y + frame.height / 2 - 60
    }

    var positionYPlaying: CGFloat {
        return frame.origin.y + frame.height / 2
    }

    let opacityUpNext: CGFloat = 0.67
    let opacityNowPlaying: CGFloat = 1

    let shadowRadiusUpNext: CGFloat = 0
    let shadowRadiusPlaying: CGFloat = 10

    let shadowColorUpNext: UIColor = .clear
    let shadowColorPlaying: UIColor = .black

    init(
        text: String,
        frame: CGRect,
        alignmentMode: CATextLayerAlignmentMode,
        startTime: Double,
        showTime: Double,
        endTime: Double,
        font: UIFont = TypographyV1.headline2.neueMontrealMedium().uiFont!
    ) {
        self.text = text
        self.frame = frame
        self.alignmentMode = alignmentMode
        self.startTime = startTime
        self.showTime = showTime
        self.endTime = endTime
        self.font = font
    }

    func createAnimatedLayer() -> CALayer {
        let layer = CATextLayer()
        layer.string = text
        layer.fontSize = 40
        layer.alignmentMode = alignmentMode
        layer.foregroundColor = UIColor.white.cgColor
        layer.frame = frame
        layer.backgroundColor = UIColor.clear.cgColor
        layer.font = font

        let animationGroup = CAAnimationGroup()
        animationGroup.duration = endTime - startTime
        animationGroup.beginTime = AVCoreAnimationBeginTimeAtZero + startTime
        animationGroup.fillMode = .both
        animationGroup.isRemovedOnCompletion = false

        animationGroup.animations = [opacityAnimation, positionAnimation, shadowRadiusAnimation, shadowColorAnimation]
        layer.add(animationGroup, forKey: "textAnimations")

        return layer
    }

    private func getKeyTimes() -> [NSNumber] {
        let keyTimes = [
            0,
            0.01, // The line is up-next
            (showTime - startTime - 0.2) / (endTime - startTime), // Just before line starts playing
            (showTime - startTime) / (endTime - startTime), // Line is now playing
            (endTime - startTime - 0.2) / (endTime - startTime), // Just before end
            1,
        ].map { max(0, min($0, 1)) }
        return keyTimes as [NSNumber]
    }

    private var opacityAnimation: CAKeyframeAnimation {
        let animation = CAKeyframeAnimation(keyPath: "opacity")
        animation.values = [
            0,
            opacityUpNext,
            opacityUpNext,
            opacityNowPlaying,
            opacityNowPlaying,
            0,
        ]
        animation.keyTimes = getKeyTimes()
        return animation
    }

    private var positionAnimation: CAKeyframeAnimation {
        let animation = CAKeyframeAnimation(keyPath: "position.y")
        animation.values = [
            positionYUpNext,
            positionYUpNext,
            positionYUpNext,
            positionYPlaying,
            positionYPlaying,
            positionYPlaying,
        ]
        animation.keyTimes = getKeyTimes()
        return animation
    }

    private var shadowRadiusAnimation: CAKeyframeAnimation {
        let animation = CAKeyframeAnimation(keyPath: "shadowRadius")
        animation.values = [
            0,
            shadowRadiusUpNext,
            shadowRadiusUpNext,
            shadowRadiusPlaying,
            shadowRadiusPlaying,
            0,
        ]
        animation.keyTimes = getKeyTimes()
        return animation
    }

    private var shadowColorAnimation: CAKeyframeAnimation {
        let animation = CAKeyframeAnimation(keyPath: "shadowColor")
        animation.values = [
            UIColor.clear.cgColor,
            shadowColorUpNext,
            shadowColorUpNext,
            shadowColorPlaying,
            shadowColorPlaying,
            UIColor.clear.cgColor,
        ]
        animation.keyTimes = getKeyTimes()
        return animation
    }
}
