package theorycrafter.storage

import theorycrafter.esi.EveSsoTokens
import theorycrafter.utils.StoredCollection
import java.io.DataInput
import java.io.DataOutput


/**
 * The data of a serialized representation of a skill set.
 */
class StoredSkillSet(


    /**
     * The id of the skill set.
     *
     * The value is `null` if it hasn't been written to disk yet.
     */
    id: Int? = null,


    /**
     * The user-provided name of the skill set.
     */
    val name: String,


    /**
     * Maps skill ids to the level of that skill.
     */
    val levelBySkillId: Map<Int, Int>,


    /**
     * The EVE SSO tokens through which the skill levels can be updated.
     *
     * This is `null` for custom skill sets not retrieved from an EVE character.
     */
    val ssoTokens: EveSsoTokens? = null,


    /**
     * The UTC time, in millis, of the last time the skill levels of this character skill set have been updated.
     */
    val lastUpdateTimeUtcMillis: Long? = null


) {


    init {
        if ((id != null) && (id < 0))
            throw IllegalArgumentException("Stored skill sets must have non-negative ids")
    }


    /**
     * The id of the skill set on disk; `null` if it hasn't been saved to disk yet.
     */
    var id: Int? = id
        set(value) {
            if (field != null)
                throw IllegalArgumentException("SkillSet $this already has an id")
            field = value
        }


    /**
     * Returns a copy of this [StoredSkillSet] with the given changes.
     */
    fun copy(
        name: String = this.name,
        eveSsoTokens: EveSsoTokens? = this.ssoTokens,
        levelBySkillId: Map<Int, Int> = this.levelBySkillId,
        lastUpdateTimeUtcMillis: Long? = this.lastUpdateTimeUtcMillis,
    ) = StoredSkillSet(
        id = this.id,
        name = name,
        levelBySkillId = levelBySkillId,
        ssoTokens = eveSsoTokens,
        lastUpdateTimeUtcMillis = lastUpdateTimeUtcMillis,
    )


    override fun toString(): String {
        return "StoredSkillSet($name, id=$id)"
    }


    /**
     * A [StoredCollection.Serializer] for [StoredSkillSet]s.
     */
    object Serializer : StoredCollection.Serializer<StoredSkillSet> {


        // This shouldn't actually ever be called because the fits are serialized by the FitRepoItem serializer
        // (in FittingRepository.kt)
        override val itemFormatVersion
            get() = error("StoredSkillSet format version is determined by the FitRepoItem serializer")


        /**
         * Writes an optional [EveSsoTokens].
         */
        private fun DataOutput.writeOptionalSsoTokens(eveSsoTokens: EveSsoTokens?) {
            writeBoolean(eveSsoTokens != null)
            if (eveSsoTokens != null) {
                writeInt(EveSsoTokensSerializer.itemFormatVersion)
                EveSsoTokensSerializer.writeItem(this, eveSsoTokens)
            }
        }


        /**
         * Reads an optional [EveSsoTokens].
         */
        private fun DataInput.readOptionalSsoTokens(formatVersion: Int): EveSsoTokens? {
            if (!readBoolean())
                return null

            if (formatVersion == 1)
                return EveSsoTokensSerializer.readItem(this, 1)

            val ssoTokensFormatVersion = readInt()
            return EveSsoTokensSerializer.readItem(this, ssoTokensFormatVersion)
        }


        override fun readItem(input: DataInput, formatVersion: Int): StoredSkillSet {
            with(input) {
                val id = readInt()
                val name = readUTF()
                val eveSsoTokens = readOptionalSsoTokens(formatVersion)
                val lastUpdateTimeUtcMillis = readLong().takeIf { it >= 0 }
                val count = readUnsignedShort()
                val levelBySkillId = buildMap {
                    repeat(count) {
                        val skillId = readInt()
                        val level = readUnsignedByte()
                        put(skillId, level)
                    }
                }
                return StoredSkillSet(
                    id = id,
                    name = name,
                    levelBySkillId = levelBySkillId,
                    ssoTokens = eveSsoTokens,
                    lastUpdateTimeUtcMillis = lastUpdateTimeUtcMillis
                )
            }
        }


        override fun writeItem(output: DataOutput, item: StoredSkillSet) {
            if (item.id == null)
                throw IllegalArgumentException("Can't write skill set without an id")

            with(output) {
                writeInt(item.id!!)
                writeUTF(item.name)
                writeOptionalSsoTokens(item.ssoTokens)
                writeLong(item.lastUpdateTimeUtcMillis ?: -1)
                writeShort(item.levelBySkillId.size)
                for ((skillId, level) in item.levelBySkillId) {
                    writeInt(skillId)
                    writeByte(level)
                }
            }
        }


    }


}


