package ru.dbotthepony.kstarbound.defs.tile

import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import it.unimi.dsi.fastutil.objects.Object2DoubleMap
import it.unimi.dsi.fastutil.objects.Object2DoubleMaps
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.defs.AssetReference
import ru.dbotthepony.kstarbound.world.physics.CollisionType
import ru.dbotthepony.kstarbound.defs.ThingDescription

val Registry.Ref<TileDefinition>.isEmptyTile: Boolean
	get() = entry == BuiltinMetaMaterials.EMPTY

val Registry.Ref<TileDefinition>.isNullTile: Boolean
	get() = entry == BuiltinMetaMaterials.NULL || entry == null

val Registry.Ref<TileDefinition>.orEmptyTile: Registry.Entry<TileDefinition>
	get() = entry ?: BuiltinMetaMaterials.EMPTY

val Registry.Ref<TileDefinition>.orNullTile: Registry.Entry<TileDefinition>
	get() = entry ?: BuiltinMetaMaterials.NULL

val Registry.Ref<TileDefinition>.isObjectSolidTile: Boolean
	get() = entry == BuiltinMetaMaterials.OBJECT_SOLID

val Registry.Ref<TileDefinition>.isObjectPlatformTile: Boolean
	get() = entry == BuiltinMetaMaterials.OBJECT_PLATFORM

val Registry.Entry<TileDefinition>.isEmptyTile: Boolean
	get() = this == BuiltinMetaMaterials.EMPTY || this == BuiltinMetaMaterials.NULL

val Registry.Entry<TileDefinition>.isRealTile: Boolean
	get() = !value.isMeta

val Registry.Entry<TileDefinition>.isMetaTile: Boolean
	get() = value.isMeta

val Registry.Entry<TileDefinition>.isNotEmptyTile: Boolean
	get() = !isEmptyTile

val Registry.Entry<TileDefinition>.isObjectTile: Boolean
	get() = this === BuiltinMetaMaterials.OBJECT_SOLID || this === BuiltinMetaMaterials.OBJECT_PLATFORM

val Registry.Entry<TileDefinition>.isNullTile: Boolean
	get() = this == BuiltinMetaMaterials.NULL

val Registry.Entry<TileDefinition>.isObjectSolidTile: Boolean
	get() = this == BuiltinMetaMaterials.OBJECT_SOLID

val Registry.Entry<TileDefinition>.isObjectPlatformTile: Boolean
	get() = this == BuiltinMetaMaterials.OBJECT_PLATFORM

val Registry.Entry<TileDefinition>.supportsModifiers: Boolean
	get() = !value.isMeta && value.supportsMods

/**
 * whenever modifier is empty (always supported), or material supports it
 */
fun Registry.Entry<TileDefinition>.supportsModifier(modifier: Registry.Entry<TileModifierDefinition>): Boolean {
	return modifier == BuiltinMetaMaterials.EMPTY_MOD || value.supportsModifier(modifier)
}

/**
 * whenever modifier is empty (always supported), or material supports it
 */
fun Registry.Entry<TileDefinition>.supportsModifier(modifier: Registry.Ref<TileModifierDefinition>): Boolean {
	return !modifier.isPresent || value.supportsModifier(modifier.entry!!)
}

val Registry.Entry<LiquidDefinition>.isEmptyLiquid: Boolean
	get() = this == BuiltinMetaMaterials.NO_LIQUID

val Registry.Entry<LiquidDefinition>.isNotEmptyLiquid: Boolean
	get() = !isEmptyLiquid

val Registry.Ref<LiquidDefinition>.isEmptyLiquid: Boolean
	get() = entry == null || entry == BuiltinMetaMaterials.NO_LIQUID

val Registry.Ref<LiquidDefinition>.orEmptyLiquid: Registry.Entry<LiquidDefinition>
	get() = entry ?: BuiltinMetaMaterials.NO_LIQUID

val Registry.Ref<LiquidDefinition>.isNotEmptyLiquid: Boolean
	get() = !isEmptyLiquid

val Registry.Ref<TileModifierDefinition>.isEmptyModifier: Boolean
	get() = entry == null || entry == BuiltinMetaMaterials.EMPTY_MOD

val Registry.Ref<TileModifierDefinition>.isNotEmptyModifier: Boolean
	get() = !isEmptyModifier

val Registry.Entry<TileModifierDefinition>.isEmptyModifier: Boolean
	get() = this == BuiltinMetaMaterials.EMPTY_MOD

val Registry.Entry<TileModifierDefinition>.isNotEmptyModifier: Boolean
	get() = !isEmptyModifier

val Registry.Ref<TileModifierDefinition>.orEmptyModifier: Registry.Entry<TileModifierDefinition>
	get() = entry ?: BuiltinMetaMaterials.EMPTY_MOD

val Registry.Ref<TileModifierDefinition>.isRealModifier: Boolean
	get() = entry?.value?.isMeta == false

// these are hardcoded way harder than any Hard-Coder:tm:
// considering there is no way you gonna mod-in this many (16 bit uint) dungeons
const val NO_DUNGEON_ID = 65535
const val SPAWN_DUNGEON_ID = 65534
const val MICRO_DUNGEON_ID = 65533
// meta dungeon signalling player built structures
const val ARTIFICIAL_DUNGEON_ID = 65532
// indicates a block that has been destroyed
const val DESTROYED_BLOCK_ID = 65531
// dungeonId for zero-g areas with and without tile protection
const val ZERO_GRAVITY_DUNGEON_ID = 65525
const val PROTECTED_ZERO_GRAVITY_DUNGEON_ID = 65524

const val FIRST_RESERVED_DUNGEON_ID = 65520

object BuiltinMetaMaterials {
	private fun make(id: Int, name: String, collisionType: CollisionType, isConnectable: Boolean = true) = Registries.tiles.add(key = "metamaterial:$name", id = KOptional(id), value = TileDefinition(
		materialId = id,
		materialName = "metamaterial:$name",
		descriptionData = ThingDescription.EMPTY,
		category = "meta",
		renderTemplate = AssetReference.empty(),
		renderParameters = RenderParameters.META,
		isConnectable = isConnectable,
		isMeta = true,
		supportsMods = false,
		collisionKind = collisionType,
		damageTable = AssetReference(TileDamageConfig(
			damageFactors = ImmutableMap.of(),
			damageRecovery = Double.MAX_VALUE,
			maximumEffectTime = 0.0,
			totalHealth = Double.MAX_VALUE,
			harvestLevel = Int.MAX_VALUE,
		))
	), isBuiltin = true)

	private fun makeMod(id: Int, name: String) = Registries.tileModifiers.add(key = "metamod:$name", id = KOptional(id), value = TileModifierDefinition(
		modId = id,
		modName = "metamod:$name",
		descriptionData = ThingDescription.EMPTY,
		renderParameters = RenderParameters.META,
		isMeta = true,
		renderTemplate = AssetReference.empty(),
	), isBuiltin = true)

	/**
	 * air
	 */
	val EMPTY = make(65535, "empty", CollisionType.NONE, isConnectable = false)

	/**
	 * not set / out of bounds
	 */
	val NULL = make(65534, "null", CollisionType.NULL, isConnectable = false)

	val STRUCTURE = make(65533, "structure", CollisionType.BLOCK)
	val BIOME = make(65527, "biome", CollisionType.BLOCK)
	val BIOME1 = make(65528, "biome1", CollisionType.BLOCK)
	val BIOME2 = make(65529, "biome2", CollisionType.BLOCK)
	val BIOME3 = make(65530, "biome3", CollisionType.BLOCK)
	val BIOME4 = make(65531, "biome4", CollisionType.BLOCK)
	val BIOME5 = make(65532, "biome5", CollisionType.BLOCK)
	val BOUNDARY = make(65526, "boundary", CollisionType.SLIPPERY, isConnectable = false)
	val OBJECT_SOLID = make(65500, "objectsolid", CollisionType.BLOCK)
	val OBJECT_PLATFORM = make(65501, "objectplatform", CollisionType.PLATFORM)

	val BIOME_META_MATERIALS: ImmutableList<Registry.Entry<TileDefinition>> = ImmutableList.of(BIOME, BIOME1, BIOME2, BIOME3, BIOME4, BIOME5)

	val EMPTY_MOD = makeMod(65535, "none")
	val BIOME_MOD = makeMod(65534, "biome")
	val UNDERGROUND_BIOME_MOD = makeMod(65533, "underground_biome")

	val NO_LIQUID = Registries.liquid.add(key = "metaliquid:empty", id = KOptional(0), value = LiquidDefinition(
		name = "metaliquid:empty",
		liquidId = 0,
		color = RGBAColor.TRANSPARENT_BLACK,
		texture = "",
		isMeta = true,
		textureMovementFactor = 0.0,
		bottomLightMix = RGBAColor.TRANSPARENT_BLACK
	), isBuiltin = true)
}
