package ru.dbotthepony.kstarbound.util

import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap
import it.unimi.dsi.fastutil.objects.Object2ObjectMap
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kstarbound.Starbound

class Directives private constructor(private val directivesInternal: Object2ObjectAVLTreeMap<String, String>) {
	constructor() : this(Object2ObjectAVLTreeMap())
	constructor(directives: String) : this() {
		if (directives.isNotBlank()) {
			if ('?' !in directives) {
				if ('=' !in directives) {
					throw IllegalArgumentException("Missing render directive delimiter in '$directives'")
				}

				// assume it is just "name=value"
				val key = Starbound.STRINGS.intern(directives.substringBefore('='))
				val value = Starbound.STRINGS.intern(directives.substringAfter('='))
				directivesInternal[key] = value
			} else {
				// gets interesting
				for (pair in directives.split('?').filter { it.isNotBlank() }) {
					if ('=' !in pair) {
						throw IllegalArgumentException("Missing render directive delimiter in '$pair' (full string: '$directives')")
					}

					val key = Starbound.STRINGS.intern(pair.substringBefore('='))
					val value = Starbound.STRINGS.intern(pair.substringAfter('='))
					directivesInternal[key] = value
				}
			}
		}
	}

	val directives: Object2ObjectMap<String, String> = Object2ObjectMaps.unmodifiable(directivesInternal)

	override fun toString(): String {
		if (directivesInternal.isEmpty())
			return "Directives[empty]"
		else
			return "Directives[$directivesString]"
	}

	val directivesString by lazy {
		if (directivesInternal.isEmpty())
			""
		else
			"?${directivesInternal.entries.joinToString("?") { "${it.key}=${it.value}" }}"
	}

	override fun equals(other: Any?): Boolean {
		return this === other || other is Directives && directivesInternal == other.directivesInternal
	}

	override fun hashCode(): Int {
		return directivesInternal.hashCode()
	}

	fun add(directive: String, value: String): Directives {
		if (directivesInternal[directive] == value)
			return this

		val copy = directivesInternal.clone()
		copy[directive] = value
		return Directives(copy)
	}

	fun add(directives: String): Directives {
		if ('?' !in directives) {
			if ('=' !in directives) {
				throw IllegalArgumentException("Missing render directive delimiter in $directives")
			}

			// assume it is just "name=value"
			val key = Starbound.STRINGS.intern(directives.substringBefore('='))
			val value = Starbound.STRINGS.intern(directives.substringAfter('='))
			return add(key, value)
		} else {
			// gets interesting
			val split = directives.split('?').filter { it.isNotBlank() }

			if (split.isEmpty())
				return this

			val copy = directivesInternal.clone()

			for (pair in split) {
				if ('=' !in pair) {
					throw IllegalArgumentException("Missing render directive delimiter in '$pair' (full string: $directives)")
				}

				val key = Starbound.STRINGS.intern(pair.substringBefore('='))
				val value = Starbound.STRINGS.intern(pair.substringAfter('='))
				copy[key] = value
			}

			return Directives(copy)
		}
	}

	companion object : TypeAdapter<Directives>() {
		override fun write(out: JsonWriter, value: Directives?) {
			if (value == null)
				out.nullValue()
			else
				out.value(value.directivesString)
		}

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

			return Directives(`in`.nextString())
		}
	}
}
