package ru.dbotthepony.kstarbound.json

import com.github.benmanes.caffeine.cache.Interner
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.google.gson.TypeAdapter
import com.google.gson.internal.LazilyParsedNumber
import com.google.gson.internal.bind.TypeAdapters
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.consumeNull

class InternedJsonElementAdapter(val stringInterner: Interner<String>) : TypeAdapter<JsonElement>() {
	override fun write(out: JsonWriter, value: JsonElement?) {
		return TypeAdapters.JSON_ELEMENT.write(out, value)
	}

	override fun read(`in`: JsonReader): JsonElement {
		return when (val p = `in`.peek()) {
			JsonToken.STRING -> JsonPrimitive(stringInterner.intern(`in`.nextString()))
			JsonToken.NUMBER -> JsonPrimitive(LazilyParsedNumber(stringInterner.intern(`in`.nextString())))
			JsonToken.BOOLEAN -> if (`in`.nextBoolean()) TRUE else FALSE

			JsonToken.NULL -> {
				`in`.nextNull()
				JsonNull.INSTANCE
			}

			JsonToken.BEGIN_ARRAY -> arrays.read(`in`)
			JsonToken.BEGIN_OBJECT -> objects.read(`in`)
			else -> throw IllegalArgumentException(p.toString())
		}
	}

	val objects = object : TypeAdapter<JsonObject>() {
		override fun write(out: JsonWriter, value: JsonObject?) {
			return TypeAdapters.JSON_ELEMENT.write(out, value)
		}

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

			val output = JsonObject()
			`in`.beginObject()
			while (`in`.hasNext()) { output.add(stringInterner.intern(`in`.nextName()), this@InternedJsonElementAdapter.read(`in`)) }
			`in`.endObject()
			return output
		}
	}

	val arrays = object : TypeAdapter<JsonArray>() {
		override fun write(out: JsonWriter, value: JsonArray?) {
			return TypeAdapters.JSON_ELEMENT.write(out, value)
		}

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

			val output = JsonArray()
			`in`.beginArray()
			while (`in`.hasNext()) { output.add(this@InternedJsonElementAdapter.read(`in`)) }
			`in`.endArray()
			return output
		}
	}

	companion object {
		val TRUE = JsonPrimitive(true)
		val FALSE = JsonPrimitive(false)

		fun of(value: Boolean) = if (value) TRUE else FALSE
	}
}

class InternedStringAdapter(val stringInterner: Interner<String>) : TypeAdapter<String>() {
	override fun write(out: JsonWriter, value: String?) {
		out.value(value)
	}

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

		if (`in`.peek() == JsonToken.BOOLEAN)
			return `in`.nextBoolean().toString()

		return stringInterner.intern(`in`.nextString())
	}
}
