package ru.dbotthepony.mc.otm.block

import com.google.common.collect.ImmutableMap
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.particles.DustParticleOptions
import net.minecraft.network.chat.Component
import net.minecraft.util.RandomSource
import net.minecraft.world.Containers
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.MenuProvider
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.Property
import net.minecraft.world.level.material.MapColor
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.VoxelShape
import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
import ru.dbotthepony.mc.otm.block.entity.IRedstoneControlled
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.block.entity.WorkerState
import ru.dbotthepony.mc.otm.core.get
import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom
import ru.dbotthepony.mc.otm.core.math.component1
import ru.dbotthepony.mc.otm.core.math.component2
import ru.dbotthepony.mc.otm.core.math.component3
import ru.dbotthepony.mc.otm.core.tagNotNull
import ru.dbotthepony.mc.otm.once

fun Block.getShapeForEachState(properties: List<Property<*>>, fn: (BlockState) -> VoxelShape): Map<BlockState, VoxelShape> {
	val builder = ImmutableMap.Builder<BlockState, VoxelShape>()

	if (properties.isEmpty()) {
		val shape = fn(stateDefinition.possibleStates.first())

		for (state in stateDefinition.possibleStates) {
			builder.put(state, shape)
		}
	} else {
		val cache = Object2ObjectArrayMap<List<Any>, VoxelShape>()

		for (state in stateDefinition.possibleStates) {
			builder.put(state, cache.computeIfAbsent(properties.map { state[it] }, Object2ObjectFunction { fn(state) }))
		}
	}

	return builder.build()
}

fun Block.getShapeForEachState(property: Property<*>, fn: (BlockState) -> VoxelShape): Map<BlockState, VoxelShape> {
	return getShapeForEachState(listOf(property), fn)
}

abstract class MatteryBlock @JvmOverloads constructor(
	properties: Properties = DEFAULT_PROPERTIES
) : Block(properties) {
	override fun setPlacedBy(
		level: Level,
		blockPos: BlockPos,
		blockState: BlockState,
		entity: LivingEntity?,
		itemStack: ItemStack
	) {
		if (this is EntityBlock && itemStack.hasCustomHoverName() && !level.isClientSide) {
			val tile = level.getBlockEntity(blockPos)

			if (tile is MatteryDeviceBlockEntity) {
				try {
					tile.customDisplayName = Component.Serializer.fromJson(itemStack.tagNotNull.getCompound("display").getString("Name"))
				} catch(_: Exception) {

				}
			}
		}

		super.setPlacedBy(level, blockPos, blockState, entity, itemStack)
	}

	@Suppress("OVERRIDE_DEPRECATION")
	override fun use(
		blockState: BlockState,
		level: Level,
		blockPos: BlockPos,
		ply: Player,
		hand: InteractionHand,
		blockHitResult: BlockHitResult
	): InteractionResult {
		if (this is EntityBlock && !level.isClientSide) {
			val tile = level.getBlockEntity(blockPos)

			if (tile is MenuProvider) {
				ply.openMenu(tile)
				return InteractionResult.CONSUME
			}
		}

		if (this is EntityBlock && level.isClientSide)
			return InteractionResult.SUCCESS

		@Suppress("DEPRECATION")
		return super.use(blockState, level, blockPos, ply, hand, blockHitResult)
	}

	override fun animateTick(blockState: BlockState, level: Level, blockPos: BlockPos, random: RandomSource) {
		if (this is EntityBlock) {
			val state = blockState.getOptionalValue(WorkerState.WORKER_STATE).or { blockState.getOptionalValue(WorkerState.SEMI_WORKER_STATE) }

			if (state.isPresent && state.get() == WorkerState.WORKING) {
				val state2 = blockState.getOptionalValue(BlockRotationFreedom.ONE.property)
					.or { blockState.getOptionalValue(BlockRotationFreedom.TWO.property) }
					.or { blockState.getOptionalValue(BlockRotationFreedom.THREE.property) }

				if (state2.isPresent) {
					val direction = state2.get()

					val (x, y, z) = blockPos
					var xd = x + 0.5
					var yd = y + 0.5
					var zd = z + 0.5

					val (nx, ny, nz) = direction.normal
					xd += nx * 0.5
					yd += ny * 0.5
					zd += nz * 0.5

					when (direction.front) {
						Direction.DOWN, Direction.UP -> {
							xd += random.nextDouble() - 0.5
							zd += random.nextDouble() - 0.5
						}

						Direction.NORTH, Direction.SOUTH -> {
							xd += random.nextDouble() - 0.5
							yd += random.nextDouble() - 0.5
						}

						Direction.WEST, Direction.EAST -> {
							yd += random.nextDouble() - 0.5
							zd += random.nextDouble() - 0.5
						}
					}

					level.addParticle(DustParticleOptions.REDSTONE, xd, yd, zd, 0.0, 0.0, 0.0)
				}
			}
		}
	}

	@Suppress("OVERRIDE_DEPRECATION")
	override fun getMenuProvider(blockState: BlockState, level: Level, blockPos: BlockPos): MenuProvider? {
		if (this is EntityBlock) {
			val tile = level.getBlockEntity(blockPos)

			if (tile is MenuProvider) {
				return tile
			}
		}

		return null
	}

	@Suppress("OVERRIDE_DEPRECATION")
	override fun neighborChanged(
		state: BlockState,
		level: Level,
		pos: BlockPos,
		neighbour: Block,
		neighbourPos: BlockPos,
		movedByPiston: Boolean
	) {
		@Suppress("DEPRECATION")
		super.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston)

		if (this is EntityBlock && !level.isClientSide) {
			val tile = level.getBlockEntity(pos) ?: return

			level.once {
				if (!tile.isRemoved) {
					if (tile is IRedstoneControlled)
						tile.redstoneControl.redstoneSignal = level.getBestNeighborSignal(pos)

					if (tile is MatteryBlockEntity && SERVER_IS_LIVE)
						tile.neighborChanged(neighbour, neighbourPos, movedByPiston)
				}
			}
		}
	}

	@Suppress("OVERRIDE_DEPRECATION")
	override fun onRemove(
		oldBlockState: BlockState,
		level: Level,
		blockPos: BlockPos,
		newBlockState: BlockState,
		movedByPiston: Boolean
	) {
		if (!oldBlockState.`is`(newBlockState.block) && !level.isClientSide) {
			val blockentity = level.getBlockEntity(blockPos)

			if (blockentity is MatteryBlockEntity) {
				blockentity.beforeDroppingItems(oldBlockState, level, blockPos, newBlockState, movedByPiston)

				for (container in blockentity.droppableContainers)
					Containers.dropContents(level, blockPos, container)

				level.updateNeighbourForOutputSignal(blockPos, this)
			}
		}

		@Suppress("DEPRECATION")
		super.onRemove(oldBlockState, level, blockPos, newBlockState, movedByPiston)
	}

	override fun playerWillDestroy(level: Level, blockPos: BlockPos, blockState: BlockState, player: Player) {
		super.playerWillDestroy(level, blockPos, blockState, player)

		val blockentity = level.getBlockEntity(blockPos)

		if (blockentity is MatteryBlockEntity) {
			blockentity.beforeDestroyedByPlayer(level, blockPos, blockState, player)
		}
	}

	companion object {
		val DEFAULT_PROPERTIES: Properties = Properties.of().mapColor(MapColor.METAL).requiresCorrectToolForDrops().destroyTime(1.5f).explosionResistance(25.0f)
	}
}

