package web

import StreamerOutputs
import codes.Somecodes
import codes.Somecodes.Companion.ListAudioFiles
import codes.Somecodes.Companion.ValiDateForLogHtml
import codes.Somecodes.Companion.ValidFile
import codes.Somecodes.Companion.ValidIPV4
import codes.Somecodes.Companion.ValidScheduleDay
import codes.Somecodes.Companion.ValidScheduleTime
import codes.Somecodes.Companion.ValidString
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import content.Category
import content.Language
import content.ScheduleDay
import content.VoiceType
import database.BroadcastZones
import database.LanguageLink
import database.MariaDB
import database.Messagebank
import database.ScheduleBank
import database.SoundChannel
import database.Soundbank
import database.UserDB
//>>>>>>>>> Temporary merge branch 2
import db
import io.javalin.Javalin
import io.javalin.apibuilder.ApiBuilder.before
import io.javalin.apibuilder.ApiBuilder.delete
import io.javalin.apibuilder.ApiBuilder.get
import io.javalin.apibuilder.ApiBuilder.patch
import io.javalin.apibuilder.ApiBuilder.path
import io.javalin.apibuilder.ApiBuilder.post
import io.javalin.apibuilder.ApiBuilder.ws
import io.javalin.http.Context
import io.javalin.json.JavalinJackson
import io.javalin.websocket.WsMessageContext
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import java.nio.file.Files
import java.time.LocalDateTime


@Suppress("unused")
class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {

    var app: Javalin? = null
    val objectmapper = jacksonObjectMapper()

    private fun SendReply(context: WsMessageContext, command: String, value: String) {
        try {
            if (context.session.isOpen) {
                context.send(objectmapper.writeValueAsString(WebsocketReply(command, value)))
            }
        } catch (_: Exception) {
        }
    }

    fun Start() {

        app = Javalin.create { config ->
            config.useVirtualThreads = true
            config.staticFiles.add("/webpage")
            config.jsonMapper(JavalinJackson(jacksonObjectMapper()))
            config.router.apiBuilder {
                path("/") {
                    get { ctx ->
                        // Serve the main page
                        ctx.sessionAttribute("user", null) // Clear user session
                        ctx.redirect("login.html")
                    }

                }
                path("login.html") {
                    post { it ->
                        // get username and password from form
                        val username = it.formParam("username")
                        val password = it.formParam("password")
                        if (username == null || password == null) {
                            it.status(400).result(objectmapper.writeValueAsString(resultMessage("Username and password are required")))
                            return@post
                        }
                        // Check if user exists in userlist
                        val user = userlist.find { it.first == username && it.second == password }
                        if (user == null) {
                            it.status(401).result(objectmapper.writeValueAsString(resultMessage("Invalid username or password")))
                            return@post
                        }
                        // Set user session
                        it.sessionAttribute("user", user.first)
                        //println("User ${user.first} logged in")
                        // Redirect to home page
                        it.redirect("home.html")
                    }
                }

                path("home.html") {
                    before { CheckUsers(it) }
                    ws("/ws") { ws ->
                        // WebSocket endpoint for home
//
                        ws.onMessage { wsMessageContext ->
                            try {
                                val cmd =
                                    objectmapper.readValue(wsMessageContext.message(), WebsocketCommand::class.java)
                                when (cmd.command) {
                                    "getSystemTime" -> {
                                        SendReply(
                                            wsMessageContext,
                                            cmd.command,
                                            LocalDateTime.now().format(Somecodes.datetimeformat1)
                                        )
                                    }

                                    "getCPUStatus" -> {
                                        Somecodes.getCPUUsage { vv ->
                                            SendReply(wsMessageContext, cmd.command, vv)
                                        }
                                    }

                                    "getMemoryStatus" -> {
                                        SendReply(wsMessageContext, cmd.command, Somecodes.getMemoryUsage())
                                    }

                                    "getDiskStatus" -> {
                                        SendReply(wsMessageContext, cmd.command, Somecodes.getDiskUsage())
                                    }

                                    "getNetworkStatus" -> {
                                        Somecodes.GetNetworkStatus { nn ->
                                            SendReply(wsMessageContext, cmd.command, objectmapper.writeValueAsString(nn))
                                        }
                                    }

                                    "getPagingQueue" ->{
                                        SendReply(wsMessageContext, cmd.command, objectmapper.writeValueAsString(db.queuepagingDB.List))
                                    }

                                    "getAASQueue" ->{
                                        SendReply(wsMessageContext, cmd.command, objectmapper.writeValueAsString(db.queuetableDB.List))
                                    }

                                    "getStreamerOutputs" -> {
                                        val reply : List<StreamerOutputData> = StreamerOutputs.map { so -> StreamerOutputData.fromBarixConnection(so.value) }
                                        SendReply(wsMessageContext, cmd.command, objectmapper.writeValueAsString(reply))
                                    }

                                    else -> {
                                        SendReply(wsMessageContext, cmd.command, "Unknown command")
                                    }
                                }
                            } catch (e: Exception) {
                                println("Error processing WebSocket message: ${e.message}")
                            }

                        }
//                        ws.onConnect { wsConnectContext ->
//                            println("WebSocket connected: ${wsConnectContext.session.remoteAddress}")
//                        }

                    }
                }
                path("soundbank.html") {
                    before { CheckUsers(it) }
                }
                path("messagebank.html") {
                    before { CheckUsers(it) }
                }
                path("language.html") {
                    before { CheckUsers(it) }
                }
                path("log.html") {
                    before { CheckUsers(it) }
                }
                path("setting.html") {
                    before { CheckUsers(it) }
                }
                path("timer.html") {
                    before { CheckUsers(it) }
                }
                path("api") {
                    path("VoiceType"){
                        get{
                            it.result(objectmapper.writeValueAsString(VoiceType.entries.map { vt -> vt.name }))
                        }
                    }
                    path("Category") {
                        get {
                            it.result(objectmapper.writeValueAsString(Category.entries.map { cat -> cat.name}) )
                        }
                    }
                    path("Language") {
                        get {
                            it.result(objectmapper.writeValueAsString(Language.entries.map { lang -> lang.name }) )
                        }
                    }
                    path("ScheduleDay") {
                        get {
                            it.result(objectmapper.writeValueAsString(ScheduleDay.entries.map { day -> day.toString() }))
                        }
                    }
                    path("ListFiles/{Language}/{VoiceType}/{Category}") {
                        get { ctx ->
                            val language = ctx.pathParam("Language")
                            val voiceType = ctx.pathParam("VoiceType")
                            val category = ctx.pathParam("Category")
                            if (ValidString(language) && Language.entries.any { lang -> lang.name == language }) {
                                if (ValidString(voiceType) && VoiceType.entries.any { vt -> vt.name == voiceType }) {
                                    if (ValidString(category) && Category.entries.any { cat -> cat.name == category }) {
                                        val dir = Somecodes.SoundbankDirectory(
                                            Language.valueOf(language),
                                            VoiceType.valueOf(voiceType),
                                            Category.valueOf(category)
                                        )
                                        if (Files.isDirectory(dir)){
                                            val list = ListAudioFiles(dir)
                                            ctx.result(objectmapper.writeValueAsString(list))

                                        } else ctx.status(400)
                                            .result(objectmapper.writeValueAsString(resultMessage("Directory does not exist")))
                                    } else ctx.status(400)
                                        .result(objectmapper.writeValueAsString(resultMessage("Invalid Category")))
                                } else ctx.status(400)
                                    .result(objectmapper.writeValueAsString(resultMessage("Invalid VoiceType")))
                            } else ctx.status(400)
                                .result(objectmapper.writeValueAsString(resultMessage("Invalid Language")))
                        }
                    }
                    path("SoundBank") {
                        get("List") {
                            it.result(MariaDB.ArrayListtoString(db.soundDB.List))
                        }
                        get("ListFiles") {
                            it.result(objectmapper.writeValueAsString(ListAudioFiles(Somecodes.Soundbank_directory)))
                        }
                        get("GetPhrases/{Language}/{VoiceType}"){
                            val language = it.pathParam("Language")
                            val voiceType = it.pathParam("VoiceType")
                            if (ValidString(language) && Language.entries.any { lang -> lang.name == language }) {
                                if (ValidString(voiceType) && VoiceType.entries.any { vt -> vt.name == voiceType }) {
                                    val phrases = db.soundDB.List
                                        .filter { sb -> sb.Language == language }
                                        .filter {sb -> sb.VoiceType == voiceType}
                                        .filter { sb -> sb.Category == Category.Phrase.name}
                                        .distinctBy { sb -> sb.TAG }
                                        .sortedBy { sb -> sb.TAG }
                                    it.result(objectmapper.writeValueAsString(phrases))
                                } else it.status(400)
                                    .result(objectmapper.writeValueAsString(resultMessage("Invalid VoiceType")))
                            } else it.status(400)
                                .result(objectmapper.writeValueAsString(resultMessage("Invalid Language")))
                        }
                        get("AirlineTags") { ctx ->
                            val value = db.soundDB.List
                                .filter { it.Category == Category.Airplane_Name.name }
                                .distinctBy { it.TAG }
                                .sortedBy { it.TAG }
                                .map { KeyValueMessage(it.TAG, it.Description) }
                            ctx.result(objectmapper.writeValueAsString(value))
                        }
                        get("CityTags") { ctx ->
                            val value = db.soundDB.List
                                .filter { it.Category == Category.City.name }
                                .distinctBy { it.TAG }
                                .sortedBy { it.TAG }
                                .map { KeyValueMessage(it.TAG, it.Description) }
                            ctx.result(objectmapper.writeValueAsString(value))
                        }
                        post("Add") {
                            try {
                                val addvalue = objectmapper.readValue(it.body(), Soundbank::class.java)
                                if (addvalue.isNotEmpty()){
                                    // check apakah TAG sudah ada untuk language, category dan voicetype yang sama
                                    val exists = db.soundDB.List.any { sb ->
                                        sb.TAG == addvalue.TAG && sb.Language == addvalue.Language && sb.Category == addvalue.Category && sb.VoiceType == addvalue.VoiceType
                                    }
                                    if (!exists) {
                                        val absolutepath = Somecodes.SoundbankDirectory(Language.valueOf(addvalue.Language), VoiceType.valueOf(addvalue.VoiceType), Category.valueOf(addvalue.Category)).resolve(addvalue.Path)
                                        if (Files.isRegularFile(absolutepath)) {
                                            addvalue.Path = absolutepath.toAbsolutePath().toString()
                                            if (db.soundDB.Add(addvalue)) {
                                                db.soundDB.Resort()
                                                it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                            } else it.status(500)
                                                .result(objectmapper.writeValueAsString(resultMessage("Failed to add soundbank to database")))
                                        } else it.status(400)
                                            .result(objectmapper.writeValueAsString(resultMessage("Invalid Path, file does not exist")))
                                    } else it.status(400)
                                        .result(objectmapper.writeValueAsString(resultMessage("TAG=${addvalue.TAG} already exists for the same Language=${addvalue.Language} and Category=${addvalue.Category}")))
                                } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Some fields are empty")))

                            } catch (e: Exception) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid request body, Message: ${e.message}")))
                            }
                        }
                        delete("List") {
                            // truncate soundbank table
                            if (db.soundDB.Clear()) {
                                db.soundDB.Get()
                                it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                            } else {
                                it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate soundbank table")))
                            }
                        }
                        delete("DeleteByIndex/{index}") {
                            // delete by index
                            val index = it.pathParam("index").toUIntOrNull()
                            if (index == null) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
                            } else {
                                if (db.soundDB.DeleteByIndex(index.toInt())) {
                                    db.soundDB.Resort()
                                    it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                } else {
                                    it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete soundbank with index $index")))
                                }
                            }
                        }
                        patch("UpdateByIndex/{index}") {
                            // update by index
                            val index = it.pathParam("index").toUIntOrNull()
                            if (index!=null){
                                val sb = db.soundDB.List.find { xx -> xx.index == index }
                                if (sb!=null){
                                    try{
                                        //println("index=$index, body=${it.body()}")
                                        val newsb = objectmapper.readValue(it.body(), Soundbank::class.java)
                                        //println("newsb=$newsb")
                                        if (newsb.isNotEmpty()){
                                            var varchanged = false
                                            if (newsb.Description != sb.Description) {
                                                sb.Description = newsb.Description
                                                varchanged = true
                                            }
                                            if (newsb.TAG != sb.TAG) {
                                                sb.TAG = newsb.TAG
                                                varchanged = true
                                            }
                                            if (newsb.ValidCategory()) {
                                                if (newsb.Category != sb.Category){
                                                    sb.Category = newsb.Category
                                                    varchanged = true
                                                }
                                            } else {
                                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Category")))
                                                return@patch
                                            }
                                            if (newsb.ValidLanguage()) {
                                                if (newsb.Language != sb.Language){
                                                    sb.Language = newsb.Language
                                                    varchanged = true
                                                }
                                            } else {
                                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Language")))
                                                return@patch
                                            }
                                            if (newsb.ValidVoiceType()) {
                                                if (newsb.VoiceType != sb.VoiceType){
                                                    sb.VoiceType = newsb.VoiceType
                                                    varchanged = true
                                                }
                                            } else {
                                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid VoiceType")))
                                                return@patch
                                            }

                                            val newpath = Somecodes.SoundbankDirectory(newsb.Language, newsb.VoiceType, newsb.Category).resolve(newsb.Path)
                                            if (Files.isRegularFile(newpath)){
                                                if (newpath.toAbsolutePath().toString() != sb.Path) {
                                                    sb.Path = newpath.toAbsolutePath().toString()
                                                    varchanged = true
                                                }
                                            } else {
                                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid path")))
                                                return@patch
                                            }

                                            if (varchanged) {
                                                //println("Some fields changed for index=$index, updating...")
                                                if (db.soundDB.UpdateByIndex(index.toInt(), sb)) {
                                                    db.soundDB.Resort()
                                                    it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                                } else it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to update soundbank with index $index")))
                                            } else it.status(400)
                                                .result(objectmapper.writeValueAsString(resultMessage("Nothing has changed for soundbank with index $index")))
                                        } else Exception("Some fields are empty")
                                    } catch(e: Exception){
                                        it.status(400).result(objectmapper.writeValueAsString(resultMessage("Unable to parse request body to Soundbank, Message: ${e.message}")))
                                    }

                                } else it.status(404).result(objectmapper.writeValueAsString(resultMessage("Soundbank with index $index not found")))
                            } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))

                        }
                        get("ExportXLSX") {
                            val xlsxdata = db.soundDB.Export_XLSX()
                            if (xlsxdata != null) {
                                it.header(
                                    "Content-Type",
                                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                                )
                                it.header("Content-Disposition", "attachment; filename=\"soundbank.xlsx\"")
                                it.outputStream().use { out ->
                                    xlsxdata.write(out)
                                }
                            } else {
                                it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to export soundbank to XLSX")))
                            }
                        }
                        post("ImportXLSX") {
                            val uploaded = it.uploadedFile("file")
                            if (uploaded == null) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("No file uploaded")))
                                return@post
                            }
                            try {
                                val xlsx = XSSFWorkbook(uploaded.content())
                                if (db.soundDB.Import_XLSX(xlsx)) {
                                    db.soundDB.Resort()
                                    it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                } else {
                                    it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to import soundbank from XLSX")))
                                }
                            } catch (e: Exception) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file")))
                            }
                        }
                    }
                    path("MessageBank") {
                        get("List") {
                            // get messagebank list
                            it.result(MariaDB.ArrayListtoString(db.messageDB.List))
                        }
                        post("Add"){
                            val json : JsonNode = objectmapper.readTree(it.body())
                            val description = json.get("Description")?.asText("") ?: ""
                            val language = json.get("Language")?.asText("") ?: ""
                            val ann_id = json.get("ANN_ID")?.asInt()?.toUInt() ?: 0u
                            val voice_type = json.get("Voice_Type")?.asText("") ?: ""
                            val message_detail = json.get("Message_Detail")?.asText("") ?: ""
                            val message_tags = json.get("Message_TAGS")?.asText("") ?: ""
                            if (description.isNotEmpty()){
                                if (language.isNotEmpty() && Language.entries.any{ lang -> lang.name == language }){
                                    if (ann_id>0u){
                                        if (voice_type.isNotEmpty() && VoiceType.entries.any{ vt -> vt.name == voice_type }){
                                            if (message_detail.isNotEmpty()){
                                                if (message_tags.isNotEmpty()){
                                                    val mb = Messagebank(0u, description, language, ann_id, voice_type, message_detail, message_tags)
                                                    if (db.messageDB.Add(mb)){
                                                        db.messageDB.Resort()
                                                        it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                                    } else it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to add messagebank to database")))
                                                } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Message_TAGS")))
                                            } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Message_Detail")))
                                        } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Voice_Type")))
                                    } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid ANN_ID")))
                                } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Language")))
                            } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Description")))
                        }
                        delete("List") {
                            // truncate messagebank table
                            if (db.messageDB.Clear()) {
                                db.messageDB.Get()
                                it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                            } else {
                                it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate messagebank table")))
                            }
                        }
                        delete("DeleteByIndex/{index}") {
                            // delete by index
                            val index = it.pathParam("index").toUIntOrNull()
                            if (index == null) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
                            } else {
                                if (db.messageDB.DeleteByIndex(index.toInt())) {
                                    db.messageDB.Resort()
                                    it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                } else {
                                    it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete messagebank with index $index")))
                                }
                            }
                        }
                        patch("UpdateByIndex/{index}") {
                            // update messagebank by index
                            val index = it.pathParam("index").toUIntOrNull()
                            if (index == null) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
                            } else {
                                val mb = db.messageDB.List.find { xx -> xx.index == index }
                                if (mb == null) {
                                    it.status(404).result(objectmapper.writeValueAsString(resultMessage("Messagebank with index $index not found")))
                                } else {
                                    val json: JsonNode = objectmapper.readTree(it.body())
                                    if (json.isEmpty) {
                                        it.status(400).result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body")))
                                    } else {
                                        val _description = json.get("Description").asText("")
                                        val _language = json.get("Language").asText("")
                                        val _ann_id = json.get("ANN_ID").asInt().toUInt()
                                        val _voice_type = json.get("Voice_Type").asText("")
                                        val _message_detail = json.get("Message_Detail").asText("")
                                        val _message_tags = json.get("Message_TAGS").asText("")

                                        var changed = false
                                        if (ValidString(_description) && _description != mb.Description) {
                                            mb.Description = _description
                                            changed = true
                                        }
                                        if (ValidString(_language) && _language != mb.Language) {
                                            mb.Language = _language
                                            changed = true
                                        }
                                        if (_ann_id > 0u && _ann_id != mb.ANN_ID) {
                                            mb.ANN_ID = _ann_id
                                            changed = true
                                        }
                                        if (ValidString(_voice_type) && _voice_type != mb.Voice_Type) {
                                            if (VoiceType.entries.any { vt -> vt.name == _voice_type }) {
                                                mb.Voice_Type = _voice_type
                                                changed = true
                                            } else {
                                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Voice_Type")))
                                                return@patch
                                            }
                                        }
                                        if (ValidString(_message_detail) && _message_detail != mb.Message_Detail) {
                                            mb.Message_Detail = _message_detail
                                            changed = true
                                        }
                                        if (ValidString(_message_tags) && _message_tags != mb.Message_TAGS) {
                                            mb.Message_TAGS = _message_tags
                                            changed = true
                                        }
                                        if (changed) {
                                            if (db.messageDB.UpdateByIndex(index.toInt(), mb)) {
                                                db.messageDB.Resort()
                                                it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                            } else it.status(500)
                                                .result(objectmapper.writeValueAsString(resultMessage("Failed to update messagebank with index $index")))
                                        } else it.status(400)
                                            .result(objectmapper.writeValueAsString(resultMessage("Nothing has changed for messagebank with index $index")))
                                    }
                                }
                            }
                        }
                        get("ExportXLSX") {
                            val xlsxdata = db.messageDB.Export_XLSX()
                            if (xlsxdata != null) {
                                it.header(
                                    "Content-Type",
                                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                                )
                                it.header("Content-Disposition", "attachment; filename=\"messagebank.xlsx\"")
                                it.outputStream().use { out ->
                                    xlsxdata.write(out)
                                }
                            } else {
                                it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to export messagebank to XLSX")))
                            }
                        }
                        post("ImportXLSX") {
                            val uploaded = it.uploadedFile("file")
                            if (uploaded == null) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("No file uploaded")))
                                return@post
                            }
                            try {
                                val xlsx = XSSFWorkbook(uploaded.content())
                                if (db.messageDB.Import_XLSX(xlsx)) {
                                    db.messageDB.Resort()
                                    it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                } else {
                                    it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to import messagebank from XLSX")))
                                }
                            } catch (e: Exception) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file")))
                            }
                        }
                    }
                    path("LanguageLink") {
                        get("List") {
                            // get language link list
                            it.result(MariaDB.ArrayListtoString(db.languageDB.List))
                        }
                        post("Add"){
                            // Parse JSON from request body
                            val json: JsonNode = objectmapper.readTree(it.body())
                            val tag = json.get("tag").asText("")
                            val languages = json.get("language").asText("").split(";")
                            println("Add Language Link, tag=$tag, languages=$languages")
                            if (ValidString(tag)){
                                if (languages.all { xx -> Language.entries.any { yy -> yy.name.equals(xx,true)} }){
                                    if (!db.languageDB.List.any { ll -> ll.TAG.equals(tag,true) }) {
                                        val newvalue = LanguageLink(0u, tag, languages.joinToString(";"))
                                        if (db.languageDB.Add(newvalue)){
                                            db.languageDB.Resort()
                                            it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                        } else {
                                            it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to add language link to database")))
                                            println("Failed to add language link to database")
                                        }
                                    } else {
                                        it.status(400).result(objectmapper.writeValueAsString(resultMessage("TAG=$tag already exists")))
                                        println("TAG=$tag already exists")
                                    }
                                } else {
                                    it.status(400).result(objectmapper.writeValueAsString(resultMessage("Contains unsupported language")))
                                    println("Contains unsupported language")
                                }
                            } else {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid tag or language")))
                                println("Invalid tag")
                            }
                        }
                        delete("List") {
                            // truncate language link table
                            if (db.languageDB.Clear()) {
                                db.languageDB.Resort()
                                it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                            } else {
                                it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate language link table")))
                            }
                        }
                        delete("DeleteByIndex/{index}") {
                            // delete by index
                            val index = it.pathParam("index").toUIntOrNull()
                            if (index == null) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
                            } else {
                                if (db.languageDB.DeleteByIndex(index.toInt())) {
                                    db.languageDB.Resort()
                                    it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                } else {
                                    it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete language link with index $index")))
                                }
                            }
                        }
                        patch("UpdateByIndex/{index}") {
                            // update by index
                            val index = it.pathParam("index").toUIntOrNull()
                            if (index == null) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
                            } else {
                                val ll = db.languageDB.List.find { xx -> xx.index == index }
                                if (ll == null) {
                                    it.status(404).result(objectmapper.writeValueAsString(resultMessage("Language link with index $index not found")))
                                } else {
                                    val json: JsonNode = objectmapper.readTree(it.body())
                                    if (json.isEmpty) {
                                        it.status(400).result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body")))
                                    } else {
                                        val _tag = json.get("tag").asText("")
                                        val _language = json.get("language").asText("")
                                        var changed = false
                                        if (ValidString(_language) && _language != ll.Language) {
                                            ll.Language = _language
                                            changed = true
                                        }
                                        if (ValidString(_tag) && _tag != ll.TAG) {
                                            ll.TAG = _tag
                                            changed = true
                                        }
                                        if (changed) {
                                            if (db.languageDB.UpdateByIndex(index.toInt(), ll)) {
                                                db.languageDB.Resort()
                                                it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                            } else it.status(500)
                                                .result(objectmapper.writeValueAsString(resultMessage("Failed to update language link with index $index")))
                                        } else it.status(400)
                                            .result(objectmapper.writeValueAsString(resultMessage("Nothing has changed for language link with index $index")))
                                    }
                                }
                            }
                        }
                        get("ExportXLSX") {
                            val xlsxdata = db.languageDB.Export_XLSX()
                            if (xlsxdata != null) {
                                it.header(
                                    "Content-Type",
                                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                                )
                                it.header("Content-Disposition", "attachment; filename=\"languagelink.xlsx\"")
                                it.outputStream().use { out ->
                                    xlsxdata.write(out)
                                }
                            } else {
                                it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to export language link to XLSX")))
                            }
                        }
                        post("ImportXLSX") {
                            val uploaded = it.uploadedFile("file")
                            if (uploaded == null) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("No file uploaded")))
                                return@post
                            }
                            try {
                                val xlsx = XSSFWorkbook(uploaded.content())
                                if (db.languageDB.Import_XLSX(xlsx)) {
                                    db.languageDB.Resort()
                                    it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                } else {
                                    it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to import language link from XLSX")))
                                }
                            } catch (e: Exception) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file")))
                            }
                        }
                    }
                    path("ScheduleBank") {
                        get("List") {
                            // get timer list
                            it.result(MariaDB.ArrayListtoString(db.scheduleDB.List))
                        }
                        delete("List") {
                            // truncate timer table
                            if (db.scheduleDB.Clear()) {
                                db.scheduleDB.Get()
                                it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                            } else {
                                it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate schedulebank table")))
                            }
                        }
                        post("Add"){
                            // TODO add new schedule
                            // recheck lagi tambahan steph
                            val json: JsonNode = objectmapper.readTree(it.body())
                            val description = json.get("Description")?.asText("") ?: ""
                            val day = json.get("Day")?.asText("") ?: ""
                            val time = json.get("Time")?.asText("") ?: ""
                            val soundpath = json.get("Soundpath")?.asText("") ?: ""
                            val repeat = json.get("Repeat")?.asInt()?.toUByte() ?: 0u
                            val enable = json.get("Enable")?.asBoolean() ?: false
                            val broadcast_zones = json.get("BroadcastZones")?.asText("") ?: ""
                            val language = json.get("Language")?.asText("") ?: ""
                            if (ValidString(description)){
                                if (ValidString(day) && ValidScheduleDay(day)){
                                    if (ValidString(time) && ValidScheduleTime(time)){
                                        //soundpath is a messagebank desc and aan_id
                                       if (ValidString(soundpath)) {
                                            if (repeat in 0u..127u){
                                                if (ValidString(broadcast_zones)){
                                                    val zones = broadcast_zones.split(";")
                                                    if (zones.all { zz -> db.broadcastDB.List.any { xx -> xx.description.equals(zz,true) } }){
                                                        if (ValidString(language) && Language.entries.any{ lang -> lang.name == language }){
                                                            val newvalue = ScheduleBank(
                                                                0u,
                                                                description,
                                                                day,
                                                                time,
                                                                soundpath,
                                                                repeat,
                                                                enable,
                                                                broadcast_zones,
                                                                language
                                                            )
                                                            if (db.scheduleDB.Add(newvalue)){

                                                                db.scheduleDB.Resort()
                                                                it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                                            } else it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to add schedule to database")))
                                                        } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Language")))
                                                    } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Contains unsupported BroadcastZones")))
                                                } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid BroadcastZones")))
                                            } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Repeat, must be between 0-127")))
                                        } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Soundpath")))
                                    } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Time format, must be HH:mm")))
                                } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Day format")))
                            } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Description")))
                        }
                        delete("DeleteByIndex/{index}") {
                            // delete by index
                            val index = it.pathParam("index").toUIntOrNull()
                            if (index == null) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
                            } else {
                                if (db.scheduleDB.DeleteByIndex(index.toInt())) {
                                    db.scheduleDB.Resort()
                                    it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                } else {
                                    it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete schedule with index $index")))
                                }
                            }
                        }
                        patch("UpdateByIndex/{index}") {
                            // update by index
                            val index = it.pathParam("index").toUIntOrNull()
                            if (index == null) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
                            } else {
                                val sb = db.scheduleDB.List.find { xx -> xx.index == index }
                                if (sb == null) {
                                    it.status(404).result(objectmapper.writeValueAsString(resultMessage("Schedule with index $index not found")))
                                } else {
                                    val json: JsonNode = objectmapper.readTree(it.body())
                                    if (json.isEmpty) {
                                        it.status(400).result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body")))
                                    } else {
                                        val _description = json.get("Description").asText("")
                                        val _time = json.get("Time").asText("")
                                        val _day = json.get("Day").asText("")
                                        val _soundpath = json.get("Soundpath").asText("")
                                        val _repeat = json.get("Repeat").asInt().toUByte()
                                        val _enable = json.get("Enable").asBoolean()
                                        val _broadcast_zones = json.get("BroadcastZones").asText("")
                                        val _language = json.get("Language").asText("")
                                        var changed = false
                                        if (ValidString(_description) && _description != sb.Description) {
                                            sb.Description = _description
                                            changed = true
                                        }
                                        if (ValidString(_time) && _time != sb.Time) {
                                            if (ValidScheduleTime(_time)) {
                                                sb.Time = _time
                                                changed = true
                                            } else {
                                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Time format, must be HH:mm")))
                                                return@patch
                                            }
                                        }
                                        if (ValidString(_day) && _day != sb.Day) {
                                            if (ValidScheduleDay(_day)) {
                                                sb.Day = _day
                                                changed = true
                                            } else {
                                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Day format")))
                                                return@patch
                                            }
                                        }
                                        if (ValidString(_soundpath) && _soundpath != sb.Soundpath) {
                                            sb.Soundpath = _soundpath
                                            changed = true
                                        }
                                        if (_repeat != sb.Repeat) {
                                            sb.Repeat = _repeat
                                            changed = true
                                        }
                                        if (_enable != sb.Enable) {
                                            sb.Enable = _enable
                                            changed = true
                                        }
                                        if (ValidString(_broadcast_zones) && _broadcast_zones != sb.BroadcastZones) {
                                            sb.BroadcastZones = _broadcast_zones
                                            changed = true
                                        }
                                        if (ValidString(_language) && _language != sb.Language) {
                                            if (Language.entries.any{ lang -> lang.name == _language }) {
                                                sb.Language = _language
                                                changed = true
                                            } else {
                                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Language")))
                                                return@patch
                                            }
                                        }
                                        if (changed) {
                                            if (db.scheduleDB.UpdateByIndex(index.toInt(), sb)) {
                                                db.scheduleDB.Resort()
                                                it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                            } else it.status(500)
                                                .result(objectmapper.writeValueAsString(resultMessage("Failed to update schedule with index $index")))
                                        } else it.status(400)
                                            .result(objectmapper.writeValueAsString(resultMessage("Nothing has changed for schedule with index $index")))
                                    }
                                }
                            }
                        }
                        get("ExportXLSX") {
                            val xlsxdata = db.scheduleDB.Export_XLSX()
                            if (xlsxdata != null) {
                                it.header(
                                    "Content-Type",
                                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                                )
                                it.header("Content-Disposition", "attachment; filename=\"schedulebank.xlsx\"")
                                it.outputStream().use { out ->
                                    xlsxdata.write(out)
                                }
                            } else {
                                it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to export schedulebank to XLSX")))
                            }
                        }
                        post("ImportXLSX") {
                            val uploaded = it.uploadedFile("file")
                            if (uploaded == null) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("No file uploaded")))
                                return@post
                            }
                            try {
                                val xlsx = XSSFWorkbook(uploaded.content())
                                if (db.scheduleDB.Import_XLSX(xlsx)) {
                                    db.scheduleDB.Resort()
                                    it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                } else {
                                    it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to import schedulebank from XLSX")))
                                }
                            } catch (e: Exception) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file")))
                            }
                        }

                        //Kirim list message dan broadcast zones untuk ADD/Edit schedule
                        //TODO add at js file for messagebank and broadcast zones values
                        get("GetMessageAndBroadcastZones") {
                            val result = object {
                                val messages = db.messageDB.List
                                    .filter { mb -> !mb.Message_Detail.contains("[") && !mb.Message_Detail.contains("]")}
                                    .map { mb -> "${mb.Description} [${mb.ANN_ID}]" }
                                val broadcastzones = db.broadcastDB.List.map { it.description }
                            }
                            it.result(objectmapper.writeValueAsString(result))
                        }

                        // Kirim list language dari Messagebank berdasarkan ANN_ID
                        get("GetLanguageList/{ANN_ID}") { get1 ->
                            //kirim list language dari Messagebank
                            val langlist = db.messageDB.List
                                .filter { it.ANN_ID == get1.pathParam("ANN_ID").toInt().toUInt() }
                                .map { it.Language }.distinct()
                            get1.result(objectmapper.writeValueAsString(langlist))
                        }
                    }
                    path("Log") {
                        get("List") { get1 ->
                            val logdate = get1.queryParam("date") ?: ""
                            val logfilter = get1.queryParam("filter") ?: ""
                            if (ValiDateForLogHtml(logdate)) {
                                if (ValidString(logfilter)) {
                                    // ada log filter

                                    db.GetLogForHtml(logdate, logfilter) {
                                        get1.result(MariaDB.ArrayListtoString(it))
                                    }
                                } else {
                                    db.GetLogForHtml(logdate) {
                                        get1.result(MariaDB.ArrayListtoString(it))
                                    }
                                }
                            } else {
                                println("Invalid logdate=$logdate")
                                get1.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid logdate")))
                            }
                        }
                        get("ExportXLSX") { get1 ->
                            val logdate = get1.queryParam("date") ?: ""
                            val logfilter = get1.queryParam("filter") ?: ""
                            println("Export log to XLSX, date=$logdate, filter=$logfilter")
                            if (ValiDateForLogHtml(logdate)) {
                                val xlsxdata = if (ValidString(logfilter)) {
                                    db.Export_Log_XLSX(logdate.replace('-','/'), logfilter)
                                } else {
                                    db.Export_Log_XLSX(logdate.replace('-','/'), "")
                                }
                                if (xlsxdata != null) {
                                    get1.header(
                                        "Content-Type",
                                        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                                    )
                                    get1.header("Content-Disposition", "attachment; filename=\"log_$logdate.xlsx\"")
                                    get1.outputStream().use { out ->
                                        xlsxdata.write(out)
                                    }
                                } else {
                                    get1.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to export log to XLSX")))
                                }
                            } else get1.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid logdate")))
                        }
                    }
                    path("BroadcastZones"){
                        get("List") {
                            // TODO : temporary, convert BroadcastZones to BroadcastZonesHtml, karena harus revisi javascript di Bootstrap Studio
//                            val newlist: ArrayList<BroadcastZonesHtml> = db.broadcastDB.List.map { xx ->
//                                BroadcastZonesHtml(
//                                    xx.index,
//                                    xx.description,
//                                    xx.SoundChannel,
//                                    xx.id,
//                                    xx.bp
//                                )
//                            } as ArrayList<BroadcastZonesHtml>

                            it.result(MariaDB.ArrayListtoString(db.broadcastDB.List))
                        }
                        delete("List"){
                            // truncate broadcast zones table
                            if (db.broadcastDB.Clear()) {
                                db.broadcastDB.Get()
                                it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                            } else {
                                it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate broadcast zones table")))
                            }
                        }
                        post("Add") {
                            val json : JsonNode = objectmapper.readTree(it.body())
                            val _description = json.get("description").asText("")
                            val _soundchannel = json.get("SoundChannel").asText("")
                            val _box = json.get("Box").asText("")
                            val _relay = json.get("Relay").asText("")
                            if (ValidString(_description)){
                                if (ValidString(_soundchannel)){
                                    if (ValidString(_box)){
                                        if (ValidString(_relay)){
                                            val newbp = BroadcastZones(0u,_description,_soundchannel,_box,_relay)
                                            if (db.broadcastDB.Add(newbp)){
                                                db.broadcastDB.Resort()
                                                it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                            } else it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to add broadcast zone to database")))
                                        } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Relay")))
                                    } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Box")))
                                } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid SoundChannel")))
                            } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid description")))
                        }
                        delete("DeleteByIndex/{index}") {
                            // delete by index
                            val index = it.pathParam("index").toUIntOrNull()
                            if (index == null) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
                            } else {
                                if (db.broadcastDB.DeleteByIndex(index.toInt())) {
                                    db.broadcastDB.Resort()
                                    it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                } else {
                                    it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete broadcast zone with index $index")))
                                }
                            }
                        }
                        patch("UpdateByIndex/{index}") {
                            // update by index
                            val index = it.pathParam("index").toUIntOrNull()
                            if (index == null) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
                            } else {
                                val bz = db.broadcastDB.List.find { xx -> xx.index == index }
                                if (bz == null) {
                                    it.status(404).result(objectmapper.writeValueAsString(resultMessage("Broadcast zone with index $index not found")))
                                } else {
                                    val json: JsonNode = objectmapper.readTree(it.body())
                                    if (json.isEmpty) {
                                        it.status(400).result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body")))
                                    } else {
                                        val _description = json.get("description").asText("")
                                        val _soundchannel = json.get("SoundChannel").asText("")
                                        val _box = json.get("Box").asText("")
                                        val _relay = json.get("Relay").asText("")
                                        var changed = false
                                        if (ValidString(_description) && _description != bz.description) {
                                            bz.description = _description
                                            changed = true
                                        }
                                        if (ValidString(_soundchannel) && _soundchannel != bz.SoundChannel) {
                                            bz.SoundChannel = _soundchannel
                                            changed = true
                                        }
                                        if (ValidString(_box) && _box != bz.id) {
                                            bz.id = _box
                                            changed = true
                                        }
                                        if (ValidString(_relay) && _relay != bz.bp) {
                                            bz.bp = _relay
                                            changed = true
                                        }
                                        if (changed) {
                                            if (db.broadcastDB.UpdateByIndex(index.toInt(), bz)) {
                                                db.broadcastDB.Resort()
                                                it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                            } else it.status(500)
                                                .result(objectmapper.writeValueAsString(resultMessage("Failed to update broadcast zone with index $index")))
                                        } else it.status(400)
                                            .result(objectmapper.writeValueAsString(resultMessage("Nothing has changed for broadcast zone with index $index")))
                                    }
                                }
                            }

                        }
                        get("ExportXLSX") {
                            val xlsxdata = db.broadcastDB.Export_XLSX()
                            if (xlsxdata != null) {
                                it.header(
                                    "Content-Type",
                                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                                )
                                it.header("Content-Disposition", "attachment; filename=\"broadcastzones.xlsx\"")
                                it.outputStream().use { out ->
                                    xlsxdata.write(out)
                                }
                            } else {
                                it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to export broadcast zones to XLSX")))
                            }
                        }
                        post("ImportXLSX") {
                            val uploaded = it.uploadedFile("file")
                            if (uploaded == null) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("No file uploaded")))
                                return@post
                            }
                            try {
                                val xlsx = XSSFWorkbook(uploaded.content())
                                if (db.broadcastDB.Import_XLSX(xlsx)) {
                                    db.broadcastDB.Resort()
                                    it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                } else {
                                    it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to import broadcast zones from XLSX")))
                                }
                            } catch (e: Exception) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file")))
                            }
                        }

                    }
                    path("SoundChannel"){
                        get("List"){
                            it.result(MariaDB.ArrayListtoString(db.soundchannelDB.List))
                        }
                        delete("List"){
                            // truncate sound channel table
                            if (db.soundchannelDB.Clear()) {
                                db.soundchannelDB.Get()
                                it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                            } else {
                                it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate sound channel table")))
                            }
                        }
                        patch("UpdateByIndex/{index}"){

                            val index = it.pathParam("index").toUIntOrNull()
                            if (index == null) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
                            } else {
                                val sc = db.soundchannelDB.List.find { xx -> xx.index == index }
                                if (sc == null) {
                                    println("Sound channel with index $index not found")
                                    it.status(404).result(objectmapper.writeValueAsString(resultMessage("Sound channel with index $index not found")))
                                } else {
                                    val json: JsonNode = objectmapper.readTree(it.body())
                                    println("Received JSON: $json")
                                    if (json.isEmpty) {
                                        println("UpdateByIndex with index=$index has empty body")
                                        it.status(400).result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body")))
                                    } else {
                                        val _channel = json.get("description").asText("")
                                        val _ip = json.get("ip").asText("")
                                        println("Update sound channel with index $index, channel=$_channel, ip=$_ip")
                                        if (ValidString(_channel)){
                                            if (ValidIPV4(_ip)){
                                                if (_channel.equals(sc.channel) && _ip.equals(sc.ip)) {
                                                    println("Nothing has changed for sound channel with index $index")
                                                    it.status(400).result(objectmapper.writeValueAsString(resultMessage("Nothing has changed for sound channel with index $index")))
                                                    return@patch
                                                } else {

                                                    // cek apakah ada soundchannel lain yang pakai ip dan channel yang sama
                                                    if (db.soundchannelDB.List.any { xx -> xx.index != index && _ip.equals(xx.ip) && _channel.equals(xx.channel, true) }) {
                                                        println("Another sound channel already uses IP $_ip and channel $_channel")
                                                        it.status(400).result(objectmapper.writeValueAsString(resultMessage("Another sound channel already uses IP $_ip and channel $_channel")))
                                                        return@patch
                                                    }

                                                    // ada sesuatu yang ganti
                                                    val newsc = SoundChannel(0u, _channel, _ip)
                                                    if (db.soundchannelDB.UpdateByIndex(index.toInt(),newsc)){
                                                        println("Updated sound channel with index $index")
                                                        db.soundchannelDB.Resort()
                                                        it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                                    } else {
                                                        println("Failed to update sound channel with index $index")
                                                        it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to update sound channel with index $index")))
                                                        return@patch
                                                    }
                                                }

                                            } else {
                                                println("Invalid IP address")
                                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid IP address")))
                                            }
                                        } else {
                                            println("Invalid channel")
                                            it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid channel")))
                                        }
                                    }
                                }

                            }



                        }
                        get("ExportXLSX"){
                            val xlsxdata = db.soundchannelDB.Export_XLSX()
                            if (xlsxdata != null) {
                                it.header(
                                    "Content-Type",
                                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                                )
                                it.header("Content-Disposition", "attachment; filename=\"soundchannel.xlsx\"")
                                it.outputStream().use { out ->
                                    xlsxdata.write(out)
                                }
                            } else {
                                it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to export sound channel to XLSX")))
                            }
                        }
                        post("ImportXLSX"){
                            val uploaded = it.uploadedFile("file")
                            if (uploaded == null) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("No file uploaded")))
                                return@post
                            }
                            try {
                                val xlsx = XSSFWorkbook(uploaded.content())
                                if (db.soundchannelDB.Import_XLSX(xlsx)) {
                                    db.soundchannelDB.Resort()
                                    it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                } else {
                                    it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to import sound channel from XLSX")))
                                }
                            } catch (e: Exception) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file")))
                            }
                        }
                    }

                    // Steph : coba tambah untuk QueuePaging Table. Belum ada di JS file(?)
                    path("QueuePaging"){

                        get("List"){
                            it.result(MariaDB.ArrayListtoString(db.queuepagingDB.List))
                        }

                        delete("List"){
                            // truncate queue paging table
                            if (db.queuepagingDB.Clear()) {
                                db.queuepagingDB.Get()
                                it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                            } else {
                                it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate queue paging table")))
                            }
                        }
                        delete("DeleteByIndex/{index}") {
                            // delete by index
                            val index = it.pathParam("index").toUIntOrNull()
                            if (index == null) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
                            } else {
                                if (db.queuepagingDB.DeleteByIndex(index.toInt())) {
                                    db.queuepagingDB.Resort()
                                    it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                } else {
                                    it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete queue paging with index $index")))
                                }
                            }
                        }

                    }


                    // Steph : coba tambah untuk QueueTable Table. Belum ada di JS file(?)
                    path("QueueTable"){

                        get("List"){
                            it.result(MariaDB.ArrayListtoString(db.queuetableDB.List))
                        }

                        delete("List"){
                            // truncate queue table
                            if (db.queuetableDB.Clear()) {
                                db.queuetableDB.Get()
                                it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                            } else {
                                it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate queue sound table")))
                            }
                        }

                        delete("DeleteByIndex/{index}") {
                            // delete by index
                            val index = it.pathParam("index").toUIntOrNull()
                            if (index == null) {
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
                            } else {
                                if (db.queuetableDB.DeleteByIndex(index.toInt())) {
                                    db.queuetableDB.Resort()
                                    it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                } else {
                                    it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete queue sound with index $index")))
                                }
                            }
                        }
                    }

                }
            }
        }.start(listenPort)

    }


    fun CheckUsers(ctx: Context) {
        val user = ctx.sessionAttribute<String?>("user")
        if (user == null) {
            ctx.redirect("login.html")
        }
        val foundUser = userlist.find { it.first == user }
        if (foundUser == null) {
            ctx.redirect("login.html")
        }
    }

    fun Stop() {
        app?.stop()

    }
}