package commandServer

import audioPlayer
import codes.Somecodes.Companion.ValidString
import codes.Somecodes.Companion.datetimeformat1
import content.Category
import content.Language
import database.Messagebank
import database.QueuePaging
import database.QueueTable
import database.Soundbank
import db
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.tinylog.Logger
import tcpreceiver
import udpreceiver
import java.net.ServerSocket
import java.net.Socket
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.time.LocalDateTime
import java.util.function.Consumer
import kotlin.io.path.absolutePathString

@Suppress("unused")
class TCP_Android_Command_Server {
    lateinit var tcpserver: ServerSocket
    lateinit var job: Job
    private val socketMap = mutableMapOf<String, Socket>()
    lateinit var logcb: Consumer<String>
    private val listUserLogin = mutableListOf<userLogin>()
    private val listOnGoingPaging = mutableMapOf<String, PagingJob>()

    /**
     * Start TCP Command Server
     * @param port The port to listen on, default is 5003
     * @param logCB Callback to handle Log messages
     * @return true if successful
     */
    fun StartTcpServer(port: Int = 5003, logCB: Consumer<String>): Boolean {
        logcb = logCB
        try {
            val tcp = ServerSocket(port)
            tcpserver = tcp
            job = CoroutineScope(Dispatchers.IO).launch {
                Logger.info { "TCP Android server started on port $port" }
                while (isActive) {
                    if (tcpserver.isClosed) break
                    try {
                        val socket = tcpserver.accept()

                        CoroutineScope(Dispatchers.IO).launch {
                            if (socket != null) {
                                // key  is IP address only
                                val key: String = socket.inetAddress.hostAddress
                                socketMap[key] = socket
                                Logger.info { "Start communicating with IPMT/IPM with IP $key" }
                                val din = socket.getInputStream()
                                val dout = socket.getOutputStream()
                                try{
                                    while (isActive) {
                                        if (din.available() > 0) {
                                            val bb = ByteArray(din.available())
                                            din.read(bb)
                                            // B4A format, 4 bytes di depan adalah size
                                            val str = String(bb, 4, bb.size - 4)
                                            //println("Received command from $key : $str")
                                            str.split("@").map { it.trim() }.filter { ValidString(it) }
                                                .forEach {
                                                    process_command(key,it) { reply ->
                                                        try {
                                                            dout.write(String_to_Byte_Android(reply))
                                                        } catch (e: Exception) {
                                                            logcb.accept("Failed to send reply to $key, Message : $e")
                                                        }
                                                    }
                                                }
                                        }
                                    }
                                } catch (e : Exception){
                                    logcb.accept("Exception in communication with $key, Message : ${e.message}")
                                }

                                logcb.accept("Finished communicatiing with $key")
                                CloseSocket(socket)
                                socketMap.remove(key)
                            }

                        }

                    } catch (ex: Exception) {
                        logcb.accept("Failed accepting TCP Socket, Message : ${ex.message}")
                    }

                }
                logcb.accept("TCP server stopped")
            }
            return true
        } catch (e: Exception) {
            logcb.accept("Failed to StartTcpServer, Message : ${e.message}")
        }
        return false
    }

    private fun CloseSocket(socket : Socket) {
        try {
            socket.close()
        } catch (e: Exception) {
            Logger.error { "Failed to close socket, Message : ${e.message}" }
        }
    }

    /**
     * Convert a String to ByteArray in prefix AsyncStream format in B4X
     * @param str The input string
     * @return ByteArray with 4 bytes prefix length + string bytes
     */
    private fun String_to_Byte_Android(str: String): ByteArray {
        if (ValidString(str)) {
            val bytes = str.toByteArray(Charsets.UTF_8)
            val len = bytes.size
            return ByteBuffer.allocate(len + 4)
                .order(ByteOrder.LITTLE_ENDIAN)
                .putInt(len)
                .put(bytes)
                .array()
        }
        return ByteArray(0)
    }

    /**
     * Process command from Android client
     * @param key The client IP address
     * @param cmd The command string
     * @param cb Callback to send reply string
     */
    private fun process_command(key: String, cmd: String, cb: Consumer<String>) {
        Logger.info { "Command from $key : $cmd" }
        val parts = cmd.split(";").map { it.trim() }.filter { it.isNotBlank() }
        when (parts[0]) {
            "GETLOGIN" -> {
                // Android login request
                val username = parts.getOrElse(1) { "" }
                val password = parts.getOrElse(2) { "" }
                if (ValidString(username) && ValidString(password)) {
                    if (db.userDB.List.any{
                        it.username==username && it.password==password}) {
                        val existing = listUserLogin.find { it.ip == key}
                        if (existing!=null){
                            existing.username = username
                        } else{
                            listUserLogin.add(userLogin(key, username))
                        }
                        cb.accept("LOGIN;TRUE@")
                        logcb.accept("Android Login success from $key as $username")
                        return
                    } else {
                        logcb.accept("Android Login failed from $key as $username")
                        cb.accept("LOGIN;FALSE@")
                    }
                } else {
                    logcb.accept("Android Login failed from $key with empty username or password")
                    cb.accept("LOGIN;FALSE@")
                }
            }

            "PCMFILE_START" ->{
                // start sending PCM data from Android for paging
                val size = parts.getOrElse(1) { "0" }.toInt()
                val filename = parts.getOrElse(2) { "" }
                val zones = parts.getOrElse(3) { "" }.replace(",",";")
                if (size>0){
                    if (ValidString(filename)){
                        if (ValidString(zones)){
                            // create paging job
                            val pj = PagingJob(key, zones)
                            // ada expected size
                            pj.expectedSize = size
                            // masukin ke list
                            listOnGoingPaging[key] = pj
                            Logger.info{"PagingJob created for Android $key, zones: $zones, file: ${pj.filePath.absolutePathString()}"}

                            tcpreceiver.RequestDataFrom(key) {
                                // push data ke paging job
                                pj.addData(it, it.size)
                            }
                            cb.accept("PCMFILE_START;OK@")
                            Logger.info{"Android $key start sending PCM data, expecting $size bytes"}
                            return

                        } else logcb.accept("PCMFILE_START from Android $key failed, empty zones")
                    } else logcb.accept("PCMFILE_START from Android $key failed, empty filename")
                } else logcb.accept("PCMFILE_START from Android $key failed, invalid size")
                cb.accept("PCMFILE_START;NG@")
            }
            "PCMFILE_STOP" -> {
                // stop sending PCM data from Android for paging
                val pj = listOnGoingPaging[key]
                if (pj!=null) {
                    listOnGoingPaging.remove(key)
                    tcpreceiver.StopRequestDataFrom(key)
                    // get remaining data
                    val data = pj.GetData()
                    pj.Close()
                    if (data.size==pj.expectedSize){
                        Logger.info { "Paging job closed from Android $key, total bytes received ${data.size}, writing to file ${pj.filePath.absolutePathString()}" }
                        val result = audioPlayer.WavWriter(data, pj.filePath.absolutePathString(), true)
                        if (result.success) {
                            val qp = QueuePaging(
                                0u,
                                LocalDateTime.now().format(datetimeformat1),
                                "ANDROID",
                                "PAGING",
                                pj.filePath.absolutePathString(),
                                pj.broadcastzones
                            )
                            if (db.queuepagingDB.Add(qp)) {
                                db.queuepagingDB.Resort()
                                logcb.accept("Paging audio inserted to queue paging table from Android $key, file ${pj.filePath.absolutePathString()}")
                                cb.accept("PCMFILE_STOP;OK@")
                                return
                            } else logcb.accept("Failed to insert paging audio to queue paging table from Android $key, file ${pj.filePath.absolutePathString()}")
                        } else logcb.accept("Failed to write paging audio to file ${pj.filePath.absolutePathString()}, Message : ${result.message}")
                    } else logcb.accept("PCMFILE_STOP from Android $key received size ${data.size} does not match expected ${pj.expectedSize}")
                } else logcb.accept("PCMFILE_STOP from Android $key failed, no ongoing PCM data receiving")
                cb.accept("PCMFILE_STOP;NG@")
            }

            "STARTPAGINGAND" -> {
                // Start Paging request from IPM
                val zones = parts.getOrElse(1) { "" }.replace(",",";")
                if (ValidString(zones)){
                    // create pagingjob
                    val pj = PagingJob(key, zones)
                    // masukin ke list
                    listOnGoingPaging[key] = pj
                    Logger.info{"PagingJob created for IPM $key, zones: $zones, file: ${pj.filePath.absolutePathString()}"}

                    // start minta data dari udpreceiver
                    udpreceiver.RequestDataFrom(key){
                        // push data ke paging job
                        pj.addData(it, it.size)
                    }
                    logcb.accept("Paging started from IPM $key")
                    cb.accept("STARTPAGINGAND;OK@")
                    return
                } else logcb.accept("Paging start from IPM $key failed, empty zones")
                cb.accept("STARTPAGINGAND;NG@")
            }

            "STOPPAGINGAND"  -> {
                // stop paging request from IPM
                val pj = listOnGoingPaging[key]
                if (pj!=null){
                    listOnGoingPaging.remove(key)
                    udpreceiver.StopRequestDataFrom(key)
                    logcb.accept("Paging stopped from IPM $key")
                    // get remaining data
                    val data = pj.GetData()
                    pj.Close()
                    Logger.info{"Paging job closed from IPM $key, total bytes received ${data.size}, writing to file ${pj.filePath.absolutePathString()}"}
                    val result = audioPlayer.WavWriter(data, pj.filePath.absolutePathString(), true)
                    if (result.success){
                        val qp = QueuePaging(
                            0u,
                            LocalDateTime.now().format(datetimeformat1),
                            "IPM",
                            "PAGING",
                            pj.filePath.absolutePathString(),
                            pj.broadcastzones
                        )
                        if (db.queuepagingDB.Add(qp)){
                            db.queuepagingDB.Resort()
                            logcb.accept("Paging audio inserted to queue paging table from IPM $key, file ${pj.filePath.absolutePathString()}")
                            cb.accept("STOPPAGINGAND;OK@")
                            return
                        } else logcb.accept("Failed to insert paging audio to queue paging table from IPM $key, file ${pj.filePath.absolutePathString()}")
                    } else logcb.accept("Failed to write paging audio to file ${pj.filePath.absolutePathString()}, Message : ${result.message}")
                } else logcb.accept("Paging stop from IPM $key failed, no ongoing paging")
                cb.accept("STOPPAGINGAND;NG@")

            }

            "CANCELPAGINGAND" -> {
                // cancel paging request from IPM
                val pj = listOnGoingPaging[key]
                if (pj!=null){
                    pj.Close()
                    listOnGoingPaging.remove(key)
                    udpreceiver.StopRequestDataFrom(key)
                    logcb.accept("Paging from IPM $key cancelled")
                    cb.accept("CANCELPAGINGAND;OK@")
                    return
                } else logcb.accept("Paging cancel from IPM $key failed, no ongoing paging")
                cb.accept("CANCELPAGINGAND;NG@")

            }

            "STARTINITIALIZE" -> {
                val username = parts.getOrElse(1) { "" }
                if (ValidString(username)){
                    val userlogin = listUserLogin.find { it.username == username }
                    if (userlogin != null){
                        val userdb = db.userDB.List.find { it.username == username }
                        if (userdb != null){
                            //println("Sending initialization data to $key with username $username")
                            val result = StringBuilder()
                            // kirim Zone
                            result.append("ZONE")
                            userdb.broadcastzones.split(";").map { it.trim() }.filter { it.isNotBlank() }.forEach {
                                result.append(";")
                                result.append(it)
                            }
                            result.append("@")
                            cb.accept(result.toString())

                            // kirim MSGTOTAL
                            result.clear()
                            val VARMESSAGES = mutableListOf<Messagebank>()
                            result.append("MSGTOTAL;")
                            userdb.messagebank_ann_id
                                // messagebank_ann_id adalah rentengan ANN_ID (digit) yang dipisah dengan ;
                                .split(";")
                                // trim dulu
                                .map { it.trim() }
                                // bukan string kosong antar dua tanda ;
                                .filter { it.isNotBlank() }
                                // iterasi setiap ANN_ID
                                .forEach { annid ->
                                    // masukin ke VARMESSAGES yang unik secara ANN_ID dan Language
                                    val xx = db.messageDB.List
                                        .filter{ it.ANN_ID == annid.toUInt() }
                                        .distinctBy { it.ANN_ID }
                                    VARMESSAGES.addAll(xx)
                            }
                            result.append(VARMESSAGES.size).append("@")
                            cb.accept(result.toString())

                            // kirim VARAPTOTAL
                            result.clear()
                            result.append("VARAPTOTAL;")
                            val VARAPTOTAL = mutableListOf<Soundbank>()
                            userdb.airline_tags
                                .split(";")
                                .map { it.trim() }
                                .filter { it.isNotBlank() }
                                .forEach { al ->
                                    val sb = db.soundDB.List
                                        .filter { it.Category.equals(Category.Airplane_Name.name, true) }
                                        .filter { it.TAG.equals(al, true)}
                                        .distinctBy { it.TAG }
                                    VARAPTOTAL.addAll(sb)
                            }
                            result.append(VARAPTOTAL.size).append("@")
                            cb.accept(result.toString())

                            // kirim VARCITYTOTAL
                            result.clear()
                            result.append("VARCITYTOTAL;")
                            val VARCITYTOTAL = mutableListOf<Soundbank>()
                            userdb.city_tags
                                .split(";")
                                .map { it.trim() }
                                .filter { it.isNotBlank() }
                                .forEach { ct ->
                                    val sb = db.soundDB.List
                                        .filter { it.Category.equals(Category.City.name, true) }
                                        .filter { it.TAG.equals(ct, true)}
                                        .distinctBy { it.TAG }
                                    VARCITYTOTAL.addAll(sb)
                                }
                            result.append(VARCITYTOTAL.size).append("@")
                            cb.accept(result.toString())

                            // kirim VARPLACESTOTAL
                            result.clear()
                            result.append("VARPLACESTOTAL;")
                            val VARPLACESTOTAL = mutableListOf<Soundbank>()
                            db.soundDB.List
                                .filter { it.Category.equals(Category.Places.name, true) }
                                .distinctBy { it.TAG }
                                .forEach {
                                    VARPLACESTOTAL.add(it)
                                }
                            result.append(VARPLACESTOTAL.size).append("@")
                            cb.accept(result.toString())

                            // kirim VARSHALATTOTAL
                            result.clear()
                            result.append("VARSHALATTOTAL;")
                            val VARSHALATTOTAL = mutableListOf<Soundbank>()
                            db.soundDB.List
                                .filter { it.Category.equals(Category.Shalat.name, true) }
                                .distinctBy { it.TAG }
                                .forEach {
                                    VARSHALATTOTAL.add(it)
                                }
                            result.append(VARSHALATTOTAL.size).append("@")
                            cb.accept(result.toString())

                            // kirim VARSEQUENCETOTAL
                            result.clear()
                            result.append("VARSEQUENCETOTAL;")
                            val VARSEQUENCETOTAL = mutableListOf<Soundbank>()
                            db.soundDB.List
                                .filter { it.Category.equals(Category.Sequence.name, true) }
                                .distinctBy { it.TAG }
                                .forEach {
                                    VARSEQUENCETOTAL.add(it)
                                }
                            result.append(VARSEQUENCETOTAL.size).append("@")
                            cb.accept(result.toString())

                            // kirim VARREASONTOTAL
                            result.clear()
                            result.append("VARREASONTOTAL;")
                            val VARREASONTOTAL = mutableListOf<Soundbank>()
                            db.soundDB.List
                                .filter { it.Category.equals(Category.Reason.name, true) }
                                .distinctBy { it.TAG }
                                .forEach {
                                    VARREASONTOTAL.add(it)
                                }
                            result.append(VARREASONTOTAL.size).append("@")
                            cb.accept(result.toString())

                            // kirim VARPROCEDURETOTAL
                            val VARPROCEDURETOTAL = mutableListOf<Soundbank>()
                            result.clear()
                            result.append("VARPROCEDURETOTAL;")
                            db.soundDB.List
                                .filter { it.Category.equals(Category.Procedure.name, true) }
                                .distinctBy { it.TAG }
                                .forEach {
                                    VARPROCEDURETOTAL.add(it)
                                }
                            result.append(VARPROCEDURETOTAL.size).append("@")
                            cb.accept(result.toString())

                            // kirim VARGATETOTAL
                            val VARGATETOTAL = mutableListOf<Soundbank>()
                            result.clear()
                            result.append("VARGATETOTAL;")
                            db.soundDB.List
                                .filter { it.Category.equals(Category.Gate.name, true) }
                                .distinctBy { it.TAG }
                                .forEach {
                                    VARGATETOTAL.add(it)
                                }
                            result.append(VARGATETOTAL.size).append("@")
                            cb.accept(result.toString())

                            // kirim VARCOMPENSATIONTOTAL
                            result.clear()
                            result.append("VARCOMPENSATIONTOTAL;")
                            val VARCOMPENSATIONTOTAL = mutableListOf<Soundbank>()
                            db.soundDB.List
                                .filter { it.Category.equals(Category.Compensation.name, true) }
                                .distinctBy { it.TAG }
                                .forEach {
                                    VARCOMPENSATIONTOTAL.add(it)
                                }
                            result.append(VARCOMPENSATIONTOTAL.size).append("@")
                            cb.accept(result.toString())

                            // kirim VARGREETINGTOTAL
                            result.clear()
                            result.append("VARGREETINGTOTAL;")
                            val VARGREETINGTOTAL = mutableListOf<Soundbank>()
                            db.soundDB.List
                                .filter { it.Category.equals(Category.Greeting.name, true) }
                                .distinctBy { it.TAG }
                                .forEach {
                                    VARGREETINGTOTAL.add(it)
                                }
                            result.append(VARGREETINGTOTAL.size).append("@")
                            cb.accept(result.toString())

                            //Append MSG, for Android only Indonesia and English
                            if (VARMESSAGES.isNotEmpty()) {
                                result.clear()
                                VARMESSAGES.forEachIndexed { index, msg ->

                                    val ann_id = msg.ANN_ID
                                    val msg_indo = db.messageDB.List.find {
                                        it.ANN_ID == ann_id && it.Language.equals(
                                            Language.INDONESIA.name,
                                            true
                                        )
                                    }
                                    val msg_eng = db.messageDB.List.find {
                                        it.ANN_ID == ann_id && it.Language.equals(
                                            Language.ENGLISH.name,
                                            true
                                        )
                                    }
                                    val description = msg_indo?.Description ?: msg_eng?.Description ?: "UNKNOWN"
                                    result.append("MSG;$index;$ann_id;$description;")
                                    result.append(msg_indo?.Message_Detail ?:"").append(";")
                                    result.append(msg_eng?.Message_Detail ?:"").append("@")
                                }
                                cb.accept(result.toString())
                            }

                            // append VARAP
                            if (VARAPTOTAL.isNotEmpty()) {
                                result.clear()
                                VARAPTOTAL.forEachIndexed { index, sb ->
                                    result.append("VARAP;$index;${sb.TAG};${sb.Description}@")
                                }
                                cb.accept(result.toString())
                            }

                            // append VARCITY
                            if (VARCITYTOTAL.isNotEmpty()) {
                                result.clear()
                                VARCITYTOTAL.forEachIndexed { index, sb ->
                                    result.append("VARCITY;$index;${sb.TAG};${sb.Description}@")
                                }
                                cb.accept(result.toString())
                            }

                            // append VARPLACES
                            if (VARPLACESTOTAL.isNotEmpty()) {
                                result.clear()
                                VARPLACESTOTAL.forEachIndexed { index, sb ->
                                    result.append("VARPLACES;$index;${sb.TAG};${sb.Description}@")
                                }
                                cb.accept(result.toString())
                            }
                            // append VARSHALAT
                            if (VARSHALATTOTAL.isNotEmpty()) {
                                result.clear()
                                VARSHALATTOTAL.forEachIndexed { index, sb ->
                                    result.append("VARSHALAT;$index;${sb.TAG};${sb.Description}@")
                                }
                                cb.accept(result.toString())
                            }

                            // append VARSEQUENCE
                            if (VARSEQUENCETOTAL.isNotEmpty()) {
                                result.clear()
                                VARSEQUENCETOTAL.forEachIndexed { index, sb ->
                                    result.append("VARSEQUENCE;$index;${sb.TAG};${sb.Description}@")
                                }
                                cb.accept(result.toString())
                            }

                            // append VARREASON
                            if (VARREASONTOTAL.isNotEmpty()) {
                                result.clear()
                                VARREASONTOTAL.forEachIndexed { index, sb ->
                                    result.append("VARREASON;$index;${sb.TAG};${sb.Description}@")
                                }
                                cb.accept(result.toString())
                            }

                            // append VARPROCEDURE
                            if (VARPROCEDURETOTAL.isNotEmpty()) {
                                result.clear()
                                VARPROCEDURETOTAL.forEachIndexed { index, sb ->
                                    result.append("VARPROCEDURE;$index;${sb.TAG};${sb.Description}@")
                                }
                                cb.accept(result.toString())
                            }

                            // append VARGATE
                            if (VARGATETOTAL.isNotEmpty()) {
                                result.clear()
                                VARGATETOTAL.forEachIndexed { index, sb ->
                                    result.append("VARGATE;$index;${sb.TAG};${sb.Description}@")
                                }
                                cb.accept(result.toString())
                            }

                            // append VARCOMPENSATION
                            if (VARCOMPENSATIONTOTAL.isNotEmpty()) {
                                result.clear()
                                VARCOMPENSATIONTOTAL.forEachIndexed { index, sb ->
                                    result.append("VARCOMPENSATION;$index;${sb.TAG};${sb.Description}@")
                                }
                                cb.accept(result.toString())
                            }

                            // append VARGREETING
                            if (VARGREETINGTOTAL.isNotEmpty()) {
                                result.clear()
                                VARGREETINGTOTAL.forEachIndexed { index, sb ->
                                    result.append("VARGREETING;$index;${sb.TAG};${sb.Description}@")
                                }
                                cb.accept(result.toString())
                            }

                            logcb.accept("All variables sent to $key with username $username")
                            return
                        } else logcb.accept("STARTINITIALIZE failed from $key with username $username not found in userDB")
                    } else logcb.accept("STARTINITIALIZE failed from $key with unregistered username $username")
                } else logcb.accept("STARTINITIALIZE failed from $key with empty username")
                cb.accept("STARTINITIALIZE;FALSE@")
            }

            "BROADCASTAND" -> {
                // semi auto dari android, masukin ke queue table
                val desc = parts.getOrElse(1) { "" }
                // language bisa lebih dari satu, dipisah dengan koma
                val lang = parts.getOrElse(2) { "" }.replace(",",";")
                // tags bisa lebih dari satu, dipisah dengan spasi
                val tags = parts.getOrElse(3) { "" }.replace(",",";")
                // zone bisa lebih dari satu, dipisah dengan koma
                val zone = parts.getOrElse(4) { "" }.replace(",",";")
                if (ValidString(desc)){
                    if (ValidString(lang)){
                        if (ValidString(tags)){
                            if (ValidString(zone)){
                                val qt = QueueTable(
                                    0u,
                                    LocalDateTime.now().format(datetimeformat1),
                                    "ANDROID",
                                    "SOUNDBANK",
                                    desc,
                                    tags,
                                    zone,
                                    1u,
                                    lang
                                )
                                if (db.queuetableDB.Add(qt)){
                                    db.queuetableDB.Resort()
                                    logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} inserted. Message: $desc;$lang;$tags;$zone")
                                    cb.accept("BROADCASTAND;OK@")
                                    return
                                } else logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, cannot add to queue table")
                            } else logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty zone")
                        } else logcb.accept("Broadcsast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty tags")
                    } else logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty language")
                } else logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty description")
                cb.accept("BROADCASTAND;NG@")

            }

            else -> {
                logcb.accept("Unknown command from Android: $cmd")
            }

        }
    }

    /**
     * Stop TCP Command Server
     * @return true if succesful
     */
    fun StopTcpCommand(): Boolean {
        try {
            tcpserver.close()
            runBlocking {
                socketMap.values.forEach {
                    it.close()
                }
                socketMap.clear()
                job.join()
            }
            Logger.info { "StopTcpCommand success" }
            return true
        } catch (e: Exception) {
            Logger.error { "Failed to StopTcpServer, Message : ${e.message}" }
        }
        return false
    }
}