package ru.dbotthepony.kstarbound.defs.actor

import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.annotations.JsonAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.io.readBinaryString
import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
import java.io.DataInputStream
import java.io.DataOutputStream

@JsonAdapter(StatModifier.Adapter::class)
data class StatModifier(val stat: String, val value: Double, val type: StatModifierType) {
	class Adapter(gson: Gson) : TypeAdapter<StatModifier>() {
		override fun write(out: JsonWriter, value: StatModifier?) {
			if (value == null) {
				out.nullValue()
			} else {
				out.beginObject()
				out.name("stat")
				out.value(value.stat)
				out.name(value.type.names.first())
				out.value(value.value)
				out.endObject()
			}
		}

		override fun read(`in`: JsonReader): StatModifier? {
			if (`in`.consumeNull()) {
				return null
			} else {
				val read = Starbound.ELEMENTS_ADAPTER.objects.read(`in`)

				val stat = read["stat"]?.asString ?: throw JsonSyntaxException("No stat to modify specified")

				for (type in StatModifierType.entries)
					for (name in type.names)
						if (name in read)
							return StatModifier(stat, read.get(name, 0.0), type)

				throw JsonSyntaxException("Not a stat modifier: $read")
			}
		}
	}

	fun write(stream: DataOutputStream, isLegacy: Boolean) {
		if (isLegacy) {
			when (type) {
				// legacy network protocol doesn't support overall addition
				StatModifierType.OVERALL_ADDITION, StatModifierType.BASE_ADDITION -> stream.writeByte(1)
				StatModifierType.BASE_MULTIPLICATION -> stream.writeByte(2)
				StatModifierType.OVERALL_MULTIPLICATION -> stream.writeByte(3)
			}
		} else {
			stream.writeByte(type.ordinal)
		}

		stream.writeBinaryString(stat)

		if (isLegacy)
			stream.writeFloat(value.toFloat())
		else
			stream.writeDouble(value)
	}

	companion object {
		val CODEC = nativeCodec(::read, StatModifier::write)
		val LEGACY_CODEC = legacyCodec(::read, StatModifier::write)

		fun read(stream: DataInputStream, isLegacy: Boolean): StatModifier {
			if (isLegacy) {
				return when (val type = stream.readUnsignedByte()) {
					0 -> throw UnsupportedOperationException("Empty StatModifier MVariant received on network, this is not supported by this game engine")
					1 -> value(stream.readInternedString(), stream.readFloat().toDouble())
					2 -> multBase(stream.readInternedString(), stream.readFloat().toDouble())
					3 -> multEffective(stream.readInternedString(), stream.readFloat().toDouble())
					else -> throw IllegalArgumentException("Unknown StatModifier type: $type")
				}
			} else {
				val type = StatModifierType.entries[stream.readUnsignedByte()]
				return StatModifier(stream.readInternedString(), stream.readDouble(), type)
			}
		}

		fun value(name: String, value: Double): StatModifier {
			return StatModifier(name, value, StatModifierType.BASE_ADDITION)
		}

		fun multBase(name: String, value: Double): StatModifier {
			return StatModifier(name, value, StatModifierType.BASE_MULTIPLICATION)
		}

		fun valueEffective(name: String, value: Double): StatModifier {
			return StatModifier(name, value, StatModifierType.OVERALL_ADDITION)
		}

		fun multEffective(name: String, value: Double): StatModifier {
			return StatModifier(name, value, StatModifierType.OVERALL_MULTIPLICATION)
		}
	}
}
