package ru.dbotthepony.kstarbound.defs.world

import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.gson.value
import ru.dbotthepony.kommons.io.readByteArray
import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.io.writeByteArray
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.readJsonElement
import ru.dbotthepony.kstarbound.json.writeJsonElement
import ru.dbotthepony.kstarbound.util.random.random
import ru.dbotthepony.kstarbound.world.UniversePos
import java.io.DataInputStream
import java.io.DataOutputStream

class CelestialParameters private constructor(val coordinate: UniversePos, val seed: Long, val name: String, val parameters: JsonObject, marker: Nothing?) {
	constructor(coordinate: UniversePos, seed: Long, name: String, parameters: JsonObject) : this(coordinate, seed, name, parameters, null) {
		val worldType = parameters["worldType"]?.asString

		if (worldType != null) {
			visitableParameters = when (worldType.lowercase()) {
				"terrestrial" -> {
					val random = random(seed)
					val worldSize = parameters["worldSize"].asString
					val type = parameters["terrestrialType"]

					if (type is JsonArray) {
						TerrestrialWorldParameters.generate(type.random(random).asString, worldSize, random)
					} else if (type is JsonPrimitive) {
						TerrestrialWorldParameters.generate(type.asString, worldSize, random)
					} else {
						throw JsonSyntaxException("Invalid terrestrialType: $type")
					}
				}

				"asteroids" -> {
					AsteroidsWorldParameters.generate(seed)
				}

				"floatingdungeon" -> {
					FloatingDungeonWorldParameters.generate(parameters["dungeonWorld"].asString)
				}

				else -> null
			}
		}
	}

	constructor(coordinate: UniversePos, seed: Long, name: String, parameters: JsonObject, visitableParameters: VisitableWorldParameters?) : this(coordinate, seed, name, parameters, null) {
		this.visitableParameters = visitableParameters
	}

	private constructor(stream: DataInputStream, isLegacy: Boolean) : this(
		UniversePos(stream, isLegacy),
		stream.readLong(),
		stream.readInternedString(),
		stream.readJsonElement() as JsonObject,
		VisitableWorldParameters.fromNetwork(stream, isLegacy)
	)

	private fun write0(stream: DataOutputStream, isLegacy: Boolean) {
		coordinate.write(stream, isLegacy)
		stream.writeLong(seed)
		stream.writeBinaryString(name)
		stream.writeJsonElement(parameters)

		val visitableParameters = visitableParameters

		if (visitableParameters == null) {
			stream.writeBoolean(false)
		} else {
			visitableParameters.write(stream, isLegacy)
		}
	}

	fun write(stream: DataOutputStream, isLegacy: Boolean) {
		if (isLegacy) {
			// holy fucking shit
			val wrap = FastByteArrayOutputStream()
			write0(DataOutputStream(wrap), true)
			stream.writeByteArray(wrap.array, 0, wrap.length)
		} else {
			write0(stream, false)
		}
	}

	var visitableParameters: VisitableWorldParameters? = null
		private set

	@JsonFactory
	data class StoreData(val coordinate: UniversePos, val seed: Long, val name: String, val parameters: JsonObject, val visitableParameters: VisitableWorldParameters? = null)

	class Adapter(gson: Gson) : TypeAdapter<CelestialParameters>() {
		private val data = gson.getAdapter(StoreData::class.java)

		override fun write(out: JsonWriter, value: CelestialParameters?) {
			if (value == null)
				out.nullValue()
			else {
				data.write(out, StoreData(value.coordinate, value.seed, value.name, value.parameters, value.visitableParameters))
			}
		}

		override fun read(`in`: JsonReader): CelestialParameters? {
			if (`in`.consumeNull())
				return null

			val read = data.read(`in`)
			return CelestialParameters(read.coordinate, read.seed, read.name, read.parameters, read.visitableParameters)
		}
	}

	companion object {
		fun read(stream: DataInputStream, isLegacy: Boolean): CelestialParameters {
			if (isLegacy) {
				val wrap = FastByteArrayInputStream(stream.readByteArray())
				return CelestialParameters(DataInputStream(wrap), true)
			} else {
				return CelestialParameters(stream, false)
			}
		}
	}
}

