import Foundation
import Capacitor
import AVFoundation
import BrightcovePlayerSDK

@available(iOS 12.0, *)
@objc(BrightcovePlayer)
public class BrightcovePlayer: CAPPlugin {
    private var setup: BrightcoveSetup?
    private var downloadService: DownloadService!
    private var mediaService: MediaService!
    
    private var brightcoveVideoPlayer: BrightcoveVideoPlayerViewController!
    private var brightcoveAudioPlayer: BrightcoveAudioPlayer!
    private let audioPlayer: AudioPlayer! = AudioPlayer()

    @objc override public func load() {
        print("Brightcove plugin: Load capacitor brightcove plugin")
        BCOVOfflineVideoManager.initializeOfflineVideoManager(with: DownloadService.shared, options: nil)
        NetworkService.initNetwork()
        self.downloadService = DownloadService()
        
        do {
            try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback, options: .allowAirPlay)
            try AVAudioSession.sharedInstance().setActive(true)
        } catch let error as NSError {
            print("Brightcove plugin: Error setting the AVAudioSession:", error.localizedDescription)
        }

        self.initDownloadEvents()
        self.initVideoEvents()
        self.initAudioEvents()
     }
    
    @objc func updateBrightcoveAccount(_ call: CAPPluginCall) {
        print("Brightcove plugin: updateBrightcoveAccount")
        do {
            guard let accountId = call.getString("accountId") else {
                throw CustomError(PluginError.MISSING_ACCOUNTID, "updateBrightcoveAccount: Missing account ID")
            }
            
            guard let policyKey = call.getString("policyKey") else {
                throw CustomError(PluginError.MISSING_POLICYKEY, "updateBrightcoveAccount: Missing policy key")
            }
            

            self.setup = BrightcoveSetup(accountId: accountId, policyKey: policyKey)
            self.brightcoveAudioPlayer = BrightcoveAudioPlayer(setup: self.setup!)
            self.downloadService.setSetup(setup: self.setup!)
            self.mediaService = MediaService(setup: self.setup!, downloadService: self.downloadService)

            call.resolve()
        } catch {
            self.rejectError(error, call)
        }
    }
    
    @objc func pauseVideo(_ call: CAPPluginCall) {
        print("Brightcove plugin: pauseVideo")
        do {
            if(self.brightcoveVideoPlayer != nil && self.brightcoveVideoPlayer.videoId != nil) {
                try self.brightcoveVideoPlayer.pauseVideo()
            }
            call.resolve()
        } catch {
            self.rejectError(error, call)
        }
    }
    
    @objc func closeVideo(_ call: CAPPluginCall) {
        print("Brightcove plugin: closeVideo")
        do {
            if(self.brightcoveVideoPlayer != nil && self.brightcoveVideoPlayer.videoId != nil) {
                DispatchQueue.main.sync {
                    self.brightcoveVideoPlayer.closeVideo()
                    call.resolve()
                }
            }
            call.resolve()
        } catch {
            self.rejectError(error, call)
        }
    }
    
    @objc func getMetadata(_ call: CAPPluginCall) {
        print("Brightcove plugin: getMetadata")
        do {
            call.resolve(["metadata": try self.mediaService.getMetadata(fileId: call.getString("fileId"))])
        } catch {
            self.rejectError(error, call)
        }
    }

    @objc func playVideo(_ call: CAPPluginCall) {
        let fileId: String? = call.getString("fileId")
        do {
            // Resume video
            if(fileId == nil) {
                if(self.brightcoveVideoPlayer != nil && self.brightcoveVideoPlayer.videoId != nil) {
                    try self.brightcoveVideoPlayer.resumeVideo()
                }
                return call.resolve()
            }
            
            // Play new video
            print("Brightcove plugin: playVideo")
            
            self.brightcoveAudioPlayer.destroy()
            self.brightcoveVideoPlayer?.destroy() // To avoid the possibility of having two videos running at the same time
            // instantiate a new player each time we play to prevent having to clean state of the existing one
            // (may change this at some point)
        
            try DispatchQueue.main.sync {
                var startPosition: Int = 0
                if(call.getInt("position") != nil) {
                    startPosition = call.getInt("position")!
                }

                self.brightcoveVideoPlayer = try BrightcoveVideoPlayerViewController(
                    setup: self.setup!,
                    startPosition: startPosition,
                    videoId : fileId,
                    local: call.getBool("local"),
                    subtitle: call.getString("subtitle"),
                    animatedText: call.getString("animatedText")
                )
                
                try self.brightcoveVideoPlayer.requestContentFromPlaybackService()
            
                self.bridge?.viewController?.present(self.brightcoveVideoPlayer!, animated: false, completion: {
                    call.resolve(["value": "BrightCove player initialized"])
                });
            }
        } catch {
            self.brightcoveVideoPlayer?.destroy()
            self.rejectError(error, call)
        }
    }
    
    @objc func isMediaAvailableLocally(_ call: CAPPluginCall) {
        print("Brightcove plugin: isMediaAvailableLocally")
        do {
            call.resolve(["value": try self.downloadService.isMediaAvailableLocally(fileId: call.getString("fileId"))])
        } catch {
            self.rejectError(error, call)
        }
    }
    
    @objc func downloadMedia(_ call: CAPPluginCall) {
        print("Brightcove plugin: downloadMedia")
        do {
            try self.downloadService.download(fileId: call.getString("fileId"))
            call.resolve()
        } catch {
            self.rejectError(error, call)
        }
    }

    @objc func playInternalAudio(_ call: CAPPluginCall) {
        print("Brightcove plugin: playInternalAudio")
        do {
            try self.audioPlayer.playAudio(file : call.getString("file"))
             call.resolve()
        } catch {
            self.rejectError(error, call)
        }
    }
    
    @objc func deleteAllDownloadedMedias(_ call: CAPPluginCall) {
        print("Brightcove plugin: deleteAllDownloadedMedias")
        do {
            try self.downloadService.deleteAllDownloadedMedias()
            call.resolve()
        } catch {
            self.rejectError(error, call)
        }
    }
    
    @objc func deleteDownloadedMedia(_ call: CAPPluginCall) {
        print("Brightcove plugin: deleteDownloadedMedia")
        do {
            try self.downloadService.deleteDownloadedMedia(fileId: call.getString("fileId"))
            call.resolve()
        } catch {
            self.rejectError(error, call)
        }
    }
    
    @objc func getDownloadedMediasState(_ call: CAPPluginCall) {
        print("Brightcove plugin: getDownloadedMediasState")
        do {
            call.resolve(["medias": try self.downloadService.getDownloadedMediasState()])
        } catch {
            self.rejectError(error, call)
        }
    }
    
    @objc func setDownloadNotifications(_ call: CAPPluginCall) {
        call.unavailable()
    }
    
    @objc func loadAudio(_ call: CAPPluginCall) {
        print("Brightcove plugin: loadAudio")
        do {
            try self.brightcoveAudioPlayer.load(fileId: call.getString("fileId"), token: call.getString("token"), local: call.getBool("local"), defaultPosterUrl: call.getString("defaultPosterUrl"))
            call.resolve()
        } catch {
            self.rejectError(error, call)
        }
    }
    
    @objc func destroyAudioPlayer(_ call: CAPPluginCall) {
        print("Brightcove plugin: destroyAudioPlayer")
        self.brightcoveAudioPlayer.destroy()
        call.resolve()
    }
    
    @objc func stopAudio(_ call: CAPPluginCall) {
        print("Brightcove plugin: stopAudio")
        do {
            try self.brightcoveAudioPlayer.stop()
            call.resolve()
        } catch {
            self.rejectError(error, call)
        }
    }
    
    @objc func pauseAudio(_ call: CAPPluginCall) {
        print("Brightcove plugin: pauseAudio")
        do {
            try self.brightcoveAudioPlayer.pause()
            call.resolve()
        } catch {
            self.rejectError(error, call)
        }
    }
    
    @objc func playAudio(_ call: CAPPluginCall) {
        print("Brightcove plugin: playAudio")
        do {
            try self.brightcoveAudioPlayer.play()
            call.resolve()
        } catch {
            self.rejectError(error, call)
        }
    }
    
    @objc func backwardAudio(_ call: CAPPluginCall) {
        print("Brightcove plugin: backwardAudio")
        do {
            try self.brightcoveAudioPlayer.backward(millis: call.getDouble("amount"))
            call.resolve()
        } catch {
            self.rejectError(error, call)
        }
    }
    
    @objc func forwardAudio(_ call: CAPPluginCall) {
        print("Brightcove plugin: forwardAudio")
        do {
            try self.brightcoveAudioPlayer.forward(millis: call.getDouble("amount"))
            call.resolve()
        } catch {
            self.rejectError(error, call)
        }
    }
    
    @objc func seekToAudio(_ call: CAPPluginCall) {
        print("Brightcove plugin: seekToAudio")
        do {
            try self.brightcoveAudioPlayer.seekTo(position: call.getDouble("position"))
            call.resolve()
        } catch {
            self.rejectError(error, call)
        }
    }

    @objc func enableAudioLooping(_ call: CAPPluginCall) {
        print("Brightcove plugin: enableAudioLooping")
        do {
            try self.brightcoveAudioPlayer.enableAudioLooping(time: call.getDouble("time"))
            call.resolve()
        } catch {
            self.rejectError(error, call)
        }
    }
    
    @objc func disableAudioLooping(_ call: CAPPluginCall) {
        print("Brightcove plugin: disableAudioLooping")
        do {
            try self.brightcoveAudioPlayer.toggleLooping(enabled: false)
            call.resolve()
        } catch {
            self.rejectError(error, call)
        }
    }

    @objc func isAudioLooping(_ call: CAPPluginCall) {
        print("Brightcove plugin: isAudioLooping")
        do {
            call.resolve(["value": try self.brightcoveAudioPlayer.isLooping()])
        } catch {
            self.rejectError(error, call)
        }
    }
    
    @objc func setAudioNotificationOptions(_ call: CAPPluginCall) {
        print("Brightcove plugin: setAudioNotificationOptions")
        do {
            try self.brightcoveAudioPlayer.setLockScreenIntervals(forwardMillis: call.getInt("forwardIncrementMs"), backwardMillis: call.getInt("rewindIncrementMs"))
            call.resolve()
        } catch {
            self.rejectError(error, call)
        }
    }
    
    @objc func getAudioPlayerState(_ call: CAPPluginCall) {
        print("Brightcove plugin: getAudioPlayerState")
        do {
            let json = try JSONSerialization.jsonObject(with: self.brightcoveAudioPlayer.getPlayerState().jsonData1(), options: [])
            guard let dictionary = json as? [String : Any] else {
                return
            }
            call.resolve(dictionary)
        } catch {
            self.rejectError(error, call)
        }
    }
    
    @objc private func downLoadStateHandler(notification: Notification) {
        print("Brightcove plugin: downLoadStateHandler")
        if let data = notification.userInfo as? [String: Any] {
            self.notifyListeners("downloadStateChange", data: data)
        }
    }

    @objc private func videoPositionChangeHandler(notification: Notification) {
        print("Brightcove plugin: videoPositionChangeHandler event")
        self.notifyListeners("videoPositionChange", data: [
            "currentMillis": notification.userInfo!["currentMillis"]!,
            "totalMillis": notification.userInfo!["totalMillis"]!
        ])
    }

    @objc private func beforeCloseVideoHandler(notification: Notification) {
        print("Brightcove plugin: beforeCloseVideoHandler event")
        self.notifyListeners("beforeCloseVideo", data: [:])
    }
    
    @objc private func videoClosedHandler(notification: Notification) {
        print("Brightcove plugin: videoClosedHandler event")
        self.brightcoveVideoPlayer.destroy()
        
        self.notifyListeners("closeVideo", data: [
            "completed": notification.userInfo!["completed"]!,
            "currentMillis": notification.userInfo!["currentMillis"]!,
            "totalMillis": notification.userInfo!["totalMillis"]!,
            "subtitle": notification.userInfo!["subtitle"]!
        ])
    }
    
    @objc private func audioStateChange(notification: Notification) {
        print("Brightcove plugin: audioStateChange event")
        if let data = notification.userInfo as? [String: String] {
            self.notifyListeners("audioStateChange", data: ["state": String(describing: data["state"]!)])
        }
    }
    
    @objc private func audioPositionChange(notification: Notification) {
        print("Brightcove plugin: audioPositionChange event")
        if let data = notification.userInfo as? [String: Any] {
            self.notifyListeners("audioPositionChange", data: [
                "currentMillis": data["currentMillis"]!,
                "totalMillis": data["totalMillis"]!,
                "remainingTime": data["remainingTime"]!
            ])
        }
    }
    
    @objc private func audioError(notification: Notification) {
        print("Brightcove plugin: audioError event")
        if let data = notification.userInfo as? [String: String] {
            self.notifyListeners("audioError", data: [
                "errorMessage": data["errorMessage"]!
            ])
        }
    }

    private func initDownloadEvents() {
        NotificationCenter.default.addObserver(self, selector: #selector(self.downLoadStateHandler(notification:)), name: Notification.Name("downloadStateChange"), object: nil)
    }
    
    private func initVideoEvents() {
        NotificationCenter.default.addObserver(self, selector: #selector(self.beforeCloseVideoHandler(notification:)), name: Notification.Name("beforeVideoClose"), object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.videoClosedHandler(notification:)), name: Notification.Name("videoClosed"), object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.videoPositionChangeHandler(notification:)), name: Notification.Name("videoPositionChange"), object: nil)
    }
    
    private func initAudioEvents() {
        NotificationCenter.default.addObserver(self, selector: #selector(self.audioStateChange(notification:)), name: Notification.Name("audioStateChange"), object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.audioPositionChange(notification:)), name: Notification.Name("audioPositionChange"), object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.audioError(notification:)), name: Notification.Name("audioError"), object: nil)
    }
    
    /**
        We check if the error is a standard error (type CustomError) of the plugin or a native error of swift (type Error)
     */
    private func rejectError(_ error: Any, _ call: CAPPluginCall) {
        if(error is CustomError) {
            call.reject(
                (error as! CustomError).message,
                (error as! CustomError).code.rawValue,
                (error as! CustomError).error
            )
        } else {
            print(error)
            call.reject(
                String(describing: error),
                PluginError.TECHNICAL_ERROR.rawValue,
                error as? Error
            )
        }
    }
}
