package ru.dbotthepony.kstarbound.json.builder

import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.TypeAdapter
import com.google.gson.reflect.TypeToken
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.Starbound
import kotlin.properties.Delegates
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KProperty1
import kotlin.reflect.KType
import kotlin.reflect.javaType

open class ReferencedProperty<T : Any, V>(
	property: KProperty1<T, V>,
	val isFlat: Boolean,
	val mustBePresent: Boolean? = null,
	val isIgnored: Boolean = false,
	isMarkedNullable: Boolean? = null,
) {
	@Suppress("CanBePrimaryConstructorProperty")
	open val property: KProperty1<T, V> = property

	val name: String get() = property.name
	var adapter: TypeAdapter<V> by Delegates.notNull()
		private set

	val type: KType = property.returnType
	val isMarkedNullable: Boolean = isMarkedNullable?.also {
		if (it && !type.isMarkedNullable) throw IllegalArgumentException("Can't declare non-null property as nullable")
	} ?: type.isMarkedNullable

	operator fun component1() = property
	operator fun component2() = adapter

	private var isResolved = false

	@OptIn(ExperimentalStdlibApi::class)
	@Suppress("DEPRECATION")
	fun resolve(gson: Gson) {
		if (!isResolved) {
			isResolved = true
			val token = TypeToken.get(type.javaType)

			if (token.rawType === KOptional::class.java && type.arguments.size == 1) {
				val subtype = type.arguments[0]

				if (subtype.type!!.isMarkedNullable) {
					adapter = gson.getAdapter(TypeToken.getParameterized(KOptional.Nullable::class.java, subtype.type!!.javaType)) as TypeAdapter<V>
				} else {
					adapter = gson.getAdapter(TypeToken.getParameterized(KOptional.NotNull::class.java, subtype.type!!.javaType)) as TypeAdapter<V>
				}
			} else if (token.rawType === JsonElement::class.java) {
				adapter = Starbound.ELEMENTS_ADAPTER as TypeAdapter<V>
			} else if (token.rawType === JsonArray::class.java) {
				adapter = Starbound.ELEMENTS_ADAPTER.arrays as TypeAdapter<V>
			} else if (token.rawType === JsonObject::class.java) {
				adapter = Starbound.ELEMENTS_ADAPTER.objects as TypeAdapter<V>
			} else {
				adapter = gson.getAdapter(token) as TypeAdapter<V>
			}
		}
	}
}

class ReferencedMutableProperty<T : Any, V>(override val property: KMutableProperty1<T, V>, isFlat: Boolean, mustBePresent: Boolean?) : ReferencedProperty<T, V>(property, isFlat, mustBePresent) {
	fun set(instance: T, value: Any?) {
		(this.property as KMutableProperty1<T, Any?>).set(instance, value)
	}
}
