package ru.dbotthepony.kstarbound.defs.tile

import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kstarbound.defs.image.Image
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.util.WriteOnce
import ru.dbotthepony.kstarbound.world.api.ITileAccess
import ru.dbotthepony.kstarbound.world.api.AbstractTileState

@JsonFactory
data class RenderPiece(
	val image: Image? = null,
	val textureSize: Vector2i,
	val texturePosition: Vector2i,
	val colorStride: Vector2i? = null,
	val variantStride: Vector2i? = null,
)

fun interface EqualityRuleTester {
	fun test(thisTile: AbstractTileState?, otherTile: AbstractTileState?): Boolean
}

@JsonFactory
data class RenderRuleList(
	val entries: ImmutableList<Entry>,
	val join: Combination = Combination.ALL
) {
	enum class Combination {
		ALL, ANY;
	}

	@JsonFactory
	data class Entry(
		val type: String,
		val matchHue: Boolean = false,
		val inverse: Boolean = false,
	) {
		private fun interface ActualTester {
			fun doTest(getter: ITileAccess, equalityTester: EqualityRuleTester, thisPos: Vector2i, offsetPos: Vector2i): Boolean
		}

		private object EqualsSelf : ActualTester {
			override fun doTest(getter: ITileAccess, equalityTester: EqualityRuleTester, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
				return equalityTester.test(getter.getTile(thisPos), getter.getTile(thisPos + offsetPos))
			}
		}

		private object Conntects : ActualTester {
			override fun doTest(getter: ITileAccess, equalityTester: EqualityRuleTester, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
				return getter.getTile(thisPos + offsetPos)?.material != null
			}
		}

		private object Never : ActualTester {
			override fun doTest(getter: ITileAccess, equalityTester: EqualityRuleTester, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
				return false
			}
		}

		private val actualTester by lazy {
			when (type) {
				"EqualsSelf" -> EqualsSelf
				"Connects" -> Conntects

				else -> {
					if (LOGGED.add(type)) {
						LOGGER.error("Unknown render rule test $type!")
					}

					Never
				}
			}
		}

		private fun doTest(getter: ITileAccess, equalityTester: EqualityRuleTester, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
			return actualTester.doTest(getter, equalityTester, thisPos, offsetPos)
		}

		fun test(getter: ITileAccess, equalityTester: EqualityRuleTester, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
			if (inverse) {
				return !doTest(getter, equalityTester, thisPos, offsetPos)
			}

			return doTest(getter, equalityTester, thisPos, offsetPos)
		}

		companion object {
			private val LOGGER = LogManager.getLogger()
			private val LOGGED = ObjectOpenHashSet<String>()
		}
	}

	fun test(getter: ITileAccess, equalityTester: EqualityRuleTester, thisPos: Vector2i, offset: Vector2i): Boolean {
		when (join) {
			Combination.ALL -> {
				for (entry in entries) {
					if (!entry.test(getter, equalityTester, thisPos, offset)) {
						return false
					}
				}

				return true
			}

			Combination.ANY -> {
				for (entry in entries) {
					if (entry.test(getter, equalityTester, thisPos, offset)) {
						return true
					}
				}

				return false
			}
		}
	}
}

@JsonFactory
data class RenderMatch(
	val pieces: ImmutableList<Piece> = ImmutableList.of(),
	val matchAllPoints: ImmutableList<Matcher> = ImmutableList.of(),
	val matchAnyPoints: ImmutableList<Matcher> = ImmutableList.of(),
	val subMatches: ImmutableList<RenderMatch> = ImmutableList.of(),
	val haltOnMatch: Boolean = false,
	val haltOnSubMatch: Boolean = false,
) {
	@JsonFactory(asList = true)
	data class Piece(
		val name: String,
		val offset: Vector2i
	) {
		var piece by WriteOnce<RenderPiece>()

		fun resolve(template: RenderTemplate) {
			piece = template.pieces[name] ?: throw IllegalStateException("Unable to find render piece $name")
		}
	}

	@JsonFactory(asList = true)
	data class Matcher(
		val offset: Vector2i,
		val ruleName: String
	) {
		var rule by WriteOnce<RenderRuleList>()

		fun test(getter: ITileAccess, equalityTester: EqualityRuleTester, thisPos: Vector2i): Boolean {
			return rule.test(getter, equalityTester, thisPos, offset)
		}

		fun resolve(template: RenderTemplate) {
			rule = template.rules[ruleName] ?: throw IllegalStateException("Unable to find render rule $ruleName")
		}
	}

	fun resolve(template: RenderTemplate) {
		for (value in matchAllPoints) {
			value.resolve(template)
		}

		for (value in pieces) {
			value.resolve(template)
		}

		for (value in matchAnyPoints) {
			value.resolve(template)
		}

		for (value in subMatches) {
			value.resolve(template)
		}
	}

	/**
	 * Возвращает, сработали ли ВСЕ [matchAllPoints] или ЛЮБОЙ ИЗ [matchAnyPoints]
	 *
	 * Если хотя бы один из них вернул false, весь тест возвращает false
	 *
	 * [subMatches] стоит итерировать только если это вернуло true
	 *
	 * [equalityTester] требуется для проверки раенства между "этим" тайлом и другим
	 */
	fun test(tileAccess: ITileAccess, equalityTester: EqualityRuleTester, thisPos: Vector2i): Boolean {
		for (matcher in matchAllPoints) {
			if (!matcher.test(tileAccess, equalityTester, thisPos)) {
				return false
			}
		}

		if (matchAnyPoints.isEmpty()) {
			return true
		}

		for (matcher in matchAnyPoints) {
			if (matcher.test(tileAccess, equalityTester, thisPos)) {
				return true
			}
		}

		return false
	}
}

@JsonFactory(asList = true)
data class RenderMatchList(
	val name: String,
	val list: ImmutableList<RenderMatch>
) {
	fun resolve(template: RenderTemplate) {
		for (value in list) {
			value.resolve(template)
		}
	}
}

@JsonFactory
data class RenderTemplate(
	val pieces: ImmutableMap<String, RenderPiece>,
	val representativePiece: String,
	val matches: ImmutableList<RenderMatchList>,
	val rules: ImmutableMap<String, RenderRuleList>,
) {
	init {
		for (value in matches) {
			value.resolve(this)
		}
	}
}
