package ru.dbotthepony.mc.otm.block.entity

import com.google.common.collect.ImmutableList
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import mekanism.api.energy.IStrictEnergyHandler
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.SectionPos
import net.minecraft.core.Vec3i
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.Container
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.chunk.LevelChunk
import net.minecraft.world.phys.Vec3
import net.minecraftforge.common.capabilities.Capability
import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.common.util.INBTSerializable
import net.minecraftforge.common.util.LazyOptional
import net.minecraftforge.energy.IEnergyStorage
import net.minecraftforge.event.TickEvent
import net.minecraftforge.event.TickEvent.LevelTickEvent
import net.minecraftforge.event.entity.player.PlayerEvent
import net.minecraftforge.event.level.ChunkWatchEvent
import net.minecraftforge.event.level.LevelEvent
import net.minecraftforge.event.server.ServerStoppingEvent
import net.minecraftforge.items.IItemHandler
import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.capability.isMekanismLoaded
import ru.dbotthepony.mc.otm.compat.mekanism.Mattery2MekanismEnergyWrapper
import ru.dbotthepony.mc.otm.compat.mekanism.Mekanism2MatteryEnergyWrapper
import ru.dbotthepony.mc.otm.core.collect.SupplierList
import ru.dbotthepony.mc.otm.core.collect.WeakHashSet
import ru.dbotthepony.mc.otm.core.forValidRefs
import ru.dbotthepony.mc.otm.core.get
import ru.dbotthepony.mc.otm.core.ifPresentK
import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.core.math.BlockRotation
import ru.dbotthepony.mc.otm.core.math.RelativeSide
import ru.dbotthepony.mc.otm.core.math.minus
import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.util.Savetables
import ru.dbotthepony.mc.otm.core.util.TickList
import ru.dbotthepony.mc.otm.network.BlockEntitySyncPacket
import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer
import ru.dbotthepony.mc.otm.network.WorldNetworkChannel
import ru.dbotthepony.mc.otm.once
import ru.dbotthepony.mc.otm.onceServer
import java.lang.ref.WeakReference
import java.util.*
import java.util.function.Consumer
import java.util.function.Supplier
import java.util.stream.Stream
import kotlin.NoSuchElementException
import kotlin.collections.ArrayList
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0

/**
 * Absolute barebone (lol) block entity class in Overdrive that Matters, providing bare minimum (lulmao, minecraft engine) functionality
 */
abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: BlockPos, p_155230_: BlockState) : BlockEntity(p_155228_, p_155229_, p_155230_) {
	private var isSynchronizing = false

	/**
	 * "shortcut" for getting [BlockRotation]
	 *
	 * if block has no rotation, returns [BlockRotation.NORTH]
	 */
	open val blockRotation: BlockRotation get() {
		return blockState[(blockState.block as? RotatableMatteryBlock ?: return BlockRotation.NORTH).rotationProperty]
	}

	private val _droppableContainers = ObjectArraySet<Container>()
	val droppableContainers: Set<Container> = Collections.unmodifiableSet(_droppableContainers)

	protected fun addDroppableContainer(container: Container) {
		_droppableContainers.add(container)
	}

	open fun beforeDroppingItems(oldBlockState: BlockState, level: Level, blockPos: BlockPos, newBlockState: BlockState, movedByPiston: Boolean) {}
	open fun beforeDestroyedByPlayer(level: Level, blockPos: BlockPos, blockState: BlockState, player: Player) {}

	private val _sides = EnumMap<RelativeSide, Side>(RelativeSide::class.java)
	val sides: Map<RelativeSide, Side> = Collections.unmodifiableMap(_sides)

	private data class SidelessCap<T : Any>(val cap: T, var optional: LazyOptional<T>)
	private val sidelessCaps = Reference2ObjectOpenHashMap<Capability<*>, SidelessCap<*>>()
	protected val tickList = TickList()

	/**
	 * Shared savetables, written both to level storage and to item tag
	 */
	protected val savetables = Savetables()

	/**
	 * Level-only savetables, written only to level storage
	 */
	protected val savetablesLevel = Savetables()

	private var tickedOnce = false

	open fun tick() {
		tickList.tick()
		tickedOnce = true
		synchronizeToPlayers(false)
	}

	/**
	 * exposes capability when no side is specified
	 */
	protected fun <T : Any> exposeSideless(capability: Capability<T>, value: T) {
		check(!sidelessCaps.containsKey(capability)) { "Already has globally exposed $capability!" }
		sidelessCaps[capability] = SidelessCap(value, LazyOptional.of { value })
		setChanged()
	}

	protected fun <T : Any> exposeAllSidesExcept(side: RelativeSide, capability: Capability<T>, value: T) {
		for ((k, v) in _sides) {
			if (k != side) {
				v.Cap(capability, value)
			}
		}
	}

	protected fun isSidelessExposed(capability: Capability<*>): Boolean {
		return sidelessCaps.containsKey(capability)
	}

	protected fun removeSideless(capability: Capability<*>): Boolean {
		if (sidelessCaps.remove(capability) != null) {
			setChanged()
			return true
		}

		return false
	}

	/**
	 * Exposes capability unconditionally, on all sides and sideless
	 */
	protected fun <T : Any> exposeGlobally(capability: Capability<T>, value: T) {
		exposeSideless(capability, value)

		for (side in _sides.values)
			side.Cap(capability, value)
	}

	protected fun exposeEnergyGlobally(value: IMatteryEnergyStorage) {
		exposeGlobally(ForgeCapabilities.ENERGY, value)
		exposeGlobally(MatteryCapability.ENERGY, value)

		if (isMekanismLoaded) {
			exposeGlobally(MatteryCapability.MEKANISM_ENERGY, Mattery2MekanismEnergyWrapper(value))
		}
	}

	protected fun exposeEnergySideless(value: IMatteryEnergyStorage) {
		exposeSideless(ForgeCapabilities.ENERGY, value)
		exposeSideless(MatteryCapability.ENERGY, value)

		if (isMekanismLoaded) {
			exposeSideless(MatteryCapability.MEKANISM_ENERGY, Mattery2MekanismEnergyWrapper(value))
		}
	}

	protected fun exposeEnergy(side: RelativeSide, value: IMatteryEnergyStorage): ImmutableList<Side.Cap<*>> {
		return immutableList {
			val thisSide = _sides[side]!!

			accept(thisSide.Cap(ForgeCapabilities.ENERGY, value))
			accept(thisSide.Cap(MatteryCapability.ENERGY, value))

			if (isMekanismLoaded) {
				accept(thisSide.Cap(MatteryCapability.MEKANISM_ENERGY, Mattery2MekanismEnergyWrapper(value)))
			}
		}
	}

	protected fun exposeItemsGlobally(value: IItemHandler) {
		exposeGlobally(ForgeCapabilities.ITEM_HANDLER, value)
	}

	@Suppress("SuspiciousCallableReferenceInLambda")
	protected fun <T> trackGlobally(capability: Capability<T>): List<LazyOptional<T>> {
		return SupplierList(_sides.values.map { it.track(capability)::get })
	}

	inner class Side(val side: RelativeSide) {
		init {
			check(!_sides.containsKey(side)) { "dafuq are you trying to do" }
			_sides[side] = this
		}

		private val caps = Reference2ObjectOpenHashMap<Capability<*>, Cap<*>>()
		private val subscriptions = Reference2ObjectOpenHashMap<Capability<*>, SubRef<*>>()
		private val knownLOs = WeakHashSet<LazyOptional<*>>()

		private inner class SubRef<T>(value: LazyOptional<T>) : Supplier<LazyOptional<T>> {
			var value: LazyOptional<T> = value
				set(value) {
					if (value !== field) {
						field = value

						for (tracker in listeners)
							tracker.accept(value)
					}
				}

			private val listeners = ArrayList<Consumer<LazyOptional<T>>>(0)

			fun addListener(listener: Consumer<LazyOptional<T>>) {
				listeners.add(listener)
				listener.accept(value)
			}

			override fun get(): LazyOptional<T> {
				return value
			}

			fun unset() {
				value = LazyOptional.empty()
			}
		}

		fun <T> track(capability: Capability<T>): Supplier<LazyOptional<T>> {
			var subref = subscriptions[capability] as Supplier<LazyOptional<T>>?

			if (subref == null) {
				subref = SubRef(LazyOptional.empty<Any?>()) as SubRef<T>
				subscriptions[capability] = subref
				level?.once { updateTracked(capability) }
			}

			return subref
		}

		fun trackEnergy(): Supplier<LazyOptional<IEnergyStorage>> {
			return object : Supplier<LazyOptional<IEnergyStorage>> {
				private val regular = track(ForgeCapabilities.ENERGY)
				private val mekanism: SubRef<IStrictEnergyHandler>?
				private var actualMekanism: LazyOptional<IEnergyStorage>? = null

				init {
					if (isMekanismLoaded) {
						mekanism = track(MatteryCapability.MEKANISM_ENERGY) as SubRef<IStrictEnergyHandler>

						mekanism.addListener {
							actualMekanism?.invalidate()

							if (it.isPresent) {
								actualMekanism = LazyOptional.of { Mekanism2MatteryEnergyWrapper(it.orElseThrow { NoSuchElementException("No mekanism energy was present, and tracker did not call back in time") }) }
							} else {
								actualMekanism = null
							}
						}
					} else {
						mekanism = null
					}
				}

				override fun get(): LazyOptional<IEnergyStorage> {
					val get = regular.get()
					get.ifPresentK { if (it is IMatteryEnergyStorage) return get }
					return actualMekanism ?: get
				}
			}
		}

		fun updateTracked() {
			for (key in subscriptions.keys) {
				// Concurrent Modification safety:
				// we do not add nor remove keys from map, we only update values
				updateTracked(key)
			}
		}

		private fun updateTracked(capability: Capability<*>) {
			if (isRemoved || !SERVER_IS_LIVE) return
			val dir = blockRotation.side2Dir(side)
			val targetPos = blockPos + dir.normal

			val chunk = level
				?.chunkSource
				?.getChunkNow(SectionPos.blockToSectionCoord(targetPos.x), SectionPos.blockToSectionCoord(targetPos.z))

			if (chunk == null) {
				subscriptions[capability]!!.unset()
				level?.once { updateTracked(capability) }
				return
			}

			val entity = chunk.getBlockEntity(targetPos)

			if (entity == null) {
				subscriptions[capability]!!.unset()
				return
			}

			val new = entity.getCapability(capability, dir.opposite)

			if (!new.isPresent) {
				subscriptions[capability]!!.unset()
				return
			}

			val subref = subscriptions[capability] as SubRef<Any?>

			if (subref.value !== new) {
				if (knownLOs.add(new)) {
					val ref = WeakReference(this)

					new.addListener {
						ref.get()?.updateTracked(capability)
					}
				}

				subref.value = new as LazyOptional<Any?>
			}
		}

		operator fun <T : Any> get(capability: Capability<T>): Cap<T>? {
			return caps[capability] as Cap<T>?
		}

		fun invalidate() {
			for (cap in caps.values)
				cap.invalidate()
		}

		fun revive() {
			for (cap in caps.values)
				cap.revive()
		}

		inner class Cap<T : Any>(val type: Capability<in T>, val capability: T) {
			init {
				check(!caps.containsKey(type)) { "Already has capability $type on side $side" }
				caps[type] = this
			}

			var isExposed = true
				private set
			var isValid = true
				private set
			var isRemoved = false
				private set

			var optional: LazyOptional<T> by object : ReadWriteProperty<Any?, LazyOptional<T>> {
				private var value: LazyOptional<T>? = null

				override fun getValue(thisRef: Any?, property: KProperty<*>): LazyOptional<T> {
					if (value == null) {
						value = LazyOptional.of { capability }
					}

					return value!!
				}

				override fun setValue(thisRef: Any?, property: KProperty<*>, value: LazyOptional<T>) {
					this.value = value
				}
			}
				private set

			fun remove() {
				if (!isRemoved) {
					isRemoved = true
					val removed = caps.remove(type)
					check(removed == this) { "$removed != $this" }
					optional.invalidate()
				}
			}

			fun close() {
				if (!isRemoved && isExposed) {
					isExposed = false
					optional.invalidate()

					if (SERVER_IS_LIVE)
						level?.once { setChanged() }
				}
			}

			fun expose() {
				if (!isRemoved && !isExposed) {
					isExposed = true

					if (isValid) {
						optional = LazyOptional.of { capability }

						if (SERVER_IS_LIVE)
							level?.once { setChanged() }
					}
				}
			}

			fun invalidate() {
				if (!isRemoved && isValid) {
					isValid = false
					optional.invalidate()

					if (SERVER_IS_LIVE)
						level?.once { setChanged() }
				}
			}

			fun revive() {
				if (!isRemoved && !isValid) {
					isValid = true

					if (isExposed) {
						optional = LazyOptional.of { capability }

						if (SERVER_IS_LIVE)
							level?.once { setChanged() }
					}
				}
			}
		}
	}

	val front = Side(RelativeSide.FRONT)
	val back = Side(RelativeSide.BACK)
	val left = Side(RelativeSide.LEFT)
	val right = Side(RelativeSide.RIGHT)
	val top = Side(RelativeSide.TOP)
	val bottom = Side(RelativeSide.BOTTOM)

	override fun <T : Any> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {
		if (side != null) {
			return _sides[blockRotation.dir2Side(side)]!![cap]?.optional ?: super.getCapability(cap, side)
		}

		return sidelessCaps[cap]?.optional?.cast() ?: super.getCapability(cap, side)
	}

	override fun invalidateCaps() {
		super.invalidateCaps()

		for (side in sides.values)
			side.invalidate()
	}

	override fun reviveCaps() {
		super.reviveCaps()

		for (side in sides.values)
			side.revive()
	}

	protected inline fun <reified T : Tag, V : INBTSerializable<T?>> savetable(property: KProperty0<V>, name: String = property.name) {
		savetables.stateful(property, name, T::class.java)
	}

	final override fun saveAdditional(nbt: CompoundTag) {
		super.saveAdditional(nbt)
		saveShared(nbt)
		saveLevel(nbt)
	}

	/**
	 * Saved both to item dropped, and to level storage
	 */
	open fun saveShared(nbt: CompoundTag) {
		savetables.serializeNBT(nbt)
	}

	/**
	 * Only saved to level storage, discarded when dropped as item
	 */
	open fun saveLevel(nbt: CompoundTag) {
		savetablesLevel.serializeNBT(nbt)
	}

	override fun load(nbt: CompoundTag) {
		super.load(nbt)
		savetables.deserializeNBT(nbt)
		savetablesLevel.deserializeNBT(nbt)
	}

	@Suppress("OVERRIDE_DEPRECATION")
	override fun setBlockState(pBlockState: BlockState) {
		val old = blockRotation
		@Suppress("DEPRECATION")
		super.setBlockState(pBlockState)
		val new = blockRotation

		if (old != new) {
			for (side in _sides.values) {
				side.updateTracked()
				side.invalidate()
			}
		}

		for (side in _sides.values) {
			side.revive()
		}
	}

	fun neighborChanged(neighbour: Block, neighbourPos: BlockPos, movedByPiston: Boolean) {
		val dir = vec2Dir[vecKey(neighbourPos - blockPos)] ?: return
		_sides[blockRotation.dir2Side(dir)]!!.updateTracked()
	}

	protected fun onceServer(action: (ServerLevel) -> Unit) {
		val level = level
		if (level is ServerLevel) {
			tickList.once { action.invoke(level) }
		}
	}

	// Just to mark chunk unsaved
	open fun setChangedLight() {
		val level = level
		if (level is ServerLevel) {
			level.chunkSource.getChunkNow(
				SectionPos.blockToSectionCoord(blockPos.x),
				SectionPos.blockToSectionCoord(blockPos.z))?.isUnsaved = true
		}
	}

	val synchronizer = FieldSynchronizer {
		if (isSynchronizing || tickedOnce)
			return@FieldSynchronizer

		if (!isRemoved && level?.isClientSide == false && (_subCache == null || (_subCache ?: throw ConcurrentModificationException()).players.isNotEmpty())) {
			ru.dbotthepony.mc.otm.onceServer {
				synchronizeToPlayers(true)
			}
		} else {
			markSynchronizerClean()
		}
	}

	private fun markSynchronizerClean() {
		synchronizer.markClean()
	}

	init {
		synchronizer.defaultEndpoint.markUnused()
	}

	override fun setLevel(level: Level) {
		val old = this.level
		super.setLevel(level)
		unsubscribe()
		_subCache = null

		if (!level.isClientSide) {
			subscribe()

			if (old != null) {
				for (side in _sides.values) {
					side.updateTracked()
					side.invalidate()
				}

				for (side in _sides.values) {
					side.revive()
				}
			} else {
				level.once {
					for (side in _sides.values) {
						side.updateTracked()
					}
				}
			}
		}
	}

	override fun setRemoved() {
		super.setRemoved()
		unsubscribe()
	}

	override fun clearRemoved() {
		super.clearRemoved()

		if (level?.isClientSide == false) {
			subscribe()
		}
	}

	private var _subCache: ChunkSubscribers? = null

	private fun unsubscribe() {
		val subCache = _subCache ?: return

		if (subCache.unsubscribe(this)) {
			val level = subCache.level.get()

			if (level != null) {
				playerMap.get(level)?.remove(subCache.chunkPos)
			}
		}

		lastSubscriptionChangeset = -1
	}

	private fun subscribe(): ChunkSubscribers {
		val level = level
		check(level is ServerLevel) { "Invalid realm" }
		unsubscribe()

		val subs = playerMap
			.computeIfAbsent(level) { Long2ObjectOpenHashMap() }
			.computeIfAbsent(ChunkPos(blockPos).toLong(), Long2ObjectFunction { ChunkSubscribers(level = WeakReference(level), chunkPos = ChunkPos(blockPos).toLong()) })

		subs.subscribe(this)
		_subCache = subs
		return subs
	}

	private val subscription get() = _subCache ?: subscribe()
	private var lastSubscriptionChangeset = -1

	fun synchronizeToPlayers() {
		synchronizeToPlayers(false)
	}

	protected fun synchronizeToPlayers(calledBySynchronizer: Boolean) {
		isSynchronizing = true

		try {
			if (synchronizer.isEmpty || isRemoved) {
				if (calledBySynchronizer) synchronizer.markClean()
				return
			}

			check(level is ServerLevel) { "Invalid realm or Level is null" }

			synchronizer.observe()

			val subscription = subscription

			if (subscription.players.isEmpty() || lastSubscriptionChangeset == subscription.changeset && !synchronizer.hasChanges) {
				if (calledBySynchronizer) synchronizer.markClean()
				return
			}

			lastSubscriptionChangeset = subscription.changeset

			for (player in subscription.players) {
				val payload = synchronizer.computeEndpointFor(player).collectNetworkPayload()

				if (payload != null) {
					WorldNetworkChannel.send(player, BlockEntitySyncPacket(blockPos, payload.array, payload.length))
				}
			}

			synchronizer.markClean()
		} finally {
			isSynchronizing = false
		}
	}

	private data class ChunkSubscribers(
		val blockEntities: ArrayList<WeakReference<MatteryBlockEntity>> = ArrayList(0),
		val players: ArrayList<ServerPlayer> = ArrayList(1),
		val observingBlockEntities: ArrayList<WeakReference<MatteryBlockEntity>> = ArrayList(0),
		val level: WeakReference<ServerLevel>,
		val chunkPos: Long,
		var changeset: Int = 0
	) {
		fun cleanUpBlocks() {
			val listIterator = blockEntities.listIterator()

			for (block in listIterator) {
				if (block.get() == null) {
					listIterator.remove()
				}
			}
		}

		val hasObservers: Boolean get() {
			val listIterator = blockEntities.listIterator()

			for (value in listIterator) {
				val ref = value.get()

				if (ref == null) {
					listIterator.remove()
				} else if (ref.synchronizer.hasObservers) {
					return true
				}
			}

			return false
		}

		fun subscribe(blockEntity: MatteryBlockEntity) {
			val iterator = blockEntities.listIterator()

			for (value in iterator) {
				val ref = value.get()

				if (ref === blockEntity) {
					return
				} else if (ref == null) {
					iterator.remove()
				}
			}

			val bref = WeakReference(blockEntity)
			blockEntities.add(bref)
			changeset++

			onceServer {
				blockEntity.synchronizeToPlayers()
			}

			if (blockEntity.synchronizer.hasObservers) {
				observingBlockEntities.add(bref)
				val listing = tickingMap.computeIfAbsent(level.get() ?: throw NullPointerException("Level got GCd!")) { ArrayList(1) }
				val tickerIterator = listing.listIterator()

				for (value in tickerIterator) {
					val ref = value.get()

					if (ref === this) {
						return
					} else if (ref == null) {
						tickerIterator.remove()
					}
				}

				listing.add(WeakReference(this))
			}
		}

		fun unsubscribe(blockEntity: MatteryBlockEntity): Boolean {
			val listIterator = blockEntities.listIterator()

			for (value in listIterator) {
				if (value.get() === blockEntity) {
					listIterator.remove()
					changeset++
					break
				}
			}

			if (blockEntity.synchronizer.hasObservers) {
				val tickerIterator = observingBlockEntities.listIterator()

				for (value in tickerIterator) {
					val ref = value.get()

					if (ref === blockEntity) {
						tickerIterator.remove()
						break
					} else if (ref == null) {
						tickerIterator.remove()
					}
				}
			}

			if (players.isNotEmpty()) {
				return false
			}

			cleanUpBlocks()
			return blockEntities.isEmpty()
		}

		val isEmpty: Boolean get() {
			if (players.isNotEmpty()) {
				return false
			}

			cleanUpBlocks()
			return blockEntities.isEmpty()
		}
	}

	/**
	 * Why track player-tracked chunks?
	 *
	 * because minecraft itself doesn't track them, well,
	 * in "section partitioning" way.
	 *
	 * just look at [net.minecraft.server.level.PlayerMap]
	 *
	 * the [net.minecraft.server.level.PlayerMap.getPlayers] straight ignores chunk position
	 * and just fetches all players
	 *
	 * even if they did not ignore that argument, you still have to fetch player *list* though
	 * [net.minecraft.server.level.ChunkMap.getPlayers], which is not something we want
	 */
	companion object {
		const val REDSTONE_CONTROL_KEY = "redstoneControl"
		const val INVENTORY_KEY = "container"
		const val MATTER_STORAGE_KEY = "matterStorage"
		const val ENERGY_KEY = "energyStorage"
		const val BATTERY_KEY = "batteryInventory"
		const val LOOT_TABLE_KEY = RandomizableContainerBlockEntity.LOOT_TABLE_TAG
		const val LOOT_TABLE_SEED_KEY = RandomizableContainerBlockEntity.LOOT_TABLE_SEED_TAG

		private val playerMap = WeakHashMap<ServerLevel, Long2ObjectOpenHashMap<ChunkSubscribers>>()
		private val tickingMap = WeakHashMap<ServerLevel, ArrayList<WeakReference<ChunkSubscribers>>>()

		private val vec2Dir = Int2ObjectOpenHashMap<Direction>()

		/**
		 * Returns stream of players watching (tracking) specified [chunkPos] in [level]
		 */
		fun watchingPlayers(chunkPos: Long, level: Level): Stream<ServerPlayer> {
			if (level !is ServerLevel)
				return Stream.empty()

			val subs = playerMap[level] ?: return Stream.empty()
			val chunk = subs[chunkPos] ?: return Stream.empty()
			return chunk.players.stream()
		}

		/**
		 * Returns stream of players watching (tracking) specified [chunkPos] in [level]
		 */
		fun watchingPlayers(chunkPos: ChunkPos, level: Level) = watchingPlayers(chunkPos.toLong(), level)

		/**
		 * Returns stream of players watching (tracking) specified [blockPos] in [level]
		 */
		fun watchingPlayers(blockPos: BlockPos, level: Level) = watchingPlayers(ChunkPos(blockPos), level)

		/**
		 * Returns stream of players watching (tracking) specified [blockPos] in [level]
		 */
		fun watchingPlayers(blockPos: Vec3, level: Level) = watchingPlayers(ChunkPos.asLong(SectionPos.blockToSectionCoord(blockPos.x), SectionPos.blockToSectionCoord(blockPos.z)), level)

		/**
		 * Returns stream of players watching (tracking) specified [chunk]
		 */
		fun watchingPlayers(chunk: LevelChunk) = watchingPlayers(chunk.pos, chunk.level)

		private fun vecKey(value: Vec3i): Int {
			if (value.x !in -1 .. 1) return -1
			if (value.y !in -1 .. 1) return -1
			if (value.z !in -1 .. 1) return -1
			val x = if (value.x < 0) 2 else value.x
			val y = if (value.y < 0) 2 else value.y
			val z = if (value.z < 0) 2 else value.z
			return x or (y shl 4) or (z shl 8)
		}

		init {
			for (dir in Direction.values()) {
				vec2Dir[vecKey(dir.normal)] = dir
			}
		}

		fun onLevelUnload(event: LevelEvent.Unload) {
			val level = event.level as? ServerLevel ?: return
			playerMap.remove(level)
			tickingMap.remove(level)
		}

		fun onServerStopping(event: ServerStoppingEvent) {
			playerMap.clear()
			tickingMap.clear()
		}

		fun postLevelTick(event: LevelTickEvent) {
			if (event.phase == TickEvent.Phase.END) {
				val level = event.level as? ServerLevel ?: return
				val listing = tickingMap[level] ?: return
				val listIterator = listing.listIterator()
				var hitAnyObservers = false

				for (value in listIterator) {
					val ref = value.get()

					if (ref == null) {
						listIterator.remove()
					} else if (ref.players.isNotEmpty()) {
						var hitObservers = false

						ref.observingBlockEntities.forValidRefs {
							hitObservers = true
							hitAnyObservers = true
							it.synchronizeToPlayers()
						}

						if (!hitObservers) {
							listIterator.remove()
						}
					}
				}

				if (!hitAnyObservers) {
					tickingMap.remove(level)
				}
			}
		}

		fun onWatch(event: ChunkWatchEvent.Watch) {
			playerMap
				.computeIfAbsent(event.level) { Long2ObjectOpenHashMap() }
				.computeIfAbsent(event.pos.toLong(), Long2ObjectFunction { ChunkSubscribers(level = WeakReference(event.level), chunkPos = event.pos.toLong()) })
			.let {
				val (blocks, players) = it

				if (event.player !in players) {
					players.add(event.player)
					it.changeset++

					onceServer(10) {
						if (!event.player.hasDisconnected() && event.player in players) {
							blocks.forValidRefs {
								it.synchronizeToPlayers(false)
							}
						}
					}
				}
			}
		}

		fun onForget(event: ChunkWatchEvent.UnWatch) {
			val subs = playerMap.get(event.level)?.get(event.pos.toLong()) ?: return

			if (subs.players.remove(event.player)) {
				if (subs.isEmpty) {
					playerMap.get(event.level)?.remove(event.pos.toLong())
				} else {
					subs.changeset++
					subs.blockEntities.forValidRefs {
						it.synchronizer.removeEndpointFor(event.player)
					}
				}
			}
		}

		fun playerDisconnected(event: PlayerEvent.PlayerLoggedOutEvent) {
			for (tree in playerMap.values) {
				val iterator = tree.iterator()

				for (entry in iterator) {
					if (entry.value.players.remove(event.entity)) {
						entry.value.changeset++

						if (entry.value.isEmpty) {
							iterator.remove()
						}
					}
				}
			}
		}
	}
}
