package ru.dbotthepony.kstarbound.defs

import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonElement
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.JsonToken
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.gson.value
import ru.dbotthepony.kstarbound.util.AssetPathStack

@Deprecated("Don't use directly, use one of subtypes instead")
@Suppress("DEPRECATION")
sealed class JsonReference<E : JsonElement?>(val path: String?, val fullPath: String?) {
	abstract val value: E

	class Element(path: String?, fullPath: String?, override val value: JsonElement?) : JsonReference<JsonElement?>(path, fullPath)
	class Object(path: String?, fullPath: String?, override val value: JsonObject?) : JsonReference<JsonObject?>(path, fullPath)
	class Array(path: String?, fullPath: String?, override val value: JsonArray?) : JsonReference<JsonArray?>(path, fullPath)
	class Primitive(path: String?, fullPath: String?, override val value: JsonPrimitive?) : JsonReference<JsonPrimitive?>(path, fullPath)

	class NElement(path: String?, fullPath: String?, override val value: JsonElement) : JsonReference<JsonElement>(path, fullPath)
	class NObject(path: String?, fullPath: String?, override val value: JsonObject) : JsonReference<JsonObject>(path, fullPath)
	class NArray(path: String?, fullPath: String?, override val value: JsonArray) : JsonReference<JsonArray>(path, fullPath)
	class NPrimitive(path: String?, fullPath: String?, override val value: JsonPrimitive) : JsonReference<JsonPrimitive>(path, fullPath)

	private fun write(writer: JsonWriter) {
		if (fullPath != null) {
			writer.value(fullPath)
		} else {
			writer.value(value)
		}
	}

	override fun toString(): String {
		return "JsonReference[$fullPath / $value]"
	}

	private class Adapter0<E : JsonElement?, J : JsonReference<E>>(gson: Gson, type: TypeToken<E>, private val factory: (String?, String?, E?) -> J) : TypeAdapter<J>() {
		private val adapter = gson.getAdapter(type)

		override fun write(out: JsonWriter, value: J?) {
			if (value == null) {
				out.nullValue()
			} else {
				value.write(out)
			}
		}

		override fun read(`in`: JsonReader): J {
			if (`in`.consumeNull()) {
				return factory(null, null, null)
			} else {
				if (`in`.peek() == JsonToken.STRING) {
					val path = `in`.nextString()
					val full = AssetPathStack.remapSafe(path)
					val get = Starbound.loadJsonAsset(full) ?: return factory(path, full, null)
					return factory(path, full, adapter.fromJsonTree(get))
				} else {
					return factory(null, null, adapter.read(`in`))
				}
			}
		}
	}

	private class Adapter1<E : JsonElement, J : JsonReference<E>>(gson: Gson, type: TypeToken<E>, private val factory: (String?, String?, E) -> J) : TypeAdapter<J>() {
		private val adapter = gson.getAdapter(type)

		override fun write(out: JsonWriter, value: J?) {
			if (value == null) {
				out.nullValue()
			} else {
				value.write(out)
			}
		}

		override fun read(`in`: JsonReader): J {
			if (`in`.consumeNull()) {
				throw JsonSyntaxException("Referenced json element is literal null, which is not allowed")
			} else {
				if (`in`.peek() == JsonToken.STRING) {
					val path = `in`.nextString()
					val full = AssetPathStack.remapSafe(path)
					val get = Starbound.loadJsonAsset(full) ?: throw JsonSyntaxException("Json asset at $full does not exist")
					return factory(path, full, adapter.fromJsonTree(get) ?: throw JsonSyntaxException("Json asset at $full is literal null, which is not allowed"))
				} else {
					return factory(null, null, adapter.read(`in`))
				}
			}
		}
	}

	companion object : TypeAdapterFactory {
		override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
			return when (type.rawType) {
				JsonReference::class.java -> throw IllegalArgumentException("Don't use JsonReference class directly, use one of its subtypes")

				Element::class.java -> Adapter0(gson, TypeToken.get(JsonElement::class.java), ::Element)
				Object::class.java -> Adapter0(gson, TypeToken.get(JsonObject::class.java), ::Object)
				Array::class.java -> Adapter0(gson, TypeToken.get(JsonArray::class.java), ::Array)
				Primitive::class.java -> Adapter0(gson, TypeToken.get(JsonPrimitive::class.java), ::Primitive)

				NElement::class.java -> Adapter1(gson, TypeToken.get(JsonElement::class.java), ::NElement)
				NObject::class.java -> Adapter1(gson, TypeToken.get(JsonObject::class.java), ::NObject)
				NArray::class.java -> Adapter1(gson, TypeToken.get(JsonArray::class.java), ::NArray)
				NPrimitive::class.java -> Adapter1(gson, TypeToken.get(JsonPrimitive::class.java), ::NPrimitive)
				else -> null
			} as TypeAdapter<T>?
		}
	}
}
