import Foundation
import UIKit
import Display
import AsyncDisplayKit
import RLottieBinding
import AppBundle
import GZip
import SwiftSignalKit
import ManagedAnimationNode

enum ManagedMonkeyAnimationIdle: CaseIterable {
    case blink
    case ear
    case still
}

enum ManagedMonkeyAnimationState: Equatable {
    case idle(ManagedMonkeyAnimationIdle)
    case eyesClosed
    case peeking
    case tracking(CGFloat)
}

final class ManagedMonkeyAnimationNode: ManagedAnimationNode {
    private var monkeyState: ManagedMonkeyAnimationState = .idle(.blink)
    private var timer: SwiftSignalKit.Timer?
    
    init() {
        super.init(size: CGSize(width: 136.0, height: 136.0))
        
        self.trackTo(item: ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyIdle"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.3))
    }
    
    deinit {
        self.timer?.invalidate()
    }
    
    private func startIdleTimer() {
        self.timer?.invalidate()
        let timer = SwiftSignalKit.Timer(timeout: Double.random(in: 1.0 ..< 1.5), repeat: false, completion: { [weak self] in
            guard let strongSelf = self else {
                return
            }
            switch strongSelf.monkeyState {
            case .idle:
                if let idle = ManagedMonkeyAnimationIdle.allCases.randomElement() {
                    strongSelf.setState(.idle(idle))
                }
            default:
                break
            }
        }, queue: .mainQueue())
        self.timer = timer
        timer.start()
    }
    
    override func advanceState() {
        super.advanceState()
        
        self.timer?.invalidate()
        self.timer = nil
        
        if self.trackStack.isEmpty, case .idle = self.monkeyState {
            self.startIdleTimer()
        }
    }
    
    private func enqueueIdle(_ idle: ManagedMonkeyAnimationIdle) {
        switch idle {
        case .still:
            self.trackTo(item: ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyIdle"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.3))
        case .blink:
            self.trackTo(item: ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyIdle1"), frames: .range(startFrame: 0, endFrame: 30), duration: 0.3))
        case .ear:
            self.trackTo(item: ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyIdle2"), frames: .range(startFrame: 0, endFrame: 30), duration: 0.3))
            //self.trackTo(item: ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyIdle"), frames: .range(startFrame: 0, endFrame: 179), duration: 3.0))
        }
    }
    
    func setState(_ monkeyState: ManagedMonkeyAnimationState) {
        let previousState = self.monkeyState
        self.monkeyState = monkeyState
        
        self.timer?.invalidate()
        self.timer = nil
        
        func enqueueTracking(_ value: CGFloat) {
            let lowerBound = 18
            let upperBound = 160
            let frameIndex = lowerBound + Int(value * CGFloat(upperBound - lowerBound))
            if let state = self.state, state.item.source == .local("TwoFactorSetupMonkeyTracking") {
                let item = ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyTracking"), frames: .range(startFrame: state.frameIndex ?? 0, endFrame: frameIndex), duration: 0.3)
                self.state = ManagedAnimationState(displaySize: self.intrinsicSize, item: item, current: state)
                self.didTryAdvancingState = false
                self.updateAnimation()
            } else {
                self.trackStack = self.trackStack.filter {
                    $0.source != .local("TwoFactorSetupMonkeyTracking")
                }
                self.trackTo(item: ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyTracking"), frames: .range(startFrame: 0, endFrame: frameIndex), duration: 0.3))
            }
        }
        
        func enqueueClearTracking() {
            if let state = self.state, state.item.source == .local("TwoFactorSetupMonkeyTracking") {
                let item = ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyTracking"), frames: .range(startFrame: state.frameIndex ?? 0, endFrame: 0), duration: 0.3)
                self.state = ManagedAnimationState(displaySize: self.intrinsicSize, item: item, current: state)
                self.didTryAdvancingState = false
                self.updateAnimation()
            }
        }
        
        switch previousState {
        case .idle:
            switch monkeyState {
            case let .idle(idle):
                self.enqueueIdle(idle)
            case .eyesClosed:
                self.trackTo(item: ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyClose"), frames: .range(startFrame: 0, endFrame: 41), duration: 0.3))
            case .peeking:
                self.trackTo(item: ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyCloseAndPeek"), frames: .range(startFrame: 0, endFrame: 41), duration: 0.3))
            case let .tracking(value):
                enqueueTracking(value)
            }
        case .eyesClosed:
            switch monkeyState {
            case let .idle(idle):
                self.trackTo(item: ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyClose"), frames: .range(startFrame: 41, endFrame: 0), duration: 0.3))
                self.enqueueIdle(idle)
            case .eyesClosed:
                break
            case .peeking:
                self.trackTo(item: ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyPeek"), frames: .range(startFrame: 0, endFrame: 14), duration: 0.3))
            case let .tracking(value):
                self.trackTo(item: ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyClose"), frames: .range(startFrame: 41, endFrame: 0), duration: 0.3))
                enqueueTracking(value)
            }
        case .peeking:
            switch monkeyState {
            case let .idle(idle):
                self.trackTo(item: ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyCloseAndPeek"), frames: .range(startFrame: 41, endFrame: 0), duration: 0.3))
                self.enqueueIdle(idle)
            case .eyesClosed:
                self.trackTo(item: ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyPeek"), frames: .range(startFrame: 14, endFrame: 0), duration: 0.3))
            case .peeking:
                break
            case let .tracking(value):
                self.trackTo(item: ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyCloseAndPeek"), frames: .range(startFrame: 41, endFrame: 0), duration: 0.3))
                enqueueTracking(value)
            }
        case let .tracking(currentValue):
            switch monkeyState {
            case let .idle(idle):
                enqueueClearTracking()
                self.trackTo(item: ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyIdle"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.3))
                self.enqueueIdle(idle)
            case .eyesClosed:
                enqueueClearTracking()
                self.trackTo(item: ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyClose"), frames: .range(startFrame: 0, endFrame: 41), duration: 0.3))
            case .peeking:
                enqueueClearTracking()
                self.trackTo(item: ManagedAnimationItem(source: .local("TwoFactorSetupMonkeyCloseAndPeek"), frames: .range(startFrame: 0, endFrame: 41), duration: 0.3))
            case let .tracking(value):
                if abs(currentValue - value) > CGFloat.ulpOfOne {
                    enqueueTracking(value)
                }
            }
        }
    }
}