//
//  ViewController.swift
//  HeadNavigationBroadcaster
//
//  Created by Sergii Kryvoblotskyi on 5/20/21.
//

import UIKit
import CoreMotion
import SceneKit
import Lottie
import AVFoundation

class ViewController: UIViewController {
    
    enum ViewStyle {
        
        case active, inactive, disabled
    }
    
    private let broadcaster = Broadcaster()
    
    @IBOutlet private weak var headView: UIView!
    @IBOutlet private weak var textView: UITextView!
    @IBOutlet private weak var actionButton: UIButton!
    @IBOutlet private weak var animationView: AnimationView!
    @IBOutlet private weak var headphonesLabel: UILabel!
    @IBOutlet private weak var headphonesImageView: UIImageView!
    @IBOutlet private weak var arrowView: AnimationView!
    @IBOutlet private weak var headphonesDescriptionLabel: UILabel!
    
    private var cubeNode: SCNNode!
    private var timer: Timer?
    private var headThreshold: Double = 0.4
    private var headThresholdCorrection: Double = 0.5
    private var isHandlingGesture: Bool = true
    private let manager = CMHeadphoneMotionManager()
    private let queue = OperationQueue()
    
    private var initialAnimationPlayed: Bool = false
    private var isInAnimation: Bool = false
    
    private var viewStyle: ViewStyle = .inactive
    
    private let workingQueue = DispatchQueue(label: "CMHeadphoneMotionManager.workingQueue")

    override func viewDidLoad() {
        super.viewDidLoad()
        observeHeadphones()
        arrowView.contentMode = .scaleAspectFill
        arrowView.loopMode = .loop
        
        setupView()
        try? AVAudioSession.sharedInstance().setCategory(.playAndRecord)
        updateView(style: self.currentState)
    }
        
    @IBAction func startMotionTrackingButtonClicked(_ sender: Any) {
        if manager.isDeviceMotionActive {
            manager.stopDeviceMotionUpdates()
            return
        }
        
        manager.delegate = self
        manager.startDeviceMotionUpdates(to: OperationQueue.current!) { [weak self] motion, error in
            guard let self = self, let motion = motion else { return }
            self.updateHead(with: motion)
            self.handleMotion(with: motion)
        }
    }
    
    override var preferredStatusBarStyle: UIStatusBarStyle {
        .lightContent
    }
}

extension ViewController {
    
    func handleMotion(with motion: CMDeviceMotion) {
        let pitch = motion.attitude.pitch
        let yaw = motion.attitude.yaw
        let roll = motion.attitude.roll
                
        let pitchHandled = pitch > (headThreshold * headThresholdCorrection) || pitch < -headThreshold
        let yawHandled = yaw > headThreshold || yaw < -headThreshold
        let rollHandled = roll > headThreshold || roll < -headThreshold
        
        if pitchHandled {
            if pitch > (headThreshold * headThresholdCorrection) {
                handleGesture(gesture: .up)
            } else if pitch < -headThreshold {
                handleGesture(gesture: .down)
            }
        } else if yawHandled {
            if yaw > headThreshold {
                handleGesture(gesture: .left)
            } else if yaw < -headThreshold {
                handleGesture(gesture: .right)
            }
        } else if rollHandled {
            if roll > headThreshold {
                handleGesture(gesture: .forward)
            } else if roll < -headThreshold {
                handleGesture(gesture: .back)
            }
        }
    }
}

extension ViewController {
    
    func handleGesture(gesture: HeadGesture) {
        guard isHandlingGesture else { return }
        print("handled: \(gesture)")
        self.broadcaster.broadcast(gesture: gesture)
        pring(message: gesture.rawValue)
        isHandlingGesture = false
        self.timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false, block: { [weak self] _ in
            self?.timer?.invalidate()
            self?.timer = nil
            self?.isHandlingGesture = true
        })
    }
    
    func pring(message: String) {
        let text = "Handled gesture: \(message)"
        let textVtext = textView.text ?? ""
        var lines = textVtext.components(separatedBy: "\n")
        lines.insert(text, at: 0)
        let resultText = lines.joined(separator: "\n")
        self.textView.text = resultText
    }
}

extension ViewController {
    
    func updateHead(with motion: CMDeviceMotion) {
        let data = motion.attitude
        if initialAnimationPlayed {
            cubeNode.eulerAngles = SCNVector3(-data.pitch, -data.yaw, -data.roll)
        } else {
            guard isInAnimation == false else {
                return
            }
            isInAnimation = true
            let action = SCNAction.rotateTo(x: CGFloat(-data.pitch), y: CGFloat(-data.yaw), z: CGFloat(-data.roll), duration: 0.4)
            cubeNode.runAction(action) {
                self.initialAnimationPlayed = true
                self.isInAnimation = false
            }
        }
    }
    
    func setupView() {
        animationView.contentMode = .scaleAspectFill
        
        let scene = SCNScene(named: "mode.dae")!
        let scnView = SCNView(frame: headView.bounds)
        scnView.backgroundColor = UIColor.clear
        headView.addSubview(scnView)
        scnView.scene = scene
        cubeNode = scene.rootNode
    }
    
    func updateView(style: ViewStyle) {
        self.viewStyle = style
        switch style {
        case .active:
            headphonesDescriptionLabel.isHidden = true
            arrowView.stop()
            arrowView.isHidden = true
            self.headphonesImageView.image = UIImage(named: "airpods")
            self.headphonesLabel.text = "Connected"
            self.actionButton.isEnabled = true
            self.actionButton.setTitle("TURN OFF", for: .normal)
            animationView.clipsToBounds = false
            self.animationView.play(toFrame: .init(60))
        case .inactive:
            headphonesDescriptionLabel.isHidden = true
            arrowView.stop()
            arrowView.isHidden = true
            self.headphonesImageView.image = UIImage(named: "airpods")
            self.headphonesLabel.text = "Connected"
            self.actionButton.isEnabled = true
            initialAnimationPlayed = false
            let action = SCNAction.rotateTo(x: 0.0, y: 0.0, z: 0.0, duration: 0.4)
            cubeNode.runAction(action)
            if animationView.currentFrame == 60 {
                self.animationView.play(fromFrame: 60, toFrame: 120, loopMode: nil, completion: nil)
            }
            self.actionButton.setTitle("TURN ON", for: .normal)
        case .disabled:
            headphonesDescriptionLabel.isHidden = false
            arrowView.play()
            arrowView.isHidden = false
            let action = SCNAction.rotateTo(x: 0.0, y: 0.0, z: 0.0, duration: 0.4)
            cubeNode.runAction(action)
            if animationView.currentFrame == 60 {
                self.animationView.play(fromFrame: 60, toFrame: 120, loopMode: nil, completion: nil)
            }
            self.headphonesImageView.image = UIImage(named: "airpodsDisabled")
            self.headphonesLabel.text = "Not connected"
            self.actionButton.isEnabled = false
        }
    }
}

class ButtonExt: UIButton {
    
    override var isEnabled: Bool {
        get { super.isEnabled }
        set {
            super.isEnabled = newValue
            self.alpha = isEnabled ? 1.0 : 0.5
            
        }
    }
}


extension ViewController: CMHeadphoneMotionManagerDelegate {
    
    var currentState: ViewStyle {
        if isHeadphonesConnected {
            return manager.isDeviceMotionActive ? .active : .inactive
        } else {
            return .disabled
        }
    }
    
    func headphoneMotionManagerDidConnect(_ manager: CMHeadphoneMotionManager) {
        updateView(style: currentState)
    }
    
    func headphoneMotionManagerDidDisconnect(_ manager: CMHeadphoneMotionManager) {
        updateView(style: currentState)
    }
}

extension ViewController {
    
    func observeHeadphones() {
         NotificationCenter.default.addObserver(self, selector: #selector(audioRouteChangeListener), name: AVAudioSession.routeChangeNotification, object: nil)
    }
    
    @objc func audioRouteChangeListener() {
        updateView(style: currentState)
    }
}

extension AVAudioSession {

    static var isHeadphonesConnected: Bool {
        return sharedInstance().isHeadphonesConnected
    }

    var isHeadphonesConnected: Bool {
        return !currentRoute.outputs.filter { $0.isHeadphones }.isEmpty
    }

}

extension ViewController {
    
    var isHeadphonesConnected: Bool {
        return AVAudioSession.sharedInstance().isHeadphonesConnected
    }
}

extension AVAudioSessionPortDescription {
    
    var isHeadphones: Bool {
        let outputs = AVAudioSession.sharedInstance().currentRoute.outputs
         for output in outputs{
            if output.portType == AVAudioSession.Port.bluetoothA2DP || output.portType == AVAudioSession.Port.bluetoothHFP || output.portType == AVAudioSession.Port.bluetoothLE{
             return true
           }
         }
         return false
    }
}
