package com.laseroptek.raman.utils.ext

import com.laseroptek.raman.const.LASER_STATUS
import com.laseroptek.raman.const.TX_RX
import com.laseroptek.raman.data.model.SerialResult
import com.laseroptek.raman.data.model.serial.Alert
import com.laseroptek.raman.data.model.serial.DcdGas
import com.laseroptek.raman.data.model.serial.DcdGasOnOff
import com.laseroptek.raman.data.model.serial.EnergyControl
import com.laseroptek.raman.data.model.serial.EnergyHandpiece
import com.laseroptek.raman.data.model.serial.EnergyMeasured
import com.laseroptek.raman.data.model.serial.EnergyVersion
import com.laseroptek.raman.data.model.serial.GuideBeam
import com.laseroptek.raman.data.model.serial.HPShotCount
import com.laseroptek.raman.data.model.serial.HandPiece
import com.laseroptek.raman.data.model.serial.LaserStatus
import com.laseroptek.raman.data.model.serial.Oven
import com.laseroptek.raman.data.model.serial.Packet
import com.laseroptek.raman.data.model.serial.PurgeBubble
import com.laseroptek.raman.data.model.serial.QSwitch
import com.laseroptek.raman.data.model.serial.SprayDcd
import com.laseroptek.raman.data.model.serial.Temperature
import com.laseroptek.raman.data.model.serial.Version
import timber.log.Timber

// convert Packet to ByteArray (Packet -> ByteArray)
// 1. 우선 stx ~ etx 까지 (데이터가 한번에 하나씩 오는 것으로 가정)
// 추후 예외 상황 발생시 아래 상황 처리 필요.
// 1) 한번 수신시 두개 이상의 stx ~ etx 가 있는 경우
// 2) stx만 있는 경우. (중간에 잘려서 etx가 다음에 오는 경우)
// 3) 오류는 포맷이 맞지 않거나 기타.
fun ByteArray.toPacketResult(): SerialResult<Packet?> {
    val stxIndex = this.indexOfFirst { it.toInt() == 0x21 }
    val etxIndex = this.indexOfLast { it.toInt() == 0x0d }
    if ((this.size >= 5) && (stxIndex != -1) && (etxIndex != 1) && ((etxIndex - stxIndex) >= 4) ) {
        val packet = this.sliceArray(stxIndex..etxIndex).toPacket()
        if (packet != null) {
            val calCS = (packet.cmd + packet.data).toCS()

            Timber.d("packet cs: 0x%X, 0x%x}", packet.cs[0], packet.cs[1])
            Timber.d("calcul cs: 0x%X, 0x%x}", calCS[0], calCS[1])

            if (packet.cs.contentEquals( calCS )) {
                Timber.d("OK")
                return SerialResult.Success(packet)
            } else {
                Timber.d("NOK")
            }
        }
    }

    return  SerialResult.Error(Exception("Packet Error"))
}

//
// rxPacket() 에서 사용
//

private fun ByteArray.toPacket(): Packet? {
    val packet = if (this.size > 4)
        Packet(
            this.sliceArray(0..0),  // stx (0x21)
            this.sliceArray(1..1),  // cmd
            if (this.size > 5) this.sliceArray(2..(this.size - 4)) else byteArrayOf(),    // data
            this.sliceArray((this.size - 3)..(this.size - 2)),  // cs
            this.sliceArray((this.size - 1)..(this.size - 1)),  // etx (0x0D)
        ) else null
    return packet
}

// Packet to ByteArray
fun Packet.toByteArray(): ByteArray {
    val packet = this.stx + this.cmd + this.data + (this.cmd + this.data).toCS() + this.etx
    return packet
}

// get cs with (cmd + data) byte array to check packet is valid or not
// divide Byte with upper and lower 4 bit and ita (+0x30) each value
fun ByteArray.toCS(): ByteArray {
    return this.sum().toByte().toHexCharBytes()
}

//
// rxPacket data parsing
//

fun ByteArray.toVersion(): Version {
    val version = if (this.size == 16) {
        Version(
            String(this.sliceArray(0..3)),
            String(this.sliceArray(4..7)),
            String(this.sliceArray(8..11)),
            String(this.sliceArray(12..15)),
        )
    } else Version()

    return version
}

fun ByteArray.toHpShotCount(): HPShotCount {
    val hpCount = if (this.size == 8) {
        HPShotCount(
            String(this.sliceArray(0..7)).toIntOrNull() ?: 0
        )
    } else HPShotCount()
    return hpCount
}

fun ByteArray.toLaserStatus(): LaserStatus {
    val laserStatus =
        if (this.size == 1) {
            LaserStatus(
                this.sliceArray(0..0).toInt()
            )
        } else if (this.size == 12) {
            LaserStatus(
                this.sliceArray(0..0).toInt()
                , String(this.sliceArray(1..3)).toIntOrNull() ?: 0
                , String(this.sliceArray(4..7)).toFloatOrNull() ?: 00.0f
                , String(this.sliceArray(8..11)).toFloatOrNull() ?: 0.0f
            )
        } else LaserStatus()
    return laserStatus
}

fun ByteArray.toPurgeBubble(): PurgeBubble {
    val purgeBubble = if (this.size == 1) {
            PurgeBubble(
                this.sliceArray(0..0).toInt()
            )
        } else PurgeBubble()
    return purgeBubble
}

fun ByteArray.toDcdGas(): DcdGas {
    val dcdGas =
        if (this.size == 1) {
            DcdGas(
                this.sliceArray(0..0).toInt()
            )
        } else if (this.size == 2) {
            DcdGas(
                status =  this.sliceArray(0..0).toInt()
                ,ok =  this.sliceArray(1..1).toInt()
            )
        } else if (this.size == 5) {
            DcdGas(
                this.sliceArray(0..0).toInt()
                , String(this.sliceArray(1..3)).toFloatOrNull() ?: 00.0f
            )
        } else DcdGas()
    return dcdGas
}

fun ByteArray.toSprayDcd(): SprayDcd {
    val sprayDcd = if (this.size == 7) {
        SprayDcd(
            this.sliceArray(0..0).toInt()
            , String(this.sliceArray(1..3)).toIntOrNull() ?: 0
            , String(this.sliceArray(4..6)).toIntOrNull() ?: 0
        )
    } else SprayDcd()
    return sprayDcd
}

fun ByteArray.toHandPiece(): HandPiece {
    val handPiece = if (this.size == 2) {
        HandPiece(
            String(this.sliceArray(0..1)).toIntOrNull() ?: 0
        )
    } else HandPiece()
    return handPiece
}

fun ByteArray.toTemperature(): Temperature {
    val temperature = if (this.size >= 35) {
        Temperature(
            String(this.sliceArray(0..3)).toFloatOrNull() ?: 00.0f
            ,String(this.sliceArray(4..7)).toFloatOrNull() ?: 00.0f
            ,String(this.sliceArray(8..11)).toFloatOrNull() ?: 00.0f
            ,String(this.sliceArray(12..15)).toFloatOrNull() ?: 00.0f
            ,String(this.sliceArray(16..19)).toFloatOrNull() ?: 00.0f
            ,String(this.sliceArray(20..23)).toFloatOrNull() ?: 00.0f
            ,String(this.sliceArray(24..27)).toFloatOrNull() ?: 00.0f
            ,String(this.sliceArray(28..31)).toFloatOrNull() ?: 00.0f
            ,String(this.sliceArray(32..35)).toFloatOrNull() ?: 00.0f
        )
    } else Temperature()

    return temperature
}

fun ByteArray.toOven(): Oven {
    val oven = if (this.size == 5) {
        Oven(
            String(this.sliceArray(0..3)).toByteOrNull() ?: 0
            ,String(this.sliceArray(4..7)).toFloatOrNull() ?: 00.0f
        )
    } else Oven()

    return oven
}

fun ByteArray.toQSwitch(): QSwitch {
    val qswitch = if (this.size == 10) {
        QSwitch(
            String(this.sliceArray(0..4)).toFloatOrNull() ?: 000.0f
            ,String(this.sliceArray(5..9)).toFloatOrNull() ?: 000.0f
        )
    } else QSwitch()

    return qswitch
}

// EneryDetect - type: 0x01 (Version)
fun ByteArray.toEnergyVersion(): EnergyVersion {
    val energyVersion = if (this.size == 9) {
        EnergyVersion(
            this.sliceArray(0..0).toInt(),
            String(this.sliceArray(1..4)),
            String(this.sliceArray(5..8))
        )
    } else EnergyVersion()

    return energyVersion
}

// EneryDetect - type: 0x02 (Remove the handpiece in mount)(Check if insert in mount)
fun ByteArray.toEnergyHandpiece(): EnergyHandpiece {
    /* debug only
    var hexData = ""
    for (i in 0 .. this.size -1) {
        hexData += "0x%02X ".format(this[i])
    }
    Timber.d("size: ${this.size} hexData: $hexData")
    Timber.d("status: ${this.sliceArray(1..1).toInt()} ")
    */

    val energyHandpiece = if (this.size == 2) {
        EnergyHandpiece(
            this.sliceArray(0..0).toInt(),
            this.sliceArray(1..1).toInt()
        )
    } else EnergyHandpiece()

    return energyHandpiece
}

// EneryDetect - type: 0x03 (Control to detect energy)
fun ByteArray.toEnergyControl(): EnergyControl {
    val energyControl = if (this.size == 2) {
        EnergyControl(
            this.sliceArray(0..0).toInt(),
            this.sliceArray(1..1).toInt()
        )
    } else EnergyControl()

    return energyControl
}

// EneryDetect - type: 0x04 (Transmit the measured data)
fun ByteArray.toEnergyMeasured(): EnergyMeasured {
    val energyMeasured = if (this.size == 2) {
        EnergyMeasured(
            this.sliceArray(0..0).toInt(),
            this.sliceArray(1..1).toInt()
        )
    } else if (this.size == 10) {
        EnergyMeasured(
            this.sliceArray(0..0).toInt(),
            this.sliceArray(1..1).toInt(),
            String(this.sliceArray(2..9)).toIntOrNull() ?: 0
        )
    } else if (this.size == 20) {
        EnergyMeasured(
            this.sliceArray(0..0).toInt(),
            this.sliceArray(1..1).toInt(),
            i = this.sliceArray(2..2).toInt(),
            String(this.sliceArray(3..10)).toIntOrNull() ?: 0,
            e = this.sliceArray(11..11).toInt(),
            String(this.sliceArray(12..19)).toIntOrNull() ?: 0,
        )
    }
    else EnergyMeasured()

    return energyMeasured
}

fun ByteArray.toError(): Alert {
    val error = if (this.size == 2) {
        Alert(
            String(this.sliceArray(0..1)).toIntOrNull() ?: 0
        )
    } else Alert()

    return error
}

fun ByteArray.toWarning(): Alert {
    val alert = if (this.size == 2) {
        Alert(
            String(this.sliceArray(0..1)).toIntOrNull() ?: 0
        )
    } else Alert()

    return alert
}


fun ByteArray.printHex(
    txRx: String = "-",
    rw: String = "-",
    cmd: String = "-"
) {
    var hexStringPerLine = ""

    Timber.d("[%s] (%s) %s (%02d) Bytes ---------",
        txRx,
        rw,
        cmd,
        this.size
    )

    this.forEachIndexed { index, data ->
        val hex = "%02X".format(data)
        hexStringPerLine += hex

        if ((index+1) % 16 == 0 || (index+1) == this.size) {
            // --- THE FIX ---
            // Provide both arguments required by the format string:
            // 1. The integer for "%02d"
            // 2. The string for "%s" (using %s is safer than %2s if length varies)
            Timber.d( "[%02d] %s", (index/16 + 1), hexStringPerLine )
            // --- END OF FIX ---
            hexStringPerLine = "" // init
        } else {
            hexStringPerLine += " "
        }
    }
    Timber.d("%s", "--".repeat(26))
    Timber.d("\n")
}

fun ByteArray.getHexString(
    txRx: TX_RX = TX_RX.NONE
) : String {
    var hexStringPerLine = ""

    this.forEachIndexed { index, data ->
        val hex = "%02X ".format(data)
        hexStringPerLine += hex
    }

    if (txRx == TX_RX.RX) {
        hexStringPerLine = "RX: " + hexStringPerLine
    } else if (txRx == TX_RX.TX) {
        hexStringPerLine = "TX: " + hexStringPerLine
    } else {
        //hexStringPerLine = hexStringPerLine
    }

    return hexStringPerLine
}


//
// txPacket data parsing
//

fun LaserStatus.toByteArray(): ByteArray {
    if (this.laserStatus == LASER_STATUS.READY) {
        val onTimeIntegerPart = this.onTime.toInt()
        val onTimeFractionPart = ((this.onTime - onTimeIntegerPart) * 10).toInt()
        val frequenceIntegerPart = this.frequence.toInt()
        val frequenceFractionPart = ((this.frequence - frequenceIntegerPart) * 10).toInt()

        val laserStatusArray = byteArrayOf(this.laserStatus.toByte())
        val vol3figureAsciiArray = byteArrayOf(
            ((voltage.getNthDigit(2) + 0x30) and 0xFF).toByte(),
            ((voltage.getNthDigit(1) + 0x30) and 0xFF).toByte(),
            ((voltage.getNthDigit(0) + 0x30) and 0xFF).toByte(),
        )
        val onTimeArray = byteArrayOf(
            ((onTimeIntegerPart.getNthDigit(1) + 0x30) and 0xFF).toByte(),   // 10의 자리 (100으로 나눈 나머지)
            ((onTimeIntegerPart.getNthDigit(0) + 0x30) and 0xFF).toByte(),    // 1의 자리 (10으로 나눈 나머지)
            "."[0].code.toByte(), // ascii: 0x2E
            ((onTimeFractionPart + 0x30) and 0xFF).toByte(),
        )
        val frequenceArray = byteArrayOf(
            ((frequenceIntegerPart.getNthDigit(1) + 0x30) and 0xFF).toByte(),
            ((frequenceIntegerPart.getNthDigit(0) + 0x30) and 0xFF).toByte(),
            "."[0].code.toByte(), // ascii: 0x2E
            ((frequenceFractionPart + 0x30) and 0xFF).toByte()
        )
        return laserStatusArray + vol3figureAsciiArray + onTimeArray + frequenceArray
    } else {
        return byteArrayOf(this.laserStatus.toByte())
    }
}

fun EnergyControl.toByteArray(): ByteArray {
    val typeArray = byteArrayOf(this.type.toByte())
    val statusArray = byteArrayOf(this.status.toByte())

    if (voltage == 0) {
        return typeArray + statusArray
    } else {
        val vol3figureAsciiArray = byteArrayOf(
            ((voltage.getNthDigit(2) + 0x30) and 0xFF).toByte(),
            ((voltage.getNthDigit(1) + 0x30) and 0xFF).toByte(),
            ((voltage.getNthDigit(0) + 0x30) and 0xFF).toByte(),
        )
        return typeArray + statusArray + vol3figureAsciiArray
    }
}

fun PurgeBubble.toByteArray(): ByteArray {
    return byteArrayOf(this.value.toByte())
}

fun DcdGasOnOff.toByteArray(): ByteArray {
    return byteArrayOf(this.status.toByte())
}

fun DcdGas.toByteArray(): ByteArray {
    val pressureIntegerPart = this.pressure.toInt()
    val pressureFractionPart = ((this.pressure - pressureIntegerPart) * 10).toInt()

    val onOffArray = byteArrayOf(this.status.toByte())
    val pressureArray = byteArrayOf(
        ((pressureIntegerPart.getNthDigit(1) + 0x30) and 0xFF).toByte(),
        ((pressureIntegerPart.getNthDigit(0) + 0x30) and 0xFF).toByte(),
        "."[0].code.toByte(), // ascii: 0x2E
        ((pressureFractionPart + 0x30) and 0xFF).toByte()
    )
    val okArray = byteArrayOf(this.ok.toByte())

    return if (this.ok == 0x00) {
        (onOffArray + pressureArray)
    } else {
        (onOffArray + okArray)
    }
}

fun SprayDcd.toByteArray(): ByteArray {
    val onOffArray = byteArrayOf(this.status.toByte())
    val sprayTimeArray = byteArrayOf(
        ((this.sprayTime.getNthDigit(2) + 0x30) and 0xFF).toByte(),
        ((this.sprayTime.getNthDigit(1) + 0x30) and 0xFF).toByte(),
        ((this.sprayTime.getNthDigit(0) + 0x30) and 0xFF).toByte(),
    )
    val sprayDelayArray = byteArrayOf(
        ((this.sprayDelay.getNthDigit(2) + 0x30) and 0xFF).toByte(),
        ((this.sprayDelay.getNthDigit(1) + 0x30) and 0xFF).toByte(),
        ((this.sprayDelay.getNthDigit(0) + 0x30) and 0xFF).toByte(),
    )
    return onOffArray + sprayTimeArray + sprayDelayArray
}

fun GuideBeam.toByteArray(): ByteArray {
    val arrayValue = byteArrayOf(
        ((this.value.getNthDigit(3) + 0x30) and 0xFF).toByte(),
        ((this.value.getNthDigit(2) + 0x30) and 0xFF).toByte(),
        ((this.value.getNthDigit(1) + 0x30) and 0xFF).toByte(),
        ((this.value.getNthDigit(0) + 0x30) and 0xFF).toByte(),
    )

    return arrayValue
}

fun Oven.toByteArray(): ByteArray {
    val ktpIntegerPart = this.ktp.toInt()
    val ktpFractionPart = ((this.ktp - ktpIntegerPart) * 10).toInt()

    val typeArray = byteArrayOf(this.type)
    val pressureArray = byteArrayOf(
        ((ktpIntegerPart.getNthDigit(1) + 0x30) and 0xFF).toByte(),
        ((ktpIntegerPart.getNthDigit(0) + 0x30) and 0xFF).toByte(),
        "."[0].code.toByte(), // ascii: 0x2E
        ((ktpFractionPart + 0x30) and 0xFF).toByte()
    )

    return typeArray + pressureArray
}

fun QSwitch.toByteArray(): ByteArray {
    val delayTimeIntegerPart = this.delayTime.toInt()
    val delayTimeFractionPart = ((this.delayTime - delayTimeIntegerPart) * 10).toInt()
    val intervalTimeIntegerPart = this.intervalTime.toInt()
    val intervalTimeFractionPart = ((this.intervalTime - delayTimeIntegerPart) * 10).toInt()

    val delayTimeArray = byteArrayOf(
        ((delayTimeIntegerPart.getNthDigit(2) + 0x30) and 0xFF).toByte(),
        ((delayTimeIntegerPart.getNthDigit(1) + 0x30) and 0xFF).toByte(),
        ((delayTimeIntegerPart.getNthDigit(0) + 0x30) and 0xFF).toByte(),
        "."[0].code.toByte(), // ascii: 0x2E
        ((delayTimeFractionPart + 0x30) and 0xFF).toByte()
    )
    val intervalTimeArray = byteArrayOf(
        ((intervalTimeIntegerPart.getNthDigit(2) + 0x30) and 0xFF).toByte(),
        ((intervalTimeIntegerPart.getNthDigit(1) + 0x30) and 0xFF).toByte(),
        ((intervalTimeIntegerPart.getNthDigit(0) + 0x30) and 0xFF).toByte(),
        "."[0].code.toByte(), // ascii: 0x2E
        ((intervalTimeFractionPart + 0x30) and 0xFF).toByte()
    )

    return delayTimeArray + intervalTimeArray
}

//
// 공통 함수
//

// 정수에서 N 번쨰 자리 수 구하기
fun Int.getNthDigit(n: Int): Int {
    return (this / Math.pow(10.0, n.toDouble()) % 10).toInt()
}

// display each versions
fun String.toVersionString(): String {
    var version = ""
    this.forEachIndexed { index, char ->
        version += char.toString()
        if (index < this.length - 1) {
            version += "."
        }
    }
    return version
}

fun String.toVersionByteArray(): ByteArray {
    var version = ""
    this.forEachIndexed { index, char ->
        version += char.toString()
        if (char.toString() != "." ) {
            version += char
        }
    }
    return version.toByteArray()
}

fun Int.toHex(int: Int): String {
    return int.toString(16)
}

fun Byte.toHexCharBytes(): ByteArray {
    val hexString = "%02X".format(this) // Convert to hex string
    val highNibble = hexString[0].toChar().code // Get ASCII code of high nibble
    val lowNibble = hexString[1].toChar().code // Get ASCII code of low nibble
    return byteArrayOf(highNibble.toByte(), lowNibble.toByte()) // Create byte array
}

fun ByteArray.toInt(): Int {
    require(this.size <= 4) { "ByteArray size must be at most 4 bytes" }
    var result = 0
    for (i in this.indices) {
        result = (result shl 8) or (this[i].toInt() and 0xFF)
    }
    return result
}

fun Int.clearNthBit(nth: Int): Int {
    val mask = (1 shl nth).inv() // Create a mask with the nth bit set to 0
    return this and mask // Apply the mask to clear the nth bit
}

fun Int.mask(flag: Int): Int {
    val mask = (flag).inv() // Create a mask with the nth bit set to 0
    return this and mask // Apply the mask to clear the nth bit
}

// convert ByteArray to Version (No Send Version CMD)
// fun Version.toByteArray(): ByteArray { }

/*
fun Byte.toHexString(): String {
    return this.toUByte().toString(16).padStart(2, '0').uppercase()
}

@OptIn(ExperimentalUnsignedTypes::class)
fun ULong.toByte12(): UByteArray {
    var hex = this.toString()
    var hexUByte:UByteArray = UByteArray(12)

    for(i in 0 until (12-hex.length))
        hex = "0" + hex

    for(i in 0 until hexUByte.size)
        hexUByte[i] = hex[i].code.toUByte()

    return hexUByte
}

@SuppressLint("DefaultLocale")
@OptIn(ExperimentalUnsignedTypes::class)
fun UShort.toByte4(): UByteArray {
    val hex = String.format("%04d", this.toInt())
    var hexUByte:UByteArray = ubyteArrayOf(0x00u, 0x00u, 0x00u, 0x00u)

    hexUByte[0] = hex[0].code.toUByte()
    hexUByte[1] = hex[1].code.toUByte()
    hexUByte[2] = hex[2].code.toUByte()
    hexUByte[3] = hex[3].code.toUByte()
    return hexUByte
}

@SuppressLint("DefaultLocale")
@OptIn(ExperimentalUnsignedTypes::class)
fun UShort.toByte3(): UByteArray {
    val hex = String.format("%03d", this.toInt())
    var hexUByte:UByteArray = ubyteArrayOf(0x00u, 0x00u, 0x00u)

    hexUByte[0] = hex[0].code.toUByte()
    hexUByte[1] = hex[1].code.toUByte()
    hexUByte[2] = hex[2].code.toUByte()
    return hexUByte
}
*/