package ru.dbotthepony.kstarbound.json.factory

import com.github.benmanes.caffeine.cache.Interner
import com.google.gson.Gson
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.objects.*
import ru.dbotthepony.kommons.gson.consumeNull
import java.lang.reflect.ParameterizedType

class MapsTypeAdapterFactory(private val interner: Interner<String>) : TypeAdapterFactory {
	private fun map1(gson: Gson, type: TypeToken<*>, typeValue: TypeToken<*>, factoryHash: () -> Map<Any?, Any?>, factoryTree: () -> Map<Any?, Any?>): TypeAdapter<MutableMap<Any?, Any?>>? {
		val p = type.type as? ParameterizedType ?: return null
		val typeKey = TypeToken.get(p.actualTypeArguments[0])
		val adapterK = gson.getAdapter(typeKey) as TypeAdapter<Any?>
		val adapterV = gson.getAdapter(typeValue) as TypeAdapter<Any?>

		if (typeKey.isAssignableFrom(String::class.java)) {
			return object : TypeAdapter<MutableMap<Any?, Any?>>() {
				override fun write(out: JsonWriter, value: MutableMap<Any?, Any?>?) {
					if (value == null) {
						out.nullValue()
					} else {
						out.beginObject()

						for ((k, v) in value.entries) {
							out.name(k as String)
							adapterV.write(out, v)
						}

						out.endObject()
					}
				}

				override fun read(`in`: JsonReader): MutableMap<Any?, Any?>? {
					if (`in`.consumeNull()) {
						return null
					} else {
						`in`.beginObject()

						val result = factoryTree.invoke() as MutableMap<Any?, Any?>

						while (`in`.hasNext()) {
							result[interner.intern(`in`.nextName())] = adapterV.read(`in`)
						}

						`in`.endObject()
						return result
					}
				}
			}
		} else {
			val factory = if (TypeToken.get(Comparable::class.java).isAssignableFrom(typeKey)) factoryTree else factoryHash

			return object : TypeAdapter<MutableMap<Any?, Any?>>() {
				override fun write(out: JsonWriter, value: MutableMap<Any?, Any?>?) {
					if (value == null) {
						out.nullValue()
					} else {
						out.beginArray()

						for ((k, v) in value.entries) {
							adapterK.write(out, k)
							adapterV.write(out, v)
						}

						out.endArray()
					}
				}

				override fun read(`in`: JsonReader): MutableMap<Any?, Any?>? {
					if (`in`.consumeNull()) {
						return null
					} else {
						`in`.beginArray()

						val result = factory.invoke() as MutableMap<Any?, Any?>

						while (`in`.hasNext()) {
							`in`.beginArray()
							result[adapterK.read(`in`)] = adapterV.read(`in`)
							`in`.endArray()
						}

						`in`.endArray()
						return result
					}
				}
			}
		}
	}

	override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
		return when (type.rawType) {
			Object2IntMap::class.java -> map1(gson, type, TypeToken.get(Int::class.java), ::Object2IntOpenHashMap, ::Object2IntAVLTreeMap)
			Object2ShortMap::class.java -> map1(gson, type, TypeToken.get(Short::class.java), ::Object2ShortOpenHashMap, ::Object2ShortAVLTreeMap)
			Object2LongMap::class.java -> map1(gson, type, TypeToken.get(Long::class.java), ::Object2LongOpenHashMap, ::Object2LongAVLTreeMap)
			Object2BooleanMap::class.java -> map1(gson, type, TypeToken.get(Boolean::class.java), ::Object2BooleanOpenHashMap, ::Object2BooleanAVLTreeMap)
			Object2ByteMap::class.java -> map1(gson, type, TypeToken.get(Byte::class.java), ::Object2ByteOpenHashMap, ::Object2ByteAVLTreeMap)
			Object2CharMap::class.java -> map1(gson, type, TypeToken.get(Char::class.java), ::Object2CharOpenHashMap, ::Object2CharAVLTreeMap)
			Object2DoubleMap::class.java -> map1(gson, type, TypeToken.get(Double::class.java), ::Object2DoubleOpenHashMap, ::Object2DoubleAVLTreeMap)
			else -> null
		} as TypeAdapter<T>?
	}
}
