//
//  VideoDataManager.swift
//  MVMCoreUI
//
//  Created by Scott Pfeil on 1/26/21.
//  Copyright © 2021 Verizon Wireless. All rights reserved.
//

import Foundation
import AVFoundation

@objcMembers open class VideoDataManager: NSObject {
    
    /// The state of the video.
    @objc public enum VideoState: Int {
        case none
        case loading
        case loaded
        case failed
    }
    
    public let videoURLString: String
    public var player: AVPlayer?
    
    // Thread Safe video state handling.
    private var _videoState = VideoState.none
    private let videoStatueQueue = DispatchQueue(label: "com.vzw.mvmcoreui.videoDataManager.state", attributes: .concurrent)

    /// The state of the video. Use KVO to listen for state changes.
    @objc public var videoState: VideoState {
        get {
            var state = VideoState.none
            videoStatueQueue.sync {
                state = _videoState
            }
            return state
        }
        set {
            willChangeValue(for: \.videoState)
            videoStatueQueue.async(flags: .barrier) {
                self._videoState = newValue
            }
            didChangeValue(for: \.videoState)
        }
    }
    
    /// Set when the state is set to failed. Follows the same pattern as apple's AVPlayerItem
    public var loadFailedError: MVMCoreErrorObject?
    
    private var kvoToken: NSKeyValueObservation?
    
    private var memoryWarningListener: Any?
    
    public init(with videoURLString: String) {
        self.videoURLString = videoURLString
        super.init()
        self.addMemoryWarningListener()
    }
    
    public func loadVideo() {
        guard videoState != .loading else { return }
        removeVideoObserver()
        player = nil
        videoState = .loading

        //Asset loading needs time, calling async method. by tracking asset's propety "duration", if we get the value of duration, we can treat asset load successfully.
        let tracksKey = "duration"
        MVMCoreCache.shared()?.playerAsset(fromFileName: videoURLString, trackKeys: [tracksKey], onComplete: { [weak self] (asset, fileName, errorObject) in
            guard let asset = asset else {
                self?.loadFailedError = errorObject
                self?.videoState = .failed
                return
            }
            
            var error: NSError? = nil
            let tracksStatus = asset.statusOfValue(forKey: tracksKey, error: &error)
            switch tracksStatus {
            case .loaded:
                //When Assets load successfully, we create playerItem and add playerItem into AVPlayer
                self?.player = AVPlayer(playerItem: AVPlayerItem(asset: asset))
                self?.addObserverToPlayerItem()
            case .failed:
                //Asset load fail
                //Since checking asset status here, no need to check player.currenItem.asset's media tracks when play button is clicked anymore.
                if let error = error,
                   let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: (asset as? AVURLAsset)?.url.absoluteString ?? self?.videoURLString) {
                    self?.loadFailedError = errorObject
                }
                self?.videoState = .failed
            default:
                break
            }
        })
    }
    
    private func addObserverToPlayerItem() {
        removeVideoObserver()

        // To know when the video player item is done loading.
        guard kvoToken == nil else { return }
        kvoToken = player?.currentItem?.observe(\.status) { [weak self] (item, change) in
            guard item == self?.player?.currentItem else { return }
            switch item.status {
            case .readyToPlay:
                self?.videoState = .loaded
            case .failed:
                if let error = item.error,
                   let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: (item.asset as? AVURLAsset)?.url.absoluteString ?? self?.videoURLString) {
                    self?.loadFailedError = errorObject
                }
                self?.videoState = .failed
            default:
                break
            }
        }
    }

    private func removeVideoObserver() {
        kvoToken?.invalidate()
        kvoToken = nil
    }
    
    private func addMemoryWarningListener() {
        memoryWarningListener = NotificationCenter.default.addObserver(forName: UIApplication.didReceiveMemoryWarningNotification, object: nil, queue: OperationQueue.main) { [weak self] (notification) in
            self?.removeVideoObserver()
            self?.player = nil
            self?.videoState = .none
        }
    }
    
    private func removeMemoryWarningListener() {
        guard let observer = memoryWarningListener else { return }
        NotificationCenter.default.removeObserver(observer)
        memoryWarningListener = nil
    }
    
    deinit {
        removeVideoObserver()
        removeMemoryWarningListener()
    }
}
