package com.dowstats.service.w40k

import com.dowstats.data.rgd.RgdData
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import java.io.DataInputStream
import java.io.File
import java.math.RoundingMode
import java.nio.ByteBuffer

@Service
class RgdParserService @Autowired constructor(
) {

    val log = LoggerFactory.getLogger(RgdParserService::class.java)

    val zeroByte: Byte = 0

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

    init {
        RgdParserService::class.java.getClassLoader().getResourceAsStream("RGD_DIC.TXT").bufferedReader(Charsets.UTF_8).lines().forEach {
            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
            }
        }
    }

    fun parseFolderToRgdFiles(folderPath: String, ignoreFiles: List<String> = emptyList()): Map<String, List<RgdData>> =
        File(folderPath).walkTopDown().map {fileOrFolder ->
            val rgdData = if(fileOrFolder.isFile && !ignoreFiles.any { fileOrFolder.name.contains(it)  } && fileOrFolder.name.endsWith(".rgd")){
                log.info("Parce file ${fileOrFolder.name}")
                parseRgdFileStream(DataInputStream(fileOrFolder.inputStream()))
            }   else {
                null
            }
            fileOrFolder.name to rgdData
        }.filter { it.second != null }
            .map { it.first to it.second!! }
            .toMap()


    fun parseRgdFileStream(dataInputStream: DataInputStream): List<RgdData> {
        val res = dataInputStream.use { it.readAllBytes() }

        val dataInfo = res.copyOfRange(24, 32)

        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()


        val dataLength =
            chunkBytes.copyOfRange(16 + stringLength, 16 + stringLength + 4).reversedArray().getUIntAt(0).toInt()

        return handleRgdData(chunkBytes.copyOfRange(20 + stringLength, 20 + stringLength + dataLength).toList())

    }

    private fun handleRgdData(byteList: List<Byte>): List<RgdData> {

        var keyCount = byteList.subList(0,4).getInt()
        val totalKeyCount = keyCount
        var offset = 4

        val rgdData = mutableListOf<RgdData>()

        while (keyCount != 0) {

            val hash = byteList.subList(offset, offset + 4).getInt()
            offset += 4

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

            val type = byteList.subList(offset, offset + 4).getInt()
            offset += 4

            val iOffset = byteList.subList(offset, offset + 4).getInt()
            offset += 4

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

            val data: Any = when (type) {
                0 -> { // Float
                    val entityData = byteList.subList(dataOffset, dataOffset + 4).toByteArray().reversedArray().getFloat()
                    entityData.toBigDecimal().setScale(4, RoundingMode.HALF_UP).toDouble()
                }
                1 -> { // Int
                    val entityData = byteList.subList(dataOffset, dataOffset + 8).getInt()
                    entityData
                }
                2 -> { // Bool
                    byteList.subList(dataOffset, dataOffset + 1).first() != 0.toByte()
                }
                3 -> { // String
                    var zeroByteOffset = 0
                    while (byteList[dataOffset + zeroByteOffset] != zeroByte) {
                        zeroByteOffset++
                    }
                    byteList.subList(dataOffset, dataOffset + zeroByteOffset).toByteArray().toString(Charsets.UTF_8)
                }
                4 -> { // String
                    var zeroByteOffset = 0
                    while (byteList[dataOffset + zeroByteOffset] != zeroByte || byteList[dataOffset + zeroByteOffset + 1] != zeroByte) {
                        zeroByteOffset++
                    }
                    zeroByteOffset++
                    byteList.subList(dataOffset, dataOffset + zeroByteOffset).toByteArray().toString(Charsets.UTF_16LE)
                }
                100 -> {
                    handleRgdData(byteList.subList(dataOffset, byteList.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 List<Byte>.getInt(): Int {
        var result = 0.toUInt()
        var shift = 0
        for (byte in this) {
            result = result or ((byte.toInt().toUInt() and 0xFFu) shl shift)
            shift += 8
        }
        return result.toInt()
    }

    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)

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