package ru.dbotthepony.kstarbound

import com.google.gson.Gson
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.kommons.gson.consumeNull
import java.lang.reflect.ParameterizedType
import kotlin.reflect.KClass

inline fun <reified S : Any> Registry<S>.adapter(): TypeAdapterFactory {
	return RegistryTypeAdapterFactory(this, S::class)
}

class RegistryTypeAdapterFactory<S : Any>(private val registry: Registry<S>, private val clazz: KClass<S>) : TypeAdapterFactory {
	override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
		val subtype = type.type as? ParameterizedType ?: return null
		if (subtype.actualTypeArguments.size != 1 || subtype.actualTypeArguments[0] != clazz.java) return null

		if (type.rawType == Registry.Entry::class.java) {
			return EntryImpl(gson) as TypeAdapter<T>
		} else if (type.rawType == Registry.Ref::class.java) {
			return RefImpl(gson) as TypeAdapter<T>
		}

		return null
	}

	private inner class EntryImpl(gson: Gson) : TypeAdapter<Registry.Entry<S>>() {
		override fun write(out: JsonWriter, value: Registry.Entry<S>?) {
			if (value != null) {
				out.value(value.key)
			} else {
				out.nullValue()
			}
		}

		override fun read(`in`: JsonReader): Registry.Entry<S>? {
			if (`in`.consumeNull()) {
				return null
			} else if (`in`.peek() == JsonToken.STRING) {
				return registry[`in`.nextString()]
			} else if (`in`.peek() == JsonToken.NUMBER) {
				return registry[`in`.nextInt()]
			} else {
				throw JsonSyntaxException("Expected registry key or registry id, got ${`in`.peek()}")
			}
		}
	}

	private inner class RefImpl(gson: Gson) : TypeAdapter<Registry.Ref<S>>() {
		override fun write(out: JsonWriter, value: Registry.Ref<S>?) {
			if (value != null) {
				if (Starbound.IS_LEGACY_JSON) {
					if (value.isPresent) {
						if (value.entry!!.id != null) {
							out.value(value.entry!!.id)
						} else {
							out.value(value.entry!!.key)
						}
					} else {
						value.key.map(out::value, out::value)
					}
				} else {
					// always write string ID if we know it
					if (value.isPresent) {
						out.value(value.entry!!.key)
					} else {
						value.key.map(out::value, out::value)
					}
				}
			} else {
				out.nullValue()
			}
		}

		override fun read(`in`: JsonReader): Registry.Ref<S>? {
			if (`in`.consumeNull()) {
				return null
			} else if (`in`.peek() == JsonToken.STRING) {
				return registry.ref(`in`.nextString())
			} else if (`in`.peek() == JsonToken.NUMBER) {
				return registry.ref(`in`.nextInt())
			} else {
				throw JsonSyntaxException("Expected registry key or registry id, got ${`in`.peek()}")
			}
		}
	}
}
