package ru.dbotthepony.kstarbound.json

import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.google.common.collect.ImmutableSet
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import com.google.gson.TypeAdapter
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.fromJson

inline fun <reified T> Gson.getAdapter(): TypeAdapter<T> {
	return getAdapter(object : TypeToken<T>() {})
}

inline fun <reified C : Collection<E>, reified E> Gson.collectionAdapter(): TypeAdapter<C> {
	return getAdapter(TypeToken.getParameterized(C::class.java, E::class.java)) as TypeAdapter<C>
}

inline fun <reified K, reified V> Gson.mapAdapter(): TypeAdapter<ImmutableMap<K, V>> {
	return getAdapter(TypeToken.getParameterized(ImmutableMap::class.java, K::class.java, V::class.java)) as TypeAdapter<ImmutableMap<K, V>>
}

inline fun <reified E> Gson.listAdapter(): TypeAdapter<ImmutableList<E>> {
	return collectionAdapter()
}

inline fun <reified A, reified B> Gson.pairAdapter(): TypeAdapter<Pair<A, B>> {
	return getAdapter(TypeToken.getParameterized(Pair::class.java, A::class.java, B::class.java)) as TypeAdapter<Pair<A, B>>
}

inline fun <reified A, reified B> Gson.pairListAdapter(): TypeAdapter<ImmutableList<Pair<A, B>>> {
	return getAdapter(TypeToken.getParameterized(ImmutableList::class.java, TypeToken.getParameterized(Pair::class.java, A::class.java, B::class.java).type)) as TypeAdapter<ImmutableList<Pair<A, B>>>
}

inline fun <reified E> Gson.mutableListAdapter(): TypeAdapter<ArrayList<E>> {
	return collectionAdapter()
}

inline fun <reified E> Gson.setAdapter(): TypeAdapter<ImmutableSet<E>> {
	return collectionAdapter()
}

inline fun <reified A, reified B> Gson.pairSetAdapter(): TypeAdapter<ImmutableSet<Pair<A, B>>> {
	return getAdapter(TypeToken.getParameterized(ImmutableSet::class.java, TypeToken.getParameterized(Pair::class.java, A::class.java, B::class.java).type)) as TypeAdapter<ImmutableSet<Pair<A, B>>>
}

inline fun <reified E> Gson.mutableSetAdapter(): TypeAdapter<ObjectOpenHashSet<E>> {
	return collectionAdapter()
}

/**
 * It is implied that [base] is a copy that can be modified
 */
fun mergeJson(base: JsonElement, with: JsonElement): JsonElement {
	if (base is JsonObject && with is JsonObject) {
		for ((k, v) in with.entrySet()) {
			base[k] = mergeJson(base[k] ?: JsonNull.INSTANCE, v)
		}

		return base
	} else if (with.isJsonNull) {
		return base
	} else {
		return with.deepCopy()
	}
}

fun mergeJson(base: JsonElement, with: Map<String, JsonElement>): JsonElement {
	if (base is JsonObject) {
		for ((k, v) in with) {
			base[k] = mergeJson(base[k] ?: JsonNull.INSTANCE, v)
		}

		return base
	} else {
		return JsonObject().apply {
			for ((k, v) in with) {
				add(k, v.deepCopy())
			}
		}
	}
}

fun mergeJson(base: JsonElement, with: JsonElement, vararg rest: JsonElement): JsonElement {
	var base = mergeJson(base, with)

	for (v in rest) {
		base = mergeJson(base, v)
	}

	return base
}

fun mergeJson(base: JsonObject, with: JsonElement): JsonObject {
	check(mergeJson(base as JsonElement, with) === base)
	return base
}

fun mergeJson(base: JsonObject, with: JsonElement, vararg rest: JsonElement): JsonObject {
	check(mergeJson(base as JsonElement, with) === base)

	for (v in rest) {
		check(mergeJson(base as JsonElement, v) === base)
	}

	return base
}

fun jsonArrayOf(vararg elements: JsonElement): JsonArray {
	val array = JsonArray(elements.size)
	elements.forEach { array.add(it) }
	return array
}

fun jsonArrayOf(vararg elements: Any?): JsonArray {
	val array = JsonArray(elements.size)
	elements.forEach { array.add(Starbound.gson.toJsonTree(it)) }
	return array
}
