package ru.dbotthepony.kstarbound.json.factory

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.JsonToken
import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.doubles.DoubleArrayList
import it.unimi.dsi.fastutil.doubles.DoubleArraySet
import it.unimi.dsi.fastutil.ints.IntArrayList
import it.unimi.dsi.fastutil.ints.IntArraySet
import it.unimi.dsi.fastutil.longs.LongArrayList
import it.unimi.dsi.fastutil.longs.LongArraySet
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import ru.dbotthepony.kommons.gson.consumeNull
import java.lang.reflect.ParameterizedType

object CollectionAdapterFactory : TypeAdapterFactory {
	override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
		if (type.type is ParameterizedType) {
			val parentType = TypeToken.get((type.type as ParameterizedType).actualTypeArguments[0]) as TypeToken<Any>

			return when (type.rawType) {
				ArrayList::class.java -> Adapter<ArrayList<Any>, Any>(::ArrayList, gson.getAdapter(parentType))
				List::class.java -> Adapter<ArrayList<Any>, Any>(::ArrayList, gson.getAdapter(parentType))
				Set::class.java -> Adapter<ObjectOpenHashSet<Any>, Any>(::ObjectOpenHashSet, gson.getAdapter(parentType))
				ObjectOpenHashSet::class.java -> Adapter<ObjectOpenHashSet<Any>, Any>(::ObjectOpenHashSet, gson.getAdapter(parentType))
				else -> null
			} as TypeAdapter<T>?
		} else {
			return when (type.rawType) {
				IntArrayList::class.java -> Adapter(::IntArrayList, gson.getAdapter(Int::class.java))
				LongArrayList::class.java -> Adapter(::LongArrayList, gson.getAdapter(Long::class.java))
				DoubleArrayList::class.java -> Adapter(::DoubleArrayList, gson.getAdapter(Double::class.java))

				IntArraySet::class.java -> Adapter(::IntArraySet, gson.getAdapter(Int::class.java))
				LongArraySet::class.java -> Adapter(::LongArraySet, gson.getAdapter(Long::class.java))
				DoubleArraySet::class.java -> Adapter(::DoubleArraySet, gson.getAdapter(Double::class.java))

				else -> null
			} as TypeAdapter<T>?
		}
	}

	private class Adapter<C : MutableCollection<E>, E>(val factory: () -> C, val parent: TypeAdapter<E>) : TypeAdapter<C>() {
		override fun write(out: JsonWriter, value: C?) {
			if (value == null)
				out.nullValue()
			else {
				out.beginArray()
				for (v in value) parent.write(out, v)
				out.endArray()
			}
		}

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

			val output = factory()

			if (`in`.peek() == JsonToken.BEGIN_ARRAY) {
				`in`.beginArray()

				while (`in`.hasNext()) {
					output.add(parent.read(`in`))
				}

				`in`.endArray()
			} else {
				output.add(parent.read(`in`))
			}

			return output
		}
	}
}
