package ru.dbotthepony.kstarbound.json.factory

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

object PairAdapterFactory : TypeAdapterFactory {
	override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
		if (type.rawType == Pair::class.java) {
			val type = type.type as? ParameterizedType ?: return null
			val (type0, type1) = type.actualTypeArguments

			return object : TypeAdapter<Pair<Any?, Any?>>() {
				private val adapter0 = gson.getAdapter(TypeToken.get(type0)) as TypeAdapter<Any?>
				private val adapter1 = gson.getAdapter(TypeToken.get(type1)) as TypeAdapter<Any?>

				override fun write(out: JsonWriter, value: Pair<Any?, Any?>?) {
					if (value == null) {
						out.nullValue()
					} else {
						out.beginArray()
						adapter0.write(out, value.first)
						adapter1.write(out, value.second)
						out.endArray()
					}
				}

				override fun read(`in`: JsonReader): Pair<Any?, Any?>? {
					if (`in`.consumeNull()) {
						return null
					} else {
						if (`in`.peek() == JsonToken.BEGIN_ARRAY) {
							`in`.beginArray()

							val value = try {
								adapter0.read(`in`)
							} catch (err: Throwable) {
								throw JsonSyntaxException("Reading left side of pair", err)
							} to try {
								adapter1.read(`in`)
							} catch (err: Throwable) {
								throw JsonSyntaxException("Reading right side of pair", err)
							}

							`in`.endArray()
							return value
						} else if (`in`.peek() == JsonToken.BEGIN_OBJECT) {
							`in`.beginObject()

							var left: Any? = null
							var right: Any? = null
							var leftPresent = false
							var rightPresent = false

							while (`in`.hasNext()) {
								when (`in`.nextName().lowercase()) {
									"left", "first", "a", "weight" -> {
										left = adapter0.read(`in`)
										leftPresent = true
									}

									"right", "second", "b", "item" -> {
										right = adapter1.read(`in`)
										rightPresent = true
									}

									else -> `in`.skipValue()
								}
							}

							`in`.endObject()

							if (!leftPresent && !rightPresent) {
								throw JsonSyntaxException("Neither left or right side of pair is present")
							} else if (!leftPresent) {
								throw JsonSyntaxException("Left side of pair is missing")
							} else if (!rightPresent) {
								throw JsonSyntaxException("Right side of pair is missing")
							} else {
								return left to right
							}
						} else {
							throw JsonSyntaxException("Expected either 2 value array or object for deserializing a pair, got ${`in`.peek()}")
						}
					}
				}
			} as TypeAdapter<T>
		}

		return null
	}
}
