package ru.dbotthepony.mc.otm.block.tech

import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.SectionPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.SoundType
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockBehaviour
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.material.MapColor
import net.minecraft.world.level.material.PushReaction
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.VoxelShape
import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock
import ru.dbotthepony.mc.otm.block.entity.tech.GravitationStabilizerBlockEntity
import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleBlockEntity
import ru.dbotthepony.mc.otm.block.entity.WorkerState
import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom
import ru.dbotthepony.mc.otm.core.math.facingTwo
import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.math.times
import ru.dbotthepony.mc.otm.oncePre
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.registry.MBlocks
import ru.dbotthepony.mc.otm.shapes.BlockShapes
import kotlin.math.PI

private val props = BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BLUE).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(3f, 600.0f)

class BlockGravitationStabilizer : RotatableMatteryBlock(props), EntityBlock {
	override fun newBlockEntity(p_153215_: BlockPos, p_153216_: BlockState): BlockEntity {
		return GravitationStabilizerBlockEntity(p_153215_, p_153216_)
	}

	override fun <T : BlockEntity?> getTicker(
		p_153212_: Level,
		p_153213_: BlockState,
		p_153214_: BlockEntityType<T>
	): BlockEntityTicker<T>? {
		if (p_153212_.isClientSide || p_153214_ !== MBlockEntities.GRAVITATION_STABILIZER)
			return null

		return BlockEntityTicker { level, _, _, tile -> if (tile is GravitationStabilizerBlockEntity) tile.tick(level) }
	}

	override fun rotationFreedom(): BlockRotationFreedom {
		return BlockRotationFreedom.TWO
	}

	override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
		super.createBlockStateDefinition(builder)
		builder.add(WorkerState.SEMI_WORKER_STATE)
	}

	override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
		var state = super.getStateForPlacement(context) ?: return null
		val blockPos = context.clickedPos
		val level = context.level

		for (face in BlockRotationFreedom.TWO.possibleValues) {
			val dir = face.normal

			for (i in 1 ..GravitationStabilizerBlockEntity.RANGE) {
				val pos = blockPos + dir * i
				val chunk = level.chunkSource.getChunkNow(SectionPos.blockToSectionCoord(pos.x), SectionPos.blockToSectionCoord(pos.z)) ?: continue
				val getState = chunk.getBlockState(pos)

				if (!getState.isAir) {
					if (chunk.getBlockEntity(pos) is BlackHoleBlockEntity) {
						state = state.setValue(BlockRotationFreedom.TWO.property, face)
						break
					}
				}
			}
		}

		val bbPos = blockPos + state.getValue(BlockRotationFreedom.TWO.property).normal
		if (!context.level.isInWorldBounds(bbPos)) return null

		if (!level.getBlockState(bbPos).canBeReplaced(context)) return null

		return state
	}

	override fun setPlacedBy(
		level: Level,
		blockPos: BlockPos,
		blockState: BlockState,
		entity: LivingEntity?,
		itemStack: ItemStack
	) {
		super.setPlacedBy(level, blockPos, blockState, entity, itemStack)

		if (!level.isClientSide) {
			val bbPos = blockPos + blockState.getValue(BlockRotationFreedom.TWO.property).normal
			val newState = MBlocks.GRAVITATION_STABILIZER_LENS.defaultBlockState()
				.setValue(BlockRotationFreedom.TWO.property, blockState.getValue(BlockRotationFreedom.TWO.property))

			level.setBlock(bbPos, newState, UPDATE_ALL)
		}
	}

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

		level.oncePre {
			if (level.getBlockState(pos).block !is BlockGravitationStabilizer) return@oncePre

			val bb = getBoundingBlock(level, state, pos)
			if (bb.block !is BlockGravitationStabilizerLens) {
				level.setBlock(pos, Blocks.AIR.defaultBlockState(), UPDATE_ALL)
			}
		}
	}

	override fun getShape(
		p_60555_: BlockState,
		p_60556_: BlockGetter,
		p_60557_: BlockPos,
		p_60558_: CollisionContext
	): VoxelShape {
		return SHAPES[p_60555_.facingTwo.ordinal]
	}

	companion object {
		private val SHAPES = arrayOf(
			BlockShapes.GRAVITATION_STABILIZER.rotateAroundX(PI / 2).computeShape(),
			BlockShapes.GRAVITATION_STABILIZER.rotateAroundX(-PI / 2).computeShape(),
			BlockShapes.GRAVITATION_STABILIZER.computeShape(),
			BlockShapes.GRAVITATION_STABILIZER.rotateFromSouth(Direction.NORTH).computeShape(),
			BlockShapes.GRAVITATION_STABILIZER.rotateFromSouth(Direction.WEST).computeShape(),
			BlockShapes.GRAVITATION_STABILIZER.rotateFromSouth(Direction.EAST).computeShape()
		)

		fun getBoundingBlock(level: Level, blockState: BlockState, blockPos: BlockPos): BlockState {
			return level.getBlockState(getBoundingBlockPos(blockState, blockPos))
		}

		fun getBoundingBlockPos(blockState: BlockState, blockPos: BlockPos): BlockPos {
			return blockPos + blockState.getValue(BlockRotationFreedom.TWO.property).normal
		}
	}
}

class BlockGravitationStabilizerLens : RotatableMatteryBlock(props) {
	override fun rotationFreedom(): BlockRotationFreedom {
		return BlockRotationFreedom.TWO
	}

	override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
		super.createBlockStateDefinition(builder)
		builder.add(WorkerState.SEMI_WORKER_STATE)
	}

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

		level.oncePre {
			if (level.getBlockState(pos).block !is BlockGravitationStabilizerLens) return@oncePre

			val bb = getBoundingBlock(level, state, pos)
			if (bb.block !is BlockGravitationStabilizer) {
				level.setBlock(pos, Blocks.AIR.defaultBlockState(), UPDATE_ALL)
			}
		}
	}

	override fun getShape(
		p_60555_: BlockState,
		p_60556_: BlockGetter,
		p_60557_: BlockPos,
		p_60558_: CollisionContext
	): VoxelShape {
		return SHAPES[p_60555_.facingTwo.ordinal]
	}

	companion object {
		fun getBoundingBlock(level: Level, blockState: BlockState, blockPos: BlockPos): BlockState {
			return level.getBlockState(getBoundingBlockPos(blockState, blockPos))
		}

		fun getBoundingBlockPos(blockState: BlockState, blockPos: BlockPos): BlockPos {
			return blockPos + blockState.getValue(BlockRotationFreedom.TWO.property).front.opposite.normal
		}

		private val SHAPES = arrayOf(
			BlockShapes.GRAVITATION_STABILIZER_LENS.rotateAroundX(PI / 2).computeShape(),
			BlockShapes.GRAVITATION_STABILIZER_LENS.rotateAroundX(-PI / 2).computeShape(),
			BlockShapes.GRAVITATION_STABILIZER_LENS.computeShape(),
			BlockShapes.GRAVITATION_STABILIZER_LENS.rotateFromSouth(Direction.NORTH).computeShape(),
			BlockShapes.GRAVITATION_STABILIZER_LENS.rotateFromSouth(Direction.WEST).computeShape(),
			BlockShapes.GRAVITATION_STABILIZER_LENS.rotateFromSouth(Direction.EAST).computeShape()
		)
	}
}
