import audio.AudioFilePlayer
import audio.AudioUtility
import audio.OpusStreamReceiver
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import somecodes.Codes.Companion.ValidString
import somecodes.configFile
import web.WsReply
import web.webApp
import zello.ZelloClient
import zello.ZelloEvent
import javafx.util.Pair
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.slf4j.LoggerFactory
import somecodes.Codes
import web.WsCommand
import java.util.function.BiFunction

//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
fun main() {
    val logger = LoggerFactory.getLogger("Main")
    val objectMapper = jacksonObjectMapper()

    val cfg = configFile()
    cfg.Load()
    var audioID = 0
    val preferedAudioDevice = "Speakers"

    AudioUtility.DetectPlaybackDevices().forEach { pair ->
        logger.info("Device ID: ${pair.first}, Name: ${pair.second}")
        if (pair.second.contains(preferedAudioDevice)) {
            audioID = pair.first
        }
    }
    if (audioID!=0){
        val initsuccess = AudioUtility.InitDevice(audioID,44100)
        logger.info("Audio Device $audioID initialized: $initsuccess")
    }

    // for Zello Client
    val o = OpusStreamReceiver(audioID)
    // for AudioFilePlayer
    var afp: AudioFilePlayer? = null

    /**
     * Creates a ZelloClient instance based on the configuration.
     */
    fun CreateZelloFromConfig(): ZelloClient {
        return when (cfg.ZelloServer) {
            "work" -> {
                ZelloClient.fromZelloWork(
                    cfg.ZelloUsername ?: "",
                    cfg.ZelloPassword ?: "",
                    cfg.ZelloChannel ?: "",
                    cfg.ZelloWorkNetworkName ?: ""
                )
            }
            "enterprise" -> {
                ZelloClient.fromZelloEnterpriseServer(
                    cfg.ZelloUsername ?: "",
                    cfg.ZelloPassword ?: "",
                    cfg.ZelloChannel ?: "",
                    cfg.ZelloEnterpriseServerDomain ?: ""
                )
            }
            else -> {
                ZelloClient.fromConsumerZello(cfg.ZelloUsername ?: "", cfg.ZelloPassword ?: "", cfg.ZelloChannel ?: "")
            }
        }
    }

    // Create Zello client from configuration and start it
    var z = CreateZelloFromConfig()

    // Create ZelloEvent implementation to handle events
    val z_event = object : ZelloEvent{
        override fun onChannelStatus(
            channel: String,
            status: String,
            userOnline: Int,
            error: String?,
            errorType: String?
        ) {
            logger.info("Channel Status: $channel is $status with $userOnline users online.")
            if (ValidString(error) && ValidString(errorType)) {
                logger.info("Error: $error, Type: $errorType")
            }
        }

        override fun onAudioData(streamID: Int, from: String, For: String, channel: String, data: ByteArray) {
            logger.info("Audio Data received from $from for $For on channel $channel with streamID $streamID ")
        }

        override fun onThumbnailImage(imageID: Int, from: String, For: String, channel: String, data: ByteArray, timestamp: Long) {
            logger.info("Thumbnail Image received from $from for $For on channel $channel with imageID $imageID at timestamp $timestamp")
        }

        override fun onFullImage(imageID: Int, from: String, For: String, channel: String, data: ByteArray, timestamp: Long) {
            logger.info("Full Image received from $from for $For on channel $channel with imageID $imageID at timestamp $timestamp")
        }

        override fun onTextMessage(messageID: Int, from: String, For: String, channel: String, text: String, timestamp: Long) {
            logger.info("Text Message received from $from for $For on channel $channel with messageID $messageID at timestamp $timestamp. Text: $text")
        }

        override fun onLocation(messageID: Int, from: String, For: String, channel: String, latitude: Double, longitude: Double, address: String, accuracy: Double, timestamp: Long) {
            logger.info("Location received from $from for $For on channel $channel with messageID $messageID at timestamp $timestamp. Location($latitude,$longitude), Address:$address, Accuracy:$accuracy")
        }

        override fun onConnected() {
            logger.info("Connected to Zello server.")
        }

        override fun onDisconnected(reason: String) {
            logger.info("Disconnected from Zello Server, reason: $reason")
            logger.info("Reconnecting after 10 seconds...")
            z.Stop()
            val e = this
            CoroutineScope(Dispatchers.Default).launch {

                delay(10000) // Wait for 10 seconds before trying to reconnect
                z = CreateZelloFromConfig()
                z.Start(e)
            }
        }

        override fun onError(errorMessage: String) {
            logger.info("Error occurred in Zello client: $errorMessage")
        }

        override fun onStartStreaming(from: String, For: String, channel: String) {
            // stop any previous playback
            afp?.Stop()
            afp = null

            if (o.Start()){
                logger.info("Opus Receiver ready for streaming from $from for $For on channel $channel")
            } else {
                logger.info("Failed to start Opus Receiver for streaming from $from for $For on channel $channel")
            }
        }

        override fun onStopStreaming(from: String, For: String, channel: String) {
            o.Stop()
            logger.info("Opus Receiver stopped streaming from $from for $For on channel $channel")
        }

        override fun onStreamingData(
            from: String,
            For: String,
            channel: String,
            data: ByteArray
        ) {
            if (o.isPlaying) o.PushData(data)
        }
    }
    z.Start(z_event)

    // Start the web application with WebSocket support
    val w = webApp("0.0.0.0",3030, BiFunction {
            source: String, cmd: WsCommand ->
        when (source) {
            "setting" -> when(cmd.command){
                "getConfig" ->{
                    val data = mapOf(
                        "zelloUsername" to cfg.ZelloUsername,
                        "zelloPassword" to cfg.ZelloPassword,
                        "zelloChannel" to cfg.ZelloChannel,
                        "zelloServer" to cfg.ZelloServer,
                        "zelloWorkNetworkName" to cfg.ZelloWorkNetworkName,
                        "zelloEnterpriseServerDomain" to cfg.ZelloEnterpriseServerDomain,
                        "m1" to cfg.M1,
                        "m2" to cfg.M2,
                        "m3" to cfg.M3,
                        "m4" to cfg.M4,
                        "m5" to cfg.M5,
                        "m6" to cfg.M6,
                        "m7" to cfg.M7,
                        "m8" to cfg.M8,
                        "messageList" to Codes.getAudioFiles()
                    )

                    WsReply(cmd.command, objectMapper.writeValueAsString(data).trim())
                }
                "setZelloConfig" -> {
                    try{
                        val xx = objectMapper.readValue(cmd.data, object: TypeReference<Map<String, String>>() {})
                        var changed = false
                        if (cfg.ZelloUsername != xx["ZelloUsername"]) {
                            cfg.ZelloUsername = xx["ZelloUsername"] ?: ""
                            changed = true
                        }
                        if (cfg.ZelloPassword != xx["ZelloPassword"]) {
                            cfg.ZelloPassword = xx["ZelloPassword"] ?: ""
                            changed = true
                        }
                        if (cfg.ZelloChannel != xx["ZelloChannel"]) {
                            cfg.ZelloChannel = xx["ZelloChannel"] ?: ""
                            changed = true
                        }
                        if (cfg.ZelloServer != xx["ZelloServer"]) {
                            cfg.ZelloServer = xx["ZelloServer"] ?: ""
                            changed = true
                        }
                        if (cfg.ZelloWorkNetworkName != xx["ZelloWorkNetworkName"]) {
                            cfg.ZelloWorkNetworkName = xx["ZelloWorkNetworkName"] ?: ""
                            changed = true
                        }
                        if (cfg.ZelloEnterpriseServerDomain != xx["ZelloEnterpriseServerDomain"]) {
                            cfg.ZelloEnterpriseServerDomain = xx["ZelloEnterpriseServerDomain"] ?: ""
                            changed = true
                        }
                        if (changed){
                            cfg.Save()
                            z.Stop()
                            z = CreateZelloFromConfig()
                            z.Start(z_event)

                            WsReply(cmd.command,"success")
                        } else WsReply(cmd.command,"No changes made")
                    } catch (e: Exception){
                        WsReply(cmd.command,"failed: ${e.message}")
                    }

                }
                "setMessageConfig"-> {
                    try{

                        val xx = objectMapper.readValue(cmd.data, object : TypeReference<Map<String, String>>() {})
                        var changed = false
                        if (cfg.M1 != xx["M1"]) {
                            cfg.M1 = xx["M1"] ?: ""
                            changed = true
                        }
                        if (cfg.M2 != xx["M2"]) {
                            cfg.M2 = xx["M2"] ?: ""
                            changed = true
                        }
                        if (cfg.M3 != xx["M3"]) {
                            cfg.M3 = xx["M3"] ?: ""
                            changed = true
                        }
                        if (cfg.M4 != xx["M4"]) {
                            cfg.M4 = xx["M4"] ?: ""
                            changed = true
                        }
                        if (cfg.M5 != xx["M5"]) {
                            cfg.M5 = xx["M5"] ?: ""
                            changed = true
                        }
                        if (cfg.M6 != xx["M6"]) {
                            cfg.M6 = xx["M6"] ?: ""
                            changed = true
                        }
                        if (cfg.M7 != xx["M7"]) {
                            cfg.M7 = xx["M7"] ?: ""
                            changed = true
                        }
                        if (cfg.M8 != xx["M8"]) {
                            cfg.M8 = xx["M8"] ?: ""
                            changed = true
                        }
                        if (changed){
                            cfg.Save()
                            WsReply(cmd.command,"success")
                        } else WsReply(cmd.command,"No changes made")
                    } catch (e: Exception){
                        WsReply(cmd.command,"failed: ${e.message}")
                    }

                }
                else -> WsReply(cmd.command,"Invalid command: ${cmd.command}")
            }

            "prerecordedbroadcast" -> when(cmd.command){
                "getMessageConfig" ->{
                    val data = mapOf(
                        "M1" to cfg.M1,
                        "M2" to cfg.M2,
                        "M3" to cfg.M3,
                        "M4" to cfg.M4,
                        "M5" to cfg.M5,
                        "M6" to cfg.M6,
                        "M7" to cfg.M7,
                        "M8" to cfg.M8
                    )
                    WsReply(cmd.command, objectMapper.writeValueAsString(data))
                }
                "getPlaybackStatus" ->{
                    if (afp!=null && true==afp?.isPlaying){
                        WsReply(cmd.command, "Playing: ${afp?.filename}, Duration: ${afp?.duration?.toInt()}, Elapsed: ${afp?.elapsed?.toInt()} seconds")
                    } else {
                        WsReply(cmd.command, "Idle")
                    }
                }
                "playMessage" ->{

                    afp?.Stop()
                    afp = null
                    // stop Opus Receiver if it is running
                    o.Stop()

                    val filename = when(cmd.data){
                        "M1" -> cfg.M1
                        "M2" -> cfg.M2
                        "M3" -> cfg.M3
                        "M4" -> cfg.M4
                        "M5" -> cfg.M5
                        "M6" -> cfg.M6
                        "M7" -> cfg.M7
                        "M8" -> cfg.M8
                        else -> {
                            null
                        }
                    }
                    if (filename!=null){
                        try{
                            afp= AudioFilePlayer(audioID, filename)
                            afp?.Play { _ -> afp = null}
                            WsReply(cmd.command,"success")

                        } catch (e: Exception){
                            afp?.Stop()
                            afp = null
                            WsReply(cmd.command, "failed: ${e.message}")
                        }
                    } else {
                        afp?.Stop()
                        afp = null
                        WsReply(cmd.command,"Invalid message name: ${cmd.data}")
                    }

                }
                "stopMessage" ->{
                    afp?.Stop()
                    afp = null
                    WsReply(cmd.command,"success")
                }
                else -> WsReply(cmd.command,"Invalid command: ${cmd.command}")
            }
            "pocreceiver" -> when(cmd.command){
                "getZelloStatus" -> {
                    var status = "Disconnected"
                    if (z.currentChannel?.isNotBlank() == true){
                        status = "Channel: ${z.currentChannel}, Online: ${z.isOnline}, Username: ${z.username}"
                        if (z.isReceivingStreaming){
                            status += ", Streaming From: ${z.receivingFrom}, Bytes Received: ${Codes.SizeToString(z.bytesReceived)}"
                        }
                    }
                    WsReply(cmd.command, status)
                }
                else -> WsReply(cmd.command,"Invalid command: ${cmd.command}")
            }
            else -> WsReply(cmd.command,"Invalid source: $source")
        }

    } , Pair("admin","admin1234"))
    w.Start()




}




