package ru.dbotthepony.kstarbound.world.api

import com.github.benmanes.caffeine.cache.Interner
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.tile.NO_DUNGEON_ID
import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState
import ru.dbotthepony.kstarbound.world.physics.CollisionType
import java.io.DataInputStream
import java.io.DataOutputStream

sealed class AbstractCell {
	abstract val foreground: AbstractTileState
	abstract val background: AbstractTileState
	abstract val liquid: AbstractLiquidState

	// ushort, can be anything, mostly used by placed dungeons
	// this value determines special logic behind this cell, such as if it is protected from modifications
	// by players or other means
	abstract val dungeonId: Int

	// ubyte, points at biome from current WorldLayout
	// in context of block
	// value of 0 points to null, 1 points to 0, and so on
	abstract val blockBiome: Int
	// ubyte, points at biome from current WorldLayout
	// in context of environment
	// value of 0 points to null, 1 points to 0, and so on
	abstract val envBiome: Int

	abstract val biomeTransition: Boolean

	abstract fun immutable(): ImmutableCell
	abstract fun mutable(): MutableCell

	open fun toLegacyNet(): LegacyNetworkCellState {
		return LegacyNetworkCellState(background.toLegacyNet(), foreground.toLegacyNet(), foreground.material.value.collisionKind, blockBiome, envBiome, liquid.toLegacyNet(), dungeonId)
	}

	fun isConnectible(materialOnly: Boolean, isBackground: Boolean): Boolean {
		if (isBackground && background.material.value.isConnectable)
			return true

		if (!isBackground && foreground.material.value.isConnectable)
			return true

		return !materialOnly && !isBackground && (foreground.material.value.collisionKind == CollisionType.BLOCK || foreground.material.value.collisionKind == CollisionType.PLATFORM)
	}

	abstract fun tile(background: Boolean): AbstractTileState

	fun write(stream: DataOutputStream) {
		foreground.write(stream)
		background.write(stream)
		liquid.write(stream)

		stream.write(0) // collisionMap

		stream.writeShort(dungeonId)
		stream.writeByte(blockBiome)
		stream.writeByte(envBiome)
		stream.writeBoolean(biomeTransition)

		stream.write(0) // unknown
	}

	companion object {
		fun skip(stream: DataInputStream) {
			AbstractTileState.skip(stream)
			AbstractTileState.skip(stream)
			AbstractLiquidState.skip(stream)
			stream.skipNBytes(1 + 2 + 1 + 1 + 1 + 1)
		}

		@JvmStatic
		protected val POOL: Interner<ImmutableCell> = if (Starbound.DEDUP_CELL_STATES) Starbound.interner() else Interner { it }

		val EMPTY: ImmutableCell = POOL.intern(ImmutableCell(AbstractTileState.EMPTY, AbstractTileState.EMPTY, AbstractLiquidState.EMPTY, NO_DUNGEON_ID, 0, 0, false))
		val NULL: ImmutableCell = POOL.intern(ImmutableCell(AbstractTileState.NULL, AbstractTileState.NULL, AbstractLiquidState.EMPTY, NO_DUNGEON_ID, 0, 0, false))
	}
}
