<|start_filename|>gson/src/main/kotlin/org/jglrxavpok/hephaistos/json/NBTGsonReader.kt<|end_filename|>
package org.jglrxavpok.hephaistos.json

import com.google.gson.Gson
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import org.jglrxavpok.hephaistos.nbt.*
import java.io.Closeable
import java.io.Reader

class NBTGsonReader(private val reader: Reader): AutoCloseable, Closeable {

    private companion object {
        val GsonInstance = Gson()
    }

    inline fun <reified Tag: NBT> read(): Tag {
        return read(NBTType.byClass<Tag>() ?: error("Invalid NBTType: ${Tag::class.qualifiedName}")) as Tag
    }

    fun <Tag: NBT> read(nbtClass: Class<Tag>): Tag = read(NBTType.byClass(nbtClass) ?: error("Invalid NBTType: ${nbtClass.canonicalName}")) as Tag

    fun guessType(element: JsonElement): NBTType<out NBT> {
        return when {
            element.isJsonObject -> NBTType.TAG_Compound
            element.isJsonNull -> NBTType.TAG_String
            element.isJsonPrimitive -> {
                val primitive = element.asJsonPrimitive
                when {
                    primitive.isBoolean -> NBTType.TAG_Byte
                    primitive.isString -> NBTType.TAG_String
                    primitive.isNumber ->
                        if(primitive.asLong.toDouble() == primitive.asDouble) {
                            if('.' in primitive.asString) {
                                NBTType.TAG_Double
                            } else {
                                NBTType.TAG_Long
                            }
                        } else {
                            NBTType.TAG_Double
                        }
                    else -> error("Primitive that is neither a boolean, a string, nor a number?")
                }
            }

            element.isJsonArray -> {
                val array = element.asJsonArray
                if(array.size() == 0) {
                    NBTType.TAG_List
                } else {
                    val firstElement = element.asJsonArray.get(0)
                    val guessedType = guessType(firstElement)
                    when(guessedType) {
                        NBTType.TAG_Long -> NBTType.TAG_Long_Array
                        NBTType.TAG_Byte -> NBTType.TAG_Byte_Array
                        NBTType.TAG_Int -> NBTType.TAG_Int_Array

                        else -> NBTType.TAG_List
                    }
                }
            }

            else -> error("Unknown json element type $element")
        }
    }

    /**
     * If nbtType is TAG_List, this method will attempt to guess the type of the list elements.
     * The guess is done by getting the first element and guessing its type via #guessType.
     * If the list is empty, this method will always guess that the subtype is TAG_String
     */
    private fun <Tag: NBT> parse(nbtType: NBTType<out NBT>, element: JsonElement): Tag {
        try {
            val result = when (nbtType) {
                NBTType.TAG_End -> NBTEnd
                NBTType.TAG_Byte -> NBT.Byte(element.asByte)
                NBTType.TAG_Short -> NBT.Short(element.asShort)
                NBTType.TAG_Int -> NBT.Int(element.asInt)
                NBTType.TAG_Long -> NBT.Long(element.asLong)
                NBTType.TAG_Float -> NBT.Float(element.asFloat)
                NBTType.TAG_Double -> NBT.Double(element.asDouble)
                NBTType.TAG_Byte_Array -> NBT.ByteArray(*element.asJsonArray.map { it.asByte }.toByteArray())
                NBTType.TAG_String -> if(element.isJsonNull) NBT.String("") else NBT.String(element.asString)

                NBTType.TAG_Compound -> toCompound(element.asJsonObject)

                NBTType.TAG_Int_Array -> NBT.IntArray(*element.asJsonArray.map { it.asInt }.toIntArray())
                NBTType.TAG_Long_Array -> NBT.LongArray(*element.asJsonArray.map { it.asLong }.toLongArray())

                NBTType.TAG_List -> {
                    if (!element.isJsonArray)
                        throw NBTException("Expected a list, but was: $element")
                    val elements = element.asJsonArray

                    if (elements.size() == 0) { // guess strings
                        NBT.List(NBTType.TAG_String)
                    } else {
                        val firstElement = parse<NBT>(guessType(elements[0]), elements[0])
                        val list = NBT.List(firstElement.ID, elements.map { parse(firstElement.ID, it) })
                        list
                    }
                }

                else -> throw IllegalArgumentException("$nbtType is not a valid/supported NBT type")
            }
            return result as Tag
        } catch (e: IllegalStateException) {
            throw NBTException("Failed to load NBT from json", e)
        }
    }

    fun read(nbtType: NBTType<out NBT>): NBT {
        return parse(nbtType, GsonInstance.fromJson(reader, JsonElement::class.java))
    }

    fun readWithGuess(): NBT {
        val element = GsonInstance.fromJson(reader, JsonElement::class.java)
        return parse(guessType(element), element)
    }

    private fun toCompound(jsonObject: JsonObject) = NBT.Kompound {
        for((key, value) in jsonObject.entrySet()) {
            val nbt = parse<NBT>(guessType(value), value)
            this[key] = nbt
        }
    }

    override fun close() {
        reader.close()
    }
}

<|start_filename|>common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTNumber.kt<|end_filename|>
package org.jglrxavpok.hephaistos.nbt

import java.util.*

abstract class NBTNumber<Type: Number>(open val value: Type): NBT {

    override fun hashCode(): Int {
        return Objects.hash(value)
    }

    override fun toString() = toSNBT()

    override fun equals(other: Any?): Boolean {
        if (this === other) return true

        other as NBTNumber<Type>

        if (value != other.value) return false

        return true
    }

}

<|start_filename|>common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTLongArray.kt<|end_filename|>
package org.jglrxavpok.hephaistos.nbt

import org.jglrxavpok.hephaistos.collections.ImmutableLongArray
import java.io.DataInputStream
import java.io.DataOutputStream

class NBTLongArray constructor(val value: ImmutableLongArray) : NBT, Iterable<Long> {

    val size get() = value.size

    override val ID = NBTType.TAG_Long_Array

    constructor(vararg numbers: Long): this(ImmutableLongArray(*numbers))

    override fun writeContents(destination: DataOutputStream) {
        destination.writeInt(size)
        value.forEach(destination::writeLong)
    }

    operator fun get(index: Int) = value[index]

    override fun toSNBT(): String {
        val list = value.joinToString(",") { "${it}L" }
        return "[L;$list]"
    }

    override fun toString() = toSNBT()

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as NBTLongArray

        if (!(value contentEquals other.value)) return false

        return true
    }

    override fun hashCode() = value.hashCode()

    override fun iterator() = value.iterator()

    companion object : NBTReaderCompanion<NBTLongArray> {
        @JvmField
        val EMPTY = NBTLongArray()

        override fun readContents(source: DataInputStream): NBTLongArray {
            val length = source.readInt()
            val value = ImmutableLongArray(length) { source.readLong() }
            return NBTLongArray(value)
        }
    }
}


<|start_filename|>common/src/main/kotlin/org/jglrxavpok/hephaistos/nbt/NBTByteArray.kt<|end_filename|>
package org.jglrxavpok.hephaistos.nbt

import org.jglrxavpok.hephaistos.collections.ImmutableByteArray
import org.jglrxavpok.hephaistos.collections.ImmutableIntArray
import java.io.DataInputStream
import java.io.DataOutputStream

class NBTByteArray constructor(val value: ImmutableByteArray) : NBT, Iterable<Byte> {

    val size get() = value.size

    override val ID = NBTType.TAG_Byte_Array

    constructor(vararg numbers: Byte): this(ImmutableByteArray(*numbers))

    override fun writeContents(destination: DataOutputStream) {
        destination.writeInt(size)
        destination.write(value.copyArray())
    }

    operator fun get(index: Int) = value[index]

    override fun toSNBT(): String {
        val list = value.joinToString(",") { "${it}B" }
        return "[B;$list]"
    }

    override fun toString() = toSNBT()

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as NBTByteArray

        if (!(value contentEquals other.value)) return false

        return true
    }

    override fun hashCode() = value.hashCode()

    override fun iterator() = value.iterator()

    companion object : NBTReaderCompanion<NBTByteArray> {

        @JvmField
        val EMPTY = NBTByteArray(ImmutableByteArray.EMPTY)

        override fun readContents(source: DataInputStream): NBTByteArray {
            val length = source.readInt()
            val value = ImmutableByteArray(length) { source.readByte() }
            return NBTByteArray(value)
        }
    }
}
