package ru.dbotthepony.kstarbound.defs

import com.google.common.collect.ImmutableMap
import com.github.benmanes.caffeine.cache.Interner
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 ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.guava.immutableMap
import ru.dbotthepony.kstarbound.json.builder.JsonImplementation

@JsonImplementation(ThingDescription::class)
interface IThingWithDescription {
	/**
	 * Краткое описание штуки. Несмотря на то, что название свойства подразумевает "описание",
	 * на самом деле данное поле отвечает за название штуки.
	 *
	 * Примеры:
	 * * Microwave Oven
	 * * Copper Ore
	 * * Poptop
	 */
	val shortdescription: String

	/**
	 * Полное описание штуки. Оно отображается игроку, когда последний наводит курсор на штуку.
	 *
	 * Примеры:
	 * * A microwave. For when you're hungry enough to nuke your food.
	 * * Copper ore. Can be used for smelting.
	 * * The Poptop hums beautifully to confuse its prey.
	 */
	val description: String

	/**
	 * Полное описание штуки для определённых рас
	 *
	 * Пример для Microwave Oven:
	 * Apex: 			A type of oven.
	 * Avian: 			A bizarre cooking device.
	 * Floran: 			Floran likess raw meat, sssometimes cooked meat is good too.
	 * Glitch: 			Irked. It is encrusted with spattered food, who left it in this state?
	 * Human: 			A microwave. Gotta get me some jacket potatoes.
	 * Hylotl: 			A strange, spinning oven.
	 * Novakid: 		This lil' rotatin' oven cooks food at speed!
	 *
	 * Как это выглядит в JSON структуре:
	 * ```json
	 * {
	 * 		...
	 *
	 * 		"apexDescription" : "A type of oven.",
	 * 		"avianDescription" : "A bizarre cooking device.",
	 * 		"floranDescription" : "Floran likess raw meat, sssometimes cooked meat is good too.",
	 * 		"glitchDescription" : "Irked. It is encrusted with spattered food, who left it in this state?",
	 * 		"humanDescription" : "A microwave. Gotta get me some jacket potatoes.",
	 * 		"hylotlDescription" : "A strange, spinning oven.",
	 * 		"novakidDescription" : "This lil' rotatin' oven cooks food at speed!",
	 *
	 * 		...
	 * }
	 * ```
	 *
	 * Для получения описания для определённой расы необходимо знать ID расы, как показано выше (`apex`, `avian`, ...)
	 */
	val racialDescription: Map<String, String> get() = mapOf()

	/**
	 * Кратное описание штуки для определённых рас. В оригинальной игре не встречается
	 */
	val racialShortDescription: Map<String, String> get() = mapOf()
}

data class ThingDescription(
	override val shortdescription: String = "...",
	override val description: String = "...",
	override val racialDescription: Map<String, String> = mapOf(),
	override val racialShortDescription: Map<String, String> = mapOf(),
) : IThingWithDescription {
	companion object {
		val EMPTY = ThingDescription()
	}

	fun toMap(): ImmutableMap<String, String> {
		return immutableMap {
			putAll(racialDescription)
			put("description", description)
		}
	}

	fun fixDescription(newDescription: String): ThingDescription {
		return copy(
			shortdescription = if (shortdescription == "...") newDescription else shortdescription,
			description = if (description == "...") newDescription else description,
		)
	}

	class Factory(val interner: Interner<String> = Interner { it }) : TypeAdapterFactory {
		override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
			if (type.rawType == ThingDescription::class.java) {
				return object : TypeAdapter<ThingDescription>() {
					override fun write(out: JsonWriter, value: ThingDescription?) {
						if (value == null)
							out.nullValue()
						else {
							out.beginObject()

							out.name("shortdescription")
							out.value(value.shortdescription)

							out.name("description")
							out.value(value.description)

							for ((k, v) in value.racialDescription) {
								out.name("${k}Description")
								out.value(v)
							}

							for ((k, v) in value.racialShortDescription) {
								out.name("${k}Shortdescription")
								out.value(v)
							}

							out.endObject()
						}
					}

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

						`in`.beginObject()

						var shortdescription: String? = null
						var description: String? = null
						val racial = ImmutableMap.Builder<String, String>()
						val racialShort = ImmutableMap.Builder<String, String>()

						while (`in`.peek() !== JsonToken.END_OBJECT) {
							when (val name = `in`.nextName()) {
								"shortdescription" -> shortdescription = `in`.nextString()
								"description" -> description = `in`.nextString()
								else -> {
									try {
										if (name.endsWith("shortdescription") || name.endsWith("shortDescription") || name.endsWith("Shortdescription") || name.endsWith("ShortDescription")) {
											racialShort.put(interner.intern(name.substring(0, name.length - "shortdescription".length)), interner.intern(`in`.nextString()))
										} else if (name.endsWith("description") || name.endsWith("Description")) {
											racial.put(interner.intern(name.substring(0, name.length - "description".length)), interner.intern(`in`.nextString()))
										} else {
											`in`.skipValue()
										}
									} catch (_: IllegalStateException) {
										`in`.skipValue()
									}
								}
							}
						}

						`in`.endObject()

						if (shortdescription == null && description == null) {
							shortdescription = "..."
							description = "..."
						} else if (shortdescription == null) {
							shortdescription = description
						} else if (description == null) {
							description = shortdescription
						}

						return ThingDescription(
							shortdescription = shortdescription!!,
							description = description!!,
							racialDescription = racial.build(),
							racialShortDescription = racialShort.build()
						)
					}
				} as TypeAdapter<T>
			}

			return null
		}
	}
}
