//
//  NutPlayerIosPlugin.swift
//  nut_player_ios
//
//  Created by Nut.Tech on 25.09.2023.
//

import Flutter
import UIKit
import NutPlayer

public class NutPlayerIosPlugin: NSObject, FlutterPlugin, NutPlayerViewFactoryDelegate, FlutterLogDestinationDelegate {
    
    private let logger = Logger(module: "")
    
    private let pluginChannel: FlutterMethodChannel
    private let registrar: FlutterPluginRegistrar
    
    private class PlayerRecord {
        let platform: PlayerFlutterPlatform
        var view: UIView?
        
        init(platform: PlayerFlutterPlatform, view: UIView? = nil) {
            self.platform = platform
            self.view = view
        }
    }
    
    private var players = [Int64: PlayerRecord]()
    private var logDestination: LogDestination?
    
    // MARK: - FlutterPlugin

    public static func register(with registrar: FlutterPluginRegistrar) {
        let instance = NutPlayerIosPlugin(with: registrar)
        registrar.publish(instance)
        
        let factory = NutPlayerViewFactory(with: registrar.messenger(), delegate: instance)
        registrar.register(factory, withId: "nut_player_view_id")
    }
    
    init(with registrar: FlutterPluginRegistrar) {
        
        self.registrar = registrar
        self.pluginChannel = FlutterMethodChannel(name: "tech.nut/plugin",
                                                 binaryMessenger: registrar.messenger())

        super.init()
        
        self.pluginChannel.setMethodCallHandler(self.pluginMethodHandler)
    }
    
    // MARK: - API


    // MARK: - FlutterMethodCallHandler
    
    private func pluginMethodHandler(call: FlutterMethodCall, result: @escaping FlutterResult) {

        let arguments = call.arguments as? [Any] ?? []
        
        // print("Method: \(call.method), arguments: \(call.arguments)")
        
        switch call.method {
        case "getPlayerVersion":
            self.playerCall(for: Int64(), method: "pluginVersion", arguments: ["version": NutPlayer.version ?? ""])
        case "pluginInitialize": 
            self.pluginInitialize(arguments, result: result)
        case "pluginDispose": 
            self.pluginDispose(arguments, result: result)
        case "pluginPlay": 
            self.pluginPlay(arguments, result: result)
        case "pluginPause": 
            self.pluginPause(arguments, result: result)
        case "pluginEnd":
            self.pluginEnd(arguments, result: result)
        case "pluginSetVolume": 
            self.pluginSetVolume(arguments, result: result)
        case "pluginGetVolume": 
            self.pluginGetVolume(arguments, result: result)
        case "pluginSetQuality":
            self.pluginSetQuality(arguments, result: result)
        case "pluginSetSubtitles":
            self.pluginSetSubtitles(arguments, result: result)
        case "pluginGetState": 
            self.pluginGetState(arguments, result: result)
        case "pluginSeek": 
            self.pluginSeek(arguments, result: result)
        case "pluginSetPlaybackSpeed": 
            self.pluginSetPlaybackSpeed(arguments, result: result)
        case "pluginCreate": 
            self.pluginCreate(arguments, result: result)
        case "pluginGetPosition": 
            self.pluginGetPosition(arguments, result: result)
        case "pluginSetLimitOutputLevel":
            self.pluginSetLog(arguments, result: result)
        default:
            result(FlutterMethodNotImplemented)
        }
    }
    
    // MARK: - NutPlayerViewFactoryDelegate
        
    func playerView(for playerId: Int64, view: UIView?) -> UIView? {
        guard let record = self.players[playerId] else { return nil }
        view >>- { record.view = $0 }
        return record.view
    }
    
    func playerInstance(for playerId: Int64) -> NutPlayer? {
        guard let record = self.players[playerId] else { return nil }
        return record.platform.nutPlayer
    }
    
    func playerParams(for playerId: Int64) -> Parameters? {
        guard let record = self.players[playerId] else { return nil }
        return record.platform.parameters
    }
    
    func playerCall(for playerId: Int64, method: String, arguments: Parameters?) {
        
        var arguments = arguments ?? [String: Any]()
        arguments["playerId"] = playerId
        
        DispatchQueue.main.async {
            self.pluginChannel.invokeMethod(method, arguments: arguments)
        }
    }
    
    // MARK: - FlutterLogDestinationDelegate
        
    func log(message: String) {
        DispatchQueue.main.async {
            self.pluginChannel.invokeMethod("pluginLogMessage", arguments: ["message": message])
        }
    }
    
    // MARK: - Private
    
    private func pluginInitialize(_ arguments: [Any], result: @escaping FlutterResult) {
        self.players
            .values
            .forEach { record in
                record.view?.removeFromSuperview()
                record.platform.dispose()
            }
        self.players.removeAll()
        result(nil)
    }
    
    private func pluginDispose(_ arguments: [Any], result: @escaping FlutterResult) {
        guard let playerId = arguments.first as? Int64,
              let record = self.players[playerId] else {
            result(FlutterMethodNotImplemented)
            return
        }
        
        self.players.removeValue(forKey: playerId)
        self.cleanLogDestinationIfNeeded()
        
        DispatchQueue.main.async {
            record.platform.dispose()
        }
        
        result(nil)
    }
    
    private func pluginPlay(_ arguments: [Any], result: @escaping FlutterResult) {
        guard let playerId = arguments.first as? Int64,
              let record = self.players[playerId] else {
            result(FlutterMethodNotImplemented)
            return
        }
        
        record.platform.play()
        result(nil)
    }
    
    private func pluginEnd(_ arguments: [Any], result: @escaping FlutterResult) {
        guard let playerId = arguments.first as? Int64,
              let record = self.players[playerId] else {
            result(FlutterMethodNotImplemented)
            return
        }
        
        record.platform.end()
        result(nil)
    }
    
    private func pluginPause(_ arguments: [Any], result: @escaping FlutterResult) {
        guard let playerId = arguments.first as? Int64,
              let record = self.players[playerId] else {
            result(FlutterMethodNotImplemented)
            return
        }
        
        record.platform.pause()
        result(nil)
    }
    
    private func pluginSetVolume(_ arguments: [Any], result: @escaping FlutterResult) {
        guard arguments.count == 2,
              let playerId = arguments.first as? Int64,
              let volume = arguments.last as? Double,
              let record = self.players[playerId] else {
            result(FlutterMethodNotImplemented)
            return
        }
        
        record.platform.change(volume: Float(volume))
        result(nil)
    }
    
    private func pluginSetQuality(_ arguments: [Any], result: @escaping FlutterResult) {
        guard arguments.count == 2,
              let playerId = arguments.first as? Int64,
              let qualityID = arguments.last as? String,
              let record = self.players[playerId] else {
            result(FlutterMethodNotImplemented)
            return
        }
        
        record.platform.change(quality: qualityID)
        result(nil)
    }
    
    private func pluginSetSubtitles(_ arguments: [Any], result: @escaping FlutterResult) {
        guard arguments.count == 2,
              let playerId = arguments.first as? Int64,
              let subtitleID = arguments.last as? String,
              let record = self.players[playerId] else {
            result(FlutterMethodNotImplemented)
            return
        }
        
        record.platform.change(subtitle: subtitleID)
        result(nil)
    }
    
    private func pluginSetLog(_ arguments: [Any], result: @escaping FlutterResult) {
        guard arguments.count == 1,
              let limitOutputLevel = arguments.first as? String else {
            result(FlutterMethodNotImplemented)
            return
        }
        
        var logLevel: LogLevel?
        
        switch limitOutputLevel {
        case "info":
            logLevel = .info;
        case "debug":
            logLevel = .debug
        default:
            logLevel = nil
        }
        
        if let logLevel {
            self.cleanLogDestinationIfNeeded()
            
            let logDestination = FlutterLogDestination(delegate: self, 
                                                       limitOutputLevel: logLevel, 
                                                       localization: "ru")
            
            self.logger.addDestination(logDestination)
            self.logDestination = logDestination
        }
        
        result(nil)
    }
    
    private func cleanLogDestinationIfNeeded() {
        if let logDestination {
            self.logger.removeDestination(logDestination)
            self.logDestination = nil
        }
    }
    
    private func pluginGetVolume(_ arguments: [Any], result: @escaping FlutterResult) {
    }
    
    private func pluginGetState(_ arguments: [Any], result: @escaping FlutterResult) {
    }
    
    private func pluginSeek(_ arguments: [Any], result: @escaping FlutterResult) {
        
        guard arguments.count == 2,
              let playerId = arguments.first as? Int64,
              let position = arguments.last as? Double,
              let record = self.players[playerId] else {
            result(FlutterMethodNotImplemented)
            return
        }
        
        record.platform.seek(time: position / 1000)
        
        DispatchQueue.main.async {
            result(nil)
        }
    }
    
    private func pluginSetPlaybackSpeed(_ arguments: [Any], result: @escaping FlutterResult) {
        
        guard arguments.count == 2,
              let playerId = arguments.first as? Int64,
              let speed = arguments.last as? Float,
              let record = self.players[playerId] else {
            result(FlutterMethodNotImplemented)
            return
        }
        
        record.platform.change(speed: speed)
        result(nil)
    }
    
    private func pluginCreate(_ arguments: [Any], result: @escaping FlutterResult) {

        guard let rawContent = arguments.first as? String else {
            result(FlutterMethodNotImplemented)
            return
        }
        
        let playerId = Int64.random(in: 1024...Int64.max - 2)
        let channel = FlutterEventChannel(name: "tech.nut/videoPlayer/videoEvents/\(playerId)",
                                          binaryMessenger: self.registrar.messenger())
        let platform = PlayerFlutterPlatform(rawContent,
                                           parameters: arguments[safe: 1] as? [String: Any],
                                           playerId: playerId,
                                           channel: channel)
        
        self.players[playerId] = PlayerRecord(platform: platform)

        result(playerId)
    }
    
    private func pluginGetPosition(_ arguments: [Any], result: @escaping FlutterResult) {
        
        guard let playerId = arguments.first as? Int64,
              let record = self.players[playerId] else {
            result(FlutterMethodNotImplemented)
            return
        }
        
        let currentTimeInMilliseconds = Int(record.platform.currentTime * 1000)
        result(currentTimeInMilliseconds)
    }

}
