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 codes.Somecodes.Companion.ValidStrings
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.SoundChannel
import database.Soundbank
import database.UserDB
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.onClose { wsCloseContext ->
                            // TODO Handle WebSocket close event
                            println("WebSocket closed: ${wsCloseContext.session.remoteAddress}")
                        }
                        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" -> {
                                        // TODO Get Network status
                                        Somecodes.GetNetworkStatus("")
                                        SendReply(wsMessageContext, cmd.command, "OK")
                                    }

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

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

                                    "getStreamerOutputs" -> {
                                        SendReply(
                                            wsMessageContext,
                                            cmd.command,
                                            objectmapper.writeValueAsString(StreamerOutputs.values.toList())
                                        )
                                    }

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

                        }
                        ws.onConnect { wsConnectContext ->
                            // TODO Handle WebSocket connect event
                            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("AirlineTags") { ctx ->
                            val value = db.soundDB.List
                                .filter { it.Category == Category.Airline_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 (ValidString(addvalue.Description)) {
                                    if (ValidString(addvalue.TAG)) {
                                        if (ValidString(addvalue.Category)) {
                                            if (ValidString(addvalue.Language)) {
                                                if (ValidString(addvalue.Path)) {
                                                    // check apakah TAG sudah ada untuk language dan category yang sama
                                                    val exists = db.soundDB.List.any { sb ->
                                                        sb.TAG == addvalue.TAG && sb.Language == addvalue.Language && sb.Category == addvalue.Category
                                                    }
                                                    if (!exists) {
                                                        if (ValidFile(addvalue.Path)) {
                                                            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("Invalid Path")))
                                            } else it.status(400)
                                                .result(objectmapper.writeValueAsString(resultMessage("Invalid Language")))
                                        } else it.status(400)
                                            .result(objectmapper.writeValueAsString(resultMessage("Invalid Category")))
                                    } else it.status(400)
                                        .result(objectmapper.writeValueAsString(resultMessage("Invalid TAG")))
                                } else it.status(400)
                                    .result(objectmapper.writeValueAsString(resultMessage("Invalid Description")))
                            } catch (_: Exception) {
                                it.status(400)
                                    .result(objectmapper.writeValueAsString(resultMessage("Invalid request body")))
                            }
                        }
                        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) {
                                // tidak ada path param index
                                it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
                            } else {
                                val sb = db.soundDB.List.find { xx -> xx.index == index }
                                if (sb == null) {
                                    // soundbank dengan index tersebut tidak ditemukan
                                    it.status(404)
                                        .result(objectmapper.writeValueAsString(resultMessage("Soundbank with index $index not found")))
                                } else {
                                    // soundbank dengan index tersebut ditemukan, sekarang update
                                    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 _tag = json.get("TAG").asText("")
                                        val _category = json.get("Category").asText("")
                                        val _language = json.get("Language").asText("")
                                        val _path = json.get("Path").asText("")
                                        var changed = false
                                        if (ValidString(_description) && _description != sb.Description) {
                                            sb.Description = _description
                                            changed = true
                                        }
                                        if (ValidString(_tag) && _tag != sb.TAG) {
                                            sb.TAG = _tag
                                            changed = true
                                        }
                                        if (ValidString(_category) && _category != sb.Category) {
                                            if (Category.entries.any { cat ->
                                                    cat.name == _category
                                                }) {
                                                sb.Category = _category
                                                changed = true
                                            } else {
                                                it.status(400)
                                                    .result(objectmapper.writeValueAsString(resultMessage("Invalid Category")))
                                                return@patch
                                            }
                                        }
                                        if (ValidString(_language) && _language != sb.Language) {
                                            sb.Language = _language
                                            changed = true
                                        }
                                        if (ValidString(_path) && _path != sb.Path) {
                                            if (ValidFile(_path)) {
                                                sb.Path = _path
                                                changed = true
                                            } else {
                                                it.status(400)
                                                    .result(objectmapper.writeValueAsString(resultMessage("Invalid Path, file does not exist")))
                                                return@patch
                                            }
                                        }
                                        if (changed) {
                                            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")))
                                    }
                                }
                            }
                        }
                        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))
                        }
                        get("MessageIDs") { ctx ->
                            val value = db.messageDB.List
                                .distinctBy { it.ANN_ID }
                                .sortedBy { it.ANN_ID }
                                .map { KeyValueMessage(it.ANN_ID.toString(), it.Description) }
                            ctx.result(objectmapper.writeValueAsString(value))
                        }
                        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
                        }
                        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")))
                            }
                        }
                    }
                    path("UserManagement") {
                        get("List") {
                            it.result(objectmapper.writeValueAsString(db.userDB.List))
                        }
                        delete("List") {
                            if (db.userDB.Clear()) {
                                db.userDB.Get()
                                it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                            } else {
                                it.status(500)
                                    .result(objectmapper.writeValueAsString(resultMessage("Failed to truncate user table")))
                            }
                        }
                        post("Add") { ctx ->
                            val json: JsonNode = objectmapper.readTree(ctx.body())
                            val username = json.get("username").asText("")
                            val password = json.get("password").asText("")
                            val location = json.get("location").asText("")
                            val airline_tags = json.get("airline_tags").asText("")
                            val city_tags = json.get("city_tags").asText("")
                            val messagebank_ann_id = json.get("messagebank_ann_id").asText("")
                            val broadcastzones = json.get("broadcastzones").asText("")
                            if (ValidStrings(listOf(username, password, location, airline_tags, city_tags, messagebank_ann_id, broadcastzones))) {
                                if (!db.Username_exists(username)) {
                                    // check apakah ada airline tag yang tidak ada di soundbank
                                    val atags = airline_tags.split(";").map { it.trim() }
                                        .filter { it.isNotEmpty() }.distinct()
                                    val airlinetags = db.Get_AirlineCode_Tags()
                                    if (atags.all { tag -> airlinetags.any { it == tag } }) {
                                        // check apakah ada city tag yang tidak ada di soundbank
                                        val ctags = city_tags.split(";").map { it.trim() }
                                            .filter { it.isNotEmpty() }.distinct()
                                        val citytags = db.Get_City_Tags()
                                        if (ctags.all { tag -> citytags.any { it == tag } }) {
                                            val bzdesc =
                                                broadcastzones.split(";").map { it.trim() }
                                                    .filter { it.isNotEmpty() }.distinct()
                                            val bzlist = db.Get_BroadcastZone_List()
                                            if (bzdesc.all { desc -> bzlist.any { it == desc } }) {
                                                val mbids = messagebank_ann_id.split(";")
                                                    .map { it.trim() }
                                                    .filter { it.isNotEmpty() }.distinct()
                                                    .mapNotNull { it.toUIntOrNull() }
                                                val mbankids = db.Get_MessageID_List()
                                                if (mbids.all { id -> mbankids.any { it == id } }) {
                                                    // semua valid, tambain ke database
                                                    val newuser = UserDB(
                                                        0u,
                                                        username,
                                                        password,
                                                        location,
                                                        airline_tags,
                                                        city_tags,
                                                        messagebank_ann_id,
                                                        broadcastzones
                                                    )
                                                    if (db.userDB.Add(newuser)) {
                                                        db.userDB.Resort()
                                                        ctx.result(objectmapper.writeValueAsString(resultMessage("OK") ))
                                                    } else ctx.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to add user to database")))
                                                } else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Some ANN_ID not found in Messagebank") ))
                                            } else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Some broadcast zone tags not found in soundbank")) )
                                        } else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Some city tags not found in soundbank")))
                                    } else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Some airline tags not found in soundbank")))
                                } else ctx.status(400).result(objectmapper.writeValueAsString("Username already exists"))
                            } else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Not all fields have value")))
                        }
                        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.userDB.DeleteByIndex(index.toInt())) {
                                    db.userDB.Resort()
                                    it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                } else it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete user with index $index")))
                            }
                        }
                        patch("UpdateByIndex/{index}"){ ctx ->
                            // update by index
                            val index = ctx.pathParam("index").toUIntOrNull()
                            if (index!=null){
                                val user = db.userDB.List.find { xx -> xx.index == index }
                                if (user!=null){
                                    val json : JsonNode = objectmapper.readTree(ctx.body())
                                    if (!json.isEmpty){
                                        val _username = json.get("username").asText("")
                                        val _password = json.get("password").asText("")
                                        val _location = json.get("location").asText("")
                                        val _airline_tags = json.get("airline_tags").asText("")
                                        val _city_tags = json.get("city_tags").asText("")
                                        val _messagebank_ann_id = json.get("messagebank_ann_id").asText("")
                                        val _broadcastzones = json.get("broadcastzones").asText("")
                                        if (ValidStrings(listOf(_username, _password, _location, _airline_tags, _city_tags, _messagebank_ann_id, _broadcastzones))) {
                                            val _otherusername = db.userDB.List.find { xx -> xx.username.equals(_username, true) && xx.index != index }
                                            if (_otherusername == null) {
                                                // belum ada user lain yang pakai username ini
                                                val atags = _airline_tags.split(";").map { it.trim() }.filter { it.isNotEmpty() }.distinct()
                                                val ctags = _city_tags.split(";").map { it.trim() }.filter { it.isNotEmpty() }.distinct()
                                                val msgids = _messagebank_ann_id.split(";").map { it.trim() }.filter { it.isNotEmpty() }.distinct().mapNotNull { it.toUIntOrNull() }
                                                val bzdesc = _broadcastzones.split(";").map { it.trim() }.filter { it.isNotEmpty() }.distinct()

                                                val airline_tags = db.Get_AirlineCode_Tags()
                                                val city_tags = db.Get_City_Tags()
                                                val msgbankids = db.Get_MessageID_List()
                                                val bzlist = db.Get_BroadcastZone_List()
                                                if (atags.all { tag -> airline_tags.any { it == tag } }) {
                                                    if (ctags.all { tag -> city_tags.any { it == tag } }) {
                                                        if (msgids.all { tag -> msgbankids.any { it == tag } }) {
                                                            if (bzdesc.all { tag -> bzlist.any { it == tag } }) {
                                                                val editeduser = UserDB(index, _username, _password, _location, _airline_tags, _city_tags, _messagebank_ann_id, _broadcastzones)
                                                                if (!user.isEqual(editeduser)){
                                                                    if (db.userDB.UpdateByIndex(index.toInt(), editeduser)){
                                                                        db.userDB.Resort()
                                                                        ctx.result(objectmapper.writeValueAsString(resultMessage("OK")))

                                                                    } else ctx.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to update user with index $index")))
                                                                } else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Nothing has changed for user with index $index")))
                                                            } else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Some broadcast zone is not found in Broadcast Zone list")))
                                                        } else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Some ANN_ID not found in Messagebank")))
                                                    } else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Some city tags not found in soundbank")))
                                                } else ctx.status(400).result(objectmapper.writeValueAsString("Some airline tags not found in soundbank"))
                                            } else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Username already exists for another user")))
                                        } else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Not all fiels have value")))
                                    } else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body")))
                                } else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("User with index $index not found")))
                            } else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
                        }
                        get("ExportXLSX") {
                            val xlsxdata = db.userDB.Export_XLSX()
                            if (xlsxdata != null) {
                                it.header(
                                    "Content-Type",
                                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                                )
                                it.header("Content-Disposition", "attachment; filename=\"userdb.xlsx\"")
                                it.outputStream().use { out ->
                                    xlsxdata.write(out)
                                }
                            } else {
                                it.status(500)
                                    .result(objectmapper.writeValueAsString(resultMessage("Failed to export user table 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.userDB.Import_XLSX(xlsx)) {
                                    db.userDB.Resort()
                                    it.result(objectmapper.writeValueAsString(resultMessage("OK")))
                                } else {
                                    it.status(500)
                                        .result(objectmapper.writeValueAsString(resultMessage("Failed to import user table from XLSX")))
                                }
                            } catch (e: Exception) {
                                it.status(400)
                                    .result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file")))
                            }
                        }
                    }
                    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") {
                            it.result(MariaDB.ArrayListtoString(db.broadcastDB.List))
                        }
                        get("BroadcastZoneDescriptions") { ctx ->
                            val value = db.broadcastDB.List
                                .distinctBy { it.description }
                                .map { it.description }
                            ctx.result(objectmapper.writeValueAsString(value))
                        }
                        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))
                        }
                        get("SoundChannelDescriptions") {
                            it.result(objectmapper.writeValueAsString(db.Get_SoundChannel_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
                                                    val othersc = db.soundchannelDB.List.filter { sc -> sc.ip == _ip }.filter { sc -> sc.index != index }
                                                    if (othersc.isNotEmpty()){
                                                        it.status(400).result(objectmapper.writeValueAsString(resultMessage("This IP address is already used by another sound channel")))
                                                    } else {
                                                        // 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")))
                                                        }
                                                    }


                                                }

                                            } 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")))
                            }
                        }
                    }
                }
            }
        }.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()

    }
}