import com.dowstats.data.rgd.RgdData
import org.junit.jupiter.api.Test
import java.io.DataInputStream
import java.io.File
import java.io.PrintWriter
import java.math.RoundingMode
import java.nio.ByteBuffer
import java.nio.file.Paths

class RgdParserLab {

    val zeroByte: Byte = 0

    val rgdDictionary: MutableMap<Int, String> = mutableMapOf()

    fun readDictionary() {
        File("src/test/resources/RGD_DIC.TXT").forEachLine {
            if(it.isNotEmpty() && it[0] != '#'){
                val kv = it.split('=')
                val key = kv.first().drop(2).decodeHex().getUIntAt(0).toInt()
                val value = kv.last()
                rgdDictionary[key] = value
            }
        }
    }

    private fun String.decodeHex(): ByteArray {
        check(length % 2 == 0) { "Must have an even length" }
        return chunked(2)
            .map { it.toInt(16).toByte() }
            .toByteArray()
    }

    @Test
    fun testSum() {

        readDictionary()



        val path = Paths.get("").toAbsolutePath().toString()
        println(path)
        val bufferedReader: DataInputStream = DataInputStream(File("src/test/resources/chaos_marine_squad.rgd").inputStream())
        val res = bufferedReader.use { it.readAllBytes() }

        println("${res.size} bytes")

        val dataInfo = res.copyOfRange(24, 32)
        println(dataInfo.toString(Charsets.UTF_8))

        val chunkBytes = res.drop(32).toByteArray()

        val version = chunkBytes.copyOfRange(0,4).reversedArray().getUIntAt(0)
        val chunkLength = chunkBytes.copyOfRange(4,8).reversedArray().getUIntAt(0)
        val stringLength = chunkBytes.copyOfRange(8,12).reversedArray().getUIntAt(0).toInt()

        println("version: $version; chunkLength: $chunkLength; stringLength: $stringLength")

        //val sString = chunkBytes.copyOfRange(12, 12 + stringLength).toString(Charsets.UTF_8)
        //val iCRC = chunkBytes.copyOfRange(12 + stringLength, 12 + stringLength + 4).reversedArray().getUIntAt(0)
        val dataLength = chunkBytes.copyOfRange(16 + stringLength, 16 + stringLength + 4).reversedArray().getUIntAt(0).toInt()

        //println("sString: $sString; iCRC: $iCRC; dataLength: $dataLength")

        fun printRgdData(data: List<RgdData>, out: PrintWriter){
            data.sortedBy { it.name }.forEach{
                if(it.value is List<*>){
                    out.println("<details closed>")
                    out.println("<summary>${it.name}</summary>")

                    out.println("<ul>")
                    printRgdData(it.value as List<RgdData>, out)
                    out.println("</ul>")

                    out.println("</details>")
                }else{
                    out.println("<li>${it.name} → ${it.value}</li>")
                }
            }
        }
    }

    private fun handleRgdData(byteArray: ByteArray): List<RgdData> {

        var keyCount = byteArray.copyOfRange(0,4).reversedArray().getUIntAt(0).toInt()
        val totalKeyCount = keyCount
        var offset = 4

        val rgdData = mutableListOf<RgdData>()

        while (keyCount != 0){

            val hash = byteArray.copyOfRange(offset,offset + 4).reversedArray().getUIntAt(0).toInt()
            offset += 4

            val valueName = rgdDictionary[hash] ?: hash.toString()

            val type = byteArray.copyOfRange(offset,offset + 4).reversedArray().getUIntAt(0).toInt()
            offset += 4

            val iOffset = byteArray.copyOfRange(offset,offset + 4).reversedArray().getUIntAt(0).toInt()
            offset += 4
            //println("$space iOffset: $iOffset")

            val dataOffset = totalKeyCount * (3 * 4) + iOffset + 4

            val data: Any = when(type){
                0 -> {// Float
                    val entityData = byteArray.copyOfRange(dataOffset,dataOffset + 4).reversedArray().getFloat()
                    entityData.toBigDecimal().setScale(2, RoundingMode.HALF_UP).toDouble()
                }
                1 -> {// Int
                    val entityData = byteArray.copyOfRange(dataOffset,dataOffset + 8).reversedArray().getUIntAt(0)
                    entityData
                }
                2 -> {// Bool
                    val entityData = byteArray.copyOfRange(dataOffset,dataOffset + 1).map { it }.toString()
                    entityData
                }
                3 -> {// String
                    var zeroByteOffset = 0
                    while(byteArray[dataOffset+zeroByteOffset] != zeroByte){
                        zeroByteOffset++
                    }
                    byteArray.copyOfRange(dataOffset,dataOffset + zeroByteOffset).toString(Charsets.UTF_8)
                }
                4 -> {// String
                    var zeroByteOffset = 0
                    while(byteArray[dataOffset+zeroByteOffset] != zeroByte || byteArray[dataOffset+zeroByteOffset+1] != zeroByte){
                        zeroByteOffset++
                    }
                    zeroByteOffset++
                    byteArray.copyOfRange(dataOffset,dataOffset + zeroByteOffset).toString(Charsets.UTF_16LE)
                }
                100 -> {
                    handleRgdData(byteArray.copyOfRange(dataOffset, byteArray.size))
                }
                else -> 0
            }

            rgdData.add(RgdData(hash, valueName, type, data))
            keyCount--
        }

        return rgdData
    }

    private fun ByteArray.getFloat(): Float  {
        val buffer = ByteBuffer.wrap(this)
        return buffer.float
    }

    private fun ByteArray.getUIntAt(idx: Int): UInt =
        ((this[idx].toUInt() and 0xFFu) shl 24) or
                ((this[idx + 1].toUInt() and 0xFFu) shl 16) or
                ((this[idx + 2].toUInt() and 0xFFu) shl 8) or
                (this[idx + 3].toUInt() and 0xFFu)

}