import audio.AudioPlayer
import audio.ContentCache
import audio.TCPReceiver
import audio.UDPReceiver
import barix.BarixConnection
import barix.TCP_Barix_Command_Server
import codes.Somecodes
import com.sun.jna.Platform
import commandServer.TCP_Android_Command_Server
import content.Category
import content.Language
import content.VoiceType
import database.Log
import database.MariaDB
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import org.tinylog.Logger
import org.tinylog.provider.ProviderRegistry
import oshi.util.GlobalConfig
import web.WebApp
import java.nio.file.Files
import java.nio.file.Paths
import kotlin.concurrent.fixedRateTimer
import kotlin.io.path.absolutePathString

lateinit var db: MariaDB
lateinit var audioPlayer: AudioPlayer
val StreamerOutputs: MutableMap<String, BarixConnection> = HashMap()
lateinit var udpreceiver: UDPReceiver
lateinit var tcpreceiver: TCPReceiver
const val version = "0.0.8 (16/10/2025)"
// AAS 64 channels
const val max_channel = 64

// dipakai untuk pilih voice type, bisa diganti via web nanti
var selected_voice = VoiceType.VOICE_1.name

// dipakai untuk ambil messagebank berdasarkan id
val urutan_bahasa = listOf(
    Language.INDONESIA.name,
    Language.LOCAL.name,
    Language.ENGLISH.name,
    Language.CHINESE.name,
    Language.JAPANESE.name,
    Language.ARABIC.name
)


val contentCache = ContentCache()
/**
 * Create necessary folders if not exist
 */
fun folder_preparation(){
    // sementara diset begini, nanti pake config file
    Somecodes.Soundbank_directory = Paths.get("c:\\soundbank")
    Files.createDirectories(Somecodes.SoundbankResult_directory)
    Files.createDirectories(Somecodes.PagingResult_directory)
    Files.createDirectories(Somecodes.Soundbank_directory)
    Language.entries.forEach { language ->
        VoiceType.entries.forEach { voice ->
            Category.entries.forEach { category ->
                Files.createDirectories(Somecodes.SoundbankDirectory(language, voice, category) )
            }
        }
    }

}

/**
 * Extract necessary wav files from classpath to soundbank directory
 * and Load them
 */
fun files_preparation(){
    val list = listOf("chimeup.wav", "chimedown.wav", "silence1s.wav", "silencehalf.wav")
    list.forEach {
        Somecodes.ExtractFilesFromClassPath("/$it", Somecodes.Soundbank_directory)
        val pp = Somecodes.Soundbank_directory.resolve(it)
        if (Files.isRegularFile(pp)){
            val afi = audioPlayer.LoadAudioFile(pp.absolutePathString())
            if (afi.isValid()){
                Logger.info { "Common audio $it loaded from ${pp.toAbsolutePath()}" }
                val key = it.substring(0, it.length - 4) // buang .wav
                contentCache.addAudioFile(key, afi)
            } else {
                Logger.error { "Failed to load common audio $it from ${pp.toAbsolutePath()}" }
            }
        } else {
            Logger.error { "Common audio $it not found at ${pp.toAbsolutePath()}" }
        }
    }

}

// Application start here
fun main() {
    if (Platform.isWindows()) {
        // supaya OSHI bisa mendapatkan CPU usage di Windows seperti di Task Manager
        GlobalConfig.set(GlobalConfig.OSHI_OS_WINDOWS_CPU_UTILITY, true)
    }
    Logger.info { "Starting AAS New Generation version $version" }

    folder_preparation()

    audioPlayer = AudioPlayer(44100) // 44100 Hz sampling rate
    audioPlayer.InitAudio(1)

    files_preparation()

    db = MariaDB()

    val subcode01 = MainExtension01()

    // Coroutine untuk cek Paging Queue dan AAS Queue setiap detik
    CoroutineScope(Dispatchers.Default).launch {
        while (isActive) {
            delay(1000)
            // prioritas 1 , habisin queue paging
            subcode01.Read_Queue_Paging()
            // prioritas 2, habisin queue shalat
            subcode01.Read_Queue_Shalat()
            // prioritas 3, habisin queue timer
            subcode01.Read_Queue_Timer()
            // prioritas 4, habisin queue soundbank
            subcode01.Read_Queue_Soundbank()
        }
    }
    // Coroutine untuk cek Schedulebank tiap menit saat detik 00
    CoroutineScope(Dispatchers.Default).launch {
        while (isActive) {
            delay(1000)
            subcode01.Read_Schedule_Table()
        }
    }

    val web = WebApp(
        3030,
        listOf(
            Pair("admin", "password"),
            Pair("user", "password")
        ))
    web.Start()

    udpreceiver = UDPReceiver()
    if (udpreceiver.Start()) {
        Logger.info { "UDP Receiver started on port 5002" }
    } else {
        Logger.error { "Failed to start UDP Receiver on port 5002" }
    }

    tcpreceiver = TCPReceiver()
    if (tcpreceiver.Start()) {
        Logger.info { "TCP Receiver started on port 5002" }
    } else {
        Logger.error { "Failed to start TCP Receiver on port 5002" }
    }



    val androidserver = TCP_Android_Command_Server()
    androidserver.StartTcpServer(5003){
        Logger.info { it }
        db.logDB.Add(Log.NewLog("ANDROID", it))
    }

    val barixserver = TCP_Barix_Command_Server()
    barixserver.StartTcpServer { cmd ->
        val _tcp = barixserver.getSocket(cmd.ipaddress)
        val _streamer = StreamerOutputs[cmd.ipaddress]
        val _sc = db.soundchannelDB.List.find { it.ip == cmd.ipaddress }
        if (_streamer == null) {
            // belum create BarixConnection untuk ipaddress ini
            //Logger.info { "New Streamer Output connection from ${cmd.ipaddress}" }
            if (_sc != null) {

                val _bc = BarixConnection(_sc.index, _sc.channel, cmd.ipaddress)
                // cmd.vu 0 - 32767, kita convert ke 0 - 100
                _bc.vu = ((1.0 * cmd.vu / 32767.0)* 100.0).toInt()
                _bc.bufferRemain = cmd.buffremain
                _bc.statusData = cmd.statusdata
                _bc.commandsocket = _tcp

                StreamerOutputs[cmd.ipaddress] = _bc
                Logger.info { "Created new Streamer Output for channel ${_sc.channel} with IP ${cmd.ipaddress}" }
            }

        } else {
            // sudah ada, update data
            if (_sc != null && _sc.channel != _streamer.channel) {
                _streamer.channel = _sc.channel
            }
            // cmd.vu 0 - 32767, kita convert ke 0 - 100
            _streamer.vu = ((1.0 * cmd.vu / 32767.0)* 100.0).toInt()
            _streamer.bufferRemain = cmd.buffremain
            _streamer.statusData = cmd.statusdata

            // cek apakah koneksi TCP nya ganti
            if (_streamer.commandsocket == null) {
                _streamer.commandsocket = _tcp
            } else {
                if (_streamer.commandsocket != _tcp) {
                    // ganti koneksi
                    try {
                        _streamer.commandsocket?.close()
                    } catch (ex: Exception) {
                        Logger.error(ex) { "Error closing previous TCP command socket for ${cmd.ipaddress}" }
                    }
                    _streamer.commandsocket = _tcp
                }
            }
        }

    }

    val onlinechecker = fixedRateTimer(name = "onlinecheck", initialDelay = 1000, period = 1000) {
        // cek setiap 1 detik, decrement online counter semua BarixConnection
        StreamerOutputs.values.forEach {
            it.decrementOnlineCounter()
        }
    }

    db.Add_Log("AAS"," Application started")

    // shutdown hook
    Runtime.getRuntime().addShutdownHook(Thread {
        db.Add_Log("AAS"," Application stopping")
        Logger.info { "Shutdown hook called, stopping services..." }
        barixserver.StopTcpCommand()
        androidserver.StopTcpCommand()
        onlinechecker.cancel()
        web.Stop()
        udpreceiver.Stop()
        tcpreceiver.Stop()
        audioPlayer.Close()
        db.close()
        Logger.info { "All services stopped, exiting application." }
        ProviderRegistry.getLoggingProvider().shutdown()
    })


}
