import Foundation
import UIKit

public final class DisplayLinkTarget: NSObject {
    private let f: () -> Void
    
    public init(_ f: @escaping () -> Void) {
        self.f = f
    }
    
    @objc public func event() {
        self.f()
    }
}

public final class DisplayLinkAnimator {
    private var displayLink: CADisplayLink!
    private let duration: Double
    private let fromValue: CGFloat
    private let toValue: CGFloat
    private let startTime: Double
    private let update: (CGFloat) -> Void
    private let completion: () -> Void
    private var completed = false
    
    public init(duration: Double, from fromValue: CGFloat, to toValue: CGFloat, update: @escaping (CGFloat) -> Void, completion: @escaping () -> Void) {
        self.duration = duration
        self.fromValue = fromValue
        self.toValue = toValue
        self.update = update
        self.completion = completion
        
        self.startTime = CACurrentMediaTime()
        
        self.displayLink = CADisplayLink(target: DisplayLinkTarget({ [weak self] in
            self?.tick()
        }), selector: #selector(DisplayLinkTarget.event))
        self.displayLink.isPaused = false
        self.displayLink.add(to: RunLoop.main, forMode: .common)
    }
    
    deinit {
        self.displayLink.isPaused = true
        self.displayLink.invalidate()
    }
    
    public func invalidate() {
        self.displayLink.isPaused = true
        self.displayLink.invalidate()
    }
    
    @objc private func tick() {
        if self.completed {
            return
        }
        let timestamp = CACurrentMediaTime()
        var t = (timestamp - self.startTime) / self.duration
        t = max(0.0, t)
        t = min(1.0, t)
        self.update(self.fromValue * CGFloat(1 - t) + self.toValue * CGFloat(t))
        if abs(t - 1.0) < Double.ulpOfOne {
            self.completed = true
            self.displayLink.isPaused = true
            self.completion()
        }
    }
}

public final class ConstantDisplayLinkAnimator {
    private var displayLink: CADisplayLink?
    private let update: () -> Void
    private var completed = false
    
    public var frameInterval: Int = 1 {
        didSet {
            self.updateDisplayLink()
        }
    }
    
    private func updateDisplayLink() {
        guard let displayLink = self.displayLink else {
            return
        }
        if self.frameInterval == 1 {
            if #available(iOS 15.0, *) {
                self.displayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: 120.0, preferred: 120.0)
            }
        } else {
            displayLink.preferredFramesPerSecond = 30
        }
    }
    
    public var isPaused: Bool = true {
        didSet {
            if self.isPaused != oldValue {
                if !self.isPaused && self.displayLink == nil {
                    let displayLink = CADisplayLink(target: DisplayLinkTarget({ [weak self] in
                        self?.tick()
                    }), selector: #selector(DisplayLinkTarget.event))
                    /*if #available(iOS 15.0, *) {
                        self.displayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: 120.0, preferred: 120.0)
                    }*/
                    displayLink.add(to: RunLoop.main, forMode: .common)
                    self.displayLink = displayLink
                    self.updateDisplayLink()
                }
                
                self.displayLink?.isPaused = self.isPaused
            }
        }
    }
    
    public init(update: @escaping () -> Void) {
        self.update = update
    }
    
    deinit {
        if let displayLink = self.displayLink {
            displayLink.isPaused = true
            displayLink.invalidate()
        }
    }
    
    public func invalidate() {
        if let displayLink = self.displayLink {
            displayLink.isPaused = true
            displayLink.invalidate()
        }
    }
    
    @objc private func tick() {
        if self.completed {
            return
        }
        self.update()
    }
}