package ru.dbotthepony.mc.otm.item.tool

import com.mojang.serialization.Codec
import net.minecraft.ChatFormatting
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.util.StringRepresentable
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.InteractionResultHolder
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.context.UseOnContext
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.gameevent.GameEvent
import net.neoforged.neoforge.event.entity.player.UseItemOnBlockEvent
import ru.dbotthepony.mc.otm.core.TranslatableComponent
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.math.plus
import ru.dbotthepony.mc.otm.core.util.TickList
import ru.dbotthepony.mc.otm.item.MatteryItem
import ru.dbotthepony.mc.otm.registry.game.MDataComponentTypes
import ru.dbotthepony.mc.otm.once
import java.util.*

class RedstoneInteractorItem : MatteryItem(Properties().stacksTo(1)) {
	init {
		tooltips.add { TranslatableComponent("$descriptionId.desc").withStyle(ChatFormatting.GRAY) }
		tooltips.add { TranslatableComponent("$descriptionId.desc1").withStyle(ChatFormatting.GRAY) }
		tooltips.add { TranslatableComponent("$descriptionId.desc2").withStyle(ChatFormatting.GRAY) }
	}

	private fun updateTickTimer(level: Level, player: Player, itemStack: ItemStack) {
		val current = itemStack[MDataComponentTypes.TICK_TIMER] ?: TickTimer.TICK_30
		val next = TickTimer.entries.getOrElse(current.ordinal + 1) { RedstoneInteractorItem.TickTimer.TICK_2 }

		if (level.isClientSide) {
			player.sendSystemMessage(TranslatableComponent("otm.gui.tick_timer_set", next.ticks.toString()))
			// play sound in front of player so it sounds properly with HRTF enabled
			val (x, y, z) = player.getEyePosition(1f) + player.getViewVector(1f)
			level.playSound(player, x, y, z, SoundEvents.DISPENSER_FAIL, SoundSource.PLAYERS, 0.1f, 1.1f)
		}

		itemStack[MDataComponentTypes.TICK_TIMER] = next
	}

	override fun use(level: Level, player: Player, hand: InteractionHand): InteractionResultHolder<ItemStack> {
		if (player.isShiftKeyDown) {
			val itemStack = player.getItemInHand(hand)
			updateTickTimer(level, player, itemStack)
			return InteractionResultHolder.success(itemStack)
		}

		return super.use(level, player, hand)
	}

	override fun useOn(context: UseOnContext): InteractionResult {
		if (context.player?.isShiftKeyDown == true) {
			updateTickTimer(context.level, context.player!!, context.itemInHand)
			return InteractionResult.sidedSuccess(context.level.isClientSide())
		}

		context.level.playSound(context.player, context.clickedPos, SoundEvents.STONE_BUTTON_CLICK_ON, SoundSource.BLOCKS, 1f, 1f)
		context.level.gameEvent(context.player, GameEvent.BLOCK_ACTIVATE, context.clickedPos)

		if (context.level.isClientSide)
			return InteractionResult.SUCCESS

		val map = signals.computeIfAbsent(context.level) { HashMap() }
		val positions = Direction.entries.map { context.clickedPos + it.normal }.toMutableList()
		positions.add(context.clickedPos)

		for (pos in positions) {
			val shouldSendUpdate = pos !in map

			val timer = context.level.once(context.itemInHand.getOrDefault(MDataComponentTypes.TICK_TIMER, TickTimer.TICK_30).ticks) {
				map.remove(pos)
				context.level.updateNeighborsAt(pos, context.level.getBlockState(pos).block)
			} ?: throw RuntimeException("Timer unexpectedly ended up being null")

			map.put(pos, timer)?.cancel()

			if (shouldSendUpdate) {
				context.level.updateNeighborsAt(pos, context.level.getBlockState(pos).block)
			}
		}

		return InteractionResult.CONSUME
	}

	enum class TickTimer(val ticks: Int) : StringRepresentable {
		TICK_2(2),
		TICK_10(10),
		TICK_20(20),
		TICK_30(30),
		TICK_40(40);

		private val str = ticks.toString()

		override fun getSerializedName(): String {
			return str
		}

		companion object {
			val CODEC: Codec<TickTimer> = StringRepresentable.fromEnum(TickTimer::values)
		}
	}

	companion object {
		private val signals = WeakHashMap<Level, HashMap<BlockPos, TickList.Timer>>()

		fun getSignalHook(level: Level, blockState: BlockState, blockPos: BlockPos, direction: Direction): Int {
			if (signals[level]?.containsKey(blockPos) == true)
				return 15

			return -1
		}

		fun onUse(event: UseItemOnBlockEvent) {
			if (event.itemStack.item is RedstoneInteractorItem) {
				if ((event.itemStack.item as RedstoneInteractorItem).useOn(event.useOnContext).consumesAction())
					event.cancelWithResult(ItemInteractionResult.sidedSuccess(event.level.isClientSide()))
			}
		}
	}
}
