import Flutter
import UIKit

public class SwiftAudioWaveformsPlugin: NSObject, FlutterPlugin {
    
    let audioRecorder: AudioRecorder
    var audioPlayers = [String: AudioPlayer]()
    var extractors = [String: WaveformExtractor]()
    var flutterChannel: FlutterMethodChannel
    
    init(registrar: FlutterPluginRegistrar, flutterChannel: FlutterMethodChannel) {
        self.flutterChannel = flutterChannel
        audioRecorder = AudioRecorder(channel: flutterChannel)
        super.init()
    }
    
    deinit {
        audioPlayers.removeAll()
        extractors.removeAll()
    }
    public static func register(with registrar: FlutterPluginRegistrar) {
        let channel = FlutterMethodChannel(name: Constants.methodChannelName, binaryMessenger: registrar.messenger())
        let instance = SwiftAudioWaveformsPlugin(registrar: registrar, flutterChannel: channel)
        registrar.addMethodCallDelegate(instance, channel: channel)
    }
    
    public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
        let args = call.arguments as? Dictionary<String, Any>
        switch call.method {
            case Constants.startRecording:
                guard let args = call.arguments as? Dictionary<String, Any> else {
                    result(FlutterError(code: Constants.audioWaveforms, message: "Invalid Arguments", details: nil))
                    return
                }
                audioRecorder.startRecording(result, RecordingSettings.fromJson((args)))
                break
            case Constants.pauseRecording:
                audioRecorder.pauseRecording(result)
                break
            case Constants.resumeRecording:
                audioRecorder.resumeRecording(result)
            case Constants.stopRecording:
                audioRecorder.stopRecording(result)
                break
            case Constants.getDecibel:
                audioRecorder.getDecibel(result)
                break
            case Constants.checkPermission:
                audioRecorder.checkHasPermission(result)
                break
            case Constants.preparePlayer:
                let key = args?[Constants.playerKey] as? String
                if(key != nil){
                    initPlayer(playerKey: key!)
                    audioPlayers[key!]?.preparePlayer(path: args?[Constants.path] as? String,
                                                      volume: args?[Constants.volume] as? Double,
                                                      updateFrequency: args?[Constants.updateFrequency] as? Int,
                                                      result: result,
                                                      overrideAudioSession: (args?[Constants.overrideAudioSession] as? Bool) ?? false)
                } else {
                    result(FlutterError(code: Constants.audioWaveforms, message: "Can not prepare player", details: "Player key is null"))
                }
                break
            case Constants.startPlayer:
                let key = args?[Constants.playerKey] as? String
                if(key != nil){
                    audioPlayers[key!]?.startPlyer(result: result)
                } else {
                    result(FlutterError(code: Constants.audioWaveforms, message: "Can not start player", details: "Player key is null"))
                }
                break
            case Constants.finishMode:
                let key = args?[Constants.playerKey] as? String
                let releaseType = args?[Constants.finishType] as? Int
                if(key != nil){
                    audioPlayers[key!]?.setFinishMode(result: result, releaseType: releaseType)
                }else{
                    result(FlutterError(code: Constants.audioWaveforms, message: "Can not set release mode", details: "Player key is null"))
                }
            case Constants.pausePlayer:
                let key = args?[Constants.playerKey] as? String
                if(key != nil){
                    audioPlayers[key!]?.pausePlayer()
                    result(true)
                } else {
                    result(FlutterError(code: Constants.audioWaveforms, message: "Can not pause player", details: "Player key is null"))
                }
                break
            case Constants.stopPlayer:
                let key = args?[Constants.playerKey] as? String
                if(key != nil){
                    audioPlayers[key!]?.stopPlayer()
                    result(true)
                } else {
                    result(FlutterError(code: Constants.audioWaveforms, message: "Can not stop player", details: "Player key is null"))
                }
                break
            case Constants.releasePlayer:
                let key = args?[Constants.playerKey] as? String
                if(key != nil){
                    audioPlayers[key!]?.release(result: result)
                }
                break;
            case Constants.seekTo:
                let key = args?[Constants.playerKey] as? String
                if(key != nil){
                    audioPlayers[key!]?.seekTo(args?[Constants.progress] as? Int,result)
                } else {
                    result(FlutterError(code: Constants.audioWaveforms, message: "Can not seek to postion", details: "Player key is null"))
                }
            case Constants.setVolume:
                let key = args?[Constants.playerKey] as? String
                if(key != nil){
                    audioPlayers[key!]?.setVolume(args?[Constants.volume] as? Double,result)
                } else {
                    result(FlutterError(code: Constants.audioWaveforms, message: "Can not set volume", details: "Player key is null"))
                }
            case Constants.setRate:
                let key = args?[Constants.playerKey] as? String
                if(key != nil){
                    audioPlayers[key!]?.setRate(args?[Constants.rate] as? Double,result)
                } else {
                    result(FlutterError(code: Constants.audioWaveforms, message: "Can not set rate", details: "Player key is null"))
                }
            case Constants.getDuration:
                let type = args?[Constants.durationType] as? Int
                let key = args?[Constants.playerKey] as? String
                if(key != nil){
                    do{
                        if(type == 0){
                            try audioPlayers[key!]?.getDuration(DurationType.Current,result)
                        } else {
                            try audioPlayers[key!]?.getDuration( DurationType.Max,result)
                        }
                    } catch{
                        result(FlutterError(code: "", message: "Failed to get duration", details: nil))
                    }
                } else {
                    result(FlutterError(code: Constants.audioWaveforms, message: "Can not get duration", details: "Player key is null"))
                }
            case Constants.stopAllPlayers:
                for (playerKey,_) in audioPlayers {
                    audioPlayers[playerKey]?.stopPlayer()
                    audioPlayers[playerKey] = nil
                }
                result(true)
            case Constants.extractWaveformData:
                guard let key = args?[Constants.playerKey] as? String else {
                    result(
                        FlutterError(
                            code: Constants.audioWaveforms,
                            message: "Can not get waveform data",
                            details: "Waveform key is null"
                        )
                    )
                    break
                }
                let path = args?[Constants.path] as? String
                let noOfSamples = args?[Constants.noOfSamples] as? Int
                createOrUpdateExtractor(
                    playerKey: key,
                    result: result,
                    path: path,
                    noOfSamples: noOfSamples
                )
            case Constants.stopExtraction:
                guard let key = args?[Constants.playerKey] as? String else {
                    result(FlutterError(code: Constants.audioWaveforms, message: "Can not get waveform data", details: "Waveform key is null"))
                    break
                }
                extractors[key]?.cancel()
                result(true)
            case Constants.pauseAllPlayers:
                for(playerKey,_) in audioPlayers {
                    audioPlayers[playerKey]?.pausePlayer()
                }
                result(true)
                break
            default:
                result(FlutterMethodNotImplemented)
                break
        }
    }
    
    
    func initPlayer(playerKey: String) {
        if audioPlayers[playerKey] == nil {
            let newPlayer = AudioPlayer(plugin: self,playerKey: playerKey,channel: flutterChannel)
            audioPlayers[playerKey] = newPlayer
        }
    }
    
    func createOrUpdateExtractor(playerKey: String, result: @escaping FlutterResult,path: String?, noOfSamples: Int?) {
        if(!(path ?? "").isEmpty) {
            do {
                let audioUrl = URL.init(string: path!)
                if(audioUrl == nil){
                    result(FlutterError(code: Constants.audioWaveforms, message: "Failed to initialise Url from provided audio file", details: "If path contains `file://` try removing it"))
                    return
                }
                extractors[playerKey]?.cancel()
                let newExtractor = try WaveformExtractor(url: audioUrl!, flutterResult: result, channel: flutterChannel)
                extractors[playerKey] = newExtractor
                Task {
                    await newExtractor
                        .extractWaveform(samplesPerPixel: noOfSamples, playerKey: playerKey,
                                         onExtractionComplete: { data in
                            result(data)
                        })
                }
            } catch {
                result(FlutterError(code: Constants.audioWaveforms, message: "Failed to decode audio file", details: nil))
            }
        } else {
            result(FlutterError(code: Constants.audioWaveforms, message: "Audio file path can't be empty or null", details: nil))
        }
    }
}

