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

import com.google.common.collect.ImmutableMultimap
import com.google.common.collect.Multimap
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import net.minecraft.ChatFormatting
import net.minecraft.core.BlockPos
import net.minecraft.network.chat.Component
import net.minecraft.network.protocol.game.ClientboundExplodePacket
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.EquipmentSlot
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.attributes.Attribute
import net.minecraft.world.entity.ai.attributes.AttributeModifier
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import net.minecraft.world.level.Explosion
import net.minecraft.world.level.Level
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.event.entity.player.PlayerInteractEvent
import net.minecraftforge.event.level.BlockEvent
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
import ru.dbotthepony.mc.otm.capability.matteryEnergy
import ru.dbotthepony.mc.otm.config.ItemsConfig
import ru.dbotthepony.mc.otm.config.ToolsConfig
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.damageType
import ru.dbotthepony.mc.otm.core.getExplosionResistance
import ru.dbotthepony.mc.otm.core.gracefulBlockBreak
import ru.dbotthepony.mc.otm.core.immutableMap
import ru.dbotthepony.mc.otm.core.immutableMultimap
import ru.dbotthepony.mc.otm.core.isExplosion
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.math.times
import ru.dbotthepony.mc.otm.core.math.toDoubleVector
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.position
import ru.dbotthepony.mc.otm.core.tagNotNull
import ru.dbotthepony.mc.otm.item.weapon.EnergySwordItem
import ru.dbotthepony.mc.otm.registry.MDamageTypes
import ru.dbotthepony.mc.otm.registry.MRegistry
import ru.dbotthepony.mc.otm.registry.MatteryDamageSource
import ru.dbotthepony.mc.otm.triggers.NailedEntityTrigger

class ExplosiveHammerItem(durability: Int = 512) : Item(Properties().stacksTo(1).fireResistant().durability(durability)) {
	override fun canBeHurtBy(pDamageSource: DamageSource): Boolean {
		return super.canBeHurtBy(pDamageSource) && !pDamageSource.isExplosion
	}

	fun isPrimed(itemStack: ItemStack): Boolean {
		return itemStack.tag?.getBoolean("primed") ?: false
	}

	fun prime(itemStack: ItemStack) {
		itemStack.tagNotNull["primed"] = true
	}

	fun unprime(itemStack: ItemStack) {
		if (isPrimed(itemStack))
			itemStack.tagNotNull["primed"] = false
	}

	private val aabb = AABB(-0.1, -0.1, -0.1, 0.1, 0.1, 0.1)

	val attributes = immutableMultimap {
		put(Attributes.ATTACK_DAMAGE, AttributeModifier(BASE_ATTACK_DAMAGE_UUID, "Weapon modifier", 3.0, AttributeModifier.Operation.ADDITION))
		put(Attributes.ATTACK_SPEED, AttributeModifier(BASE_ATTACK_SPEED_UUID, "Weapon modifier", -3.4, AttributeModifier.Operation.ADDITION))
	}

	override fun getAttributeModifiers(
		slot: EquipmentSlot,
		itemStack: ItemStack
	): Multimap<Attribute, AttributeModifier> {
		if (slot != EquipmentSlot.MAINHAND) {
			return ImmutableMultimap.of()
		}

		return attributes
	}

	fun attackAt(itemStack: ItemStack, attacker: LivingEntity, pos: Vec3, aim: Vec3, hand: InteractionHand) {
		if (!isPrimed(itemStack) || attacker.level().isClientSide || attacker is Player && attacker.getAttackStrengthScale(0.4f) < 0.98f)
			return

		val (ex, ey, ez) = pos

		// взрыв в месте удара молотком
		val exp = Explosion(attacker.level(), attacker, ex, ey, ez, 1f, false, if (ToolsConfig.ExplosiveHammer.EXPLOSION_DAMAGE_BLOCKS) Explosion.BlockInteraction.DESTROY_WITH_DECAY else Explosion.BlockInteraction.KEEP)
		exp.explode()
		exp.finalizeExplosion(true)

		if (!ToolsConfig.ExplosiveHammer.EXPLOSION_DAMAGE_BLOCKS)
			exp.clearToBlow()

		val level = attacker.level() as ServerLevel

		val hitEntities = ObjectArraySet<LivingEntity>()
		hitEntities.add(attacker)
		var canTravel = ToolsConfig.ExplosiveHammer.MAX_TRAVEL
		var rayPos = pos
		var lastBlockPos: BlockPos? = null

		// "полёт" гвоздя
		// так как у меня алгоритм "своеобразный", я изобрету велосипед ибо я такой нехороший и непослушный
		// Трассировка луча используя наивный метод
		while (canTravel > 0.0) {
			val blockPos = BlockPos.containing(rayPos)

			if (blockPos != lastBlockPos) {
				if (!level.hasChunkAt(blockPos)) break

				val block = level.getBlockState(blockPos)

				if (!block.isAir) {
					val resist = block.getExplosionResistance(level, blockPos) * ToolsConfig.ExplosiveHammer.TRAVEL_EXPLOSION_RESIST_MULT

					if (resist >= 0.0) {
						if (resist <= canTravel) {
							canTravel -= resist
							val cond = ToolsConfig.ExplosiveHammer.DAMAGE_BLOCKS &&
									(attacker !is Player || level.mayInteract(attacker, blockPos) && !MinecraftForge.EVENT_BUS.post(BlockEvent.BreakEvent(level, blockPos, block, attacker)))

							if (cond) {
								level.gracefulBlockBreak(blockPos, block)
							}
						} else {
							break
						}
					}
				}

				lastBlockPos = blockPos
			}

			val rayBox = aabb.move(rayPos)
			val entities = level.getEntities(null, rayBox) { it is LivingEntity && it.isAlive && !it.isSpectator && hitEntities.add(it) } as List<LivingEntity>
			val damageSource = MatteryDamageSource(level.registryAccess().damageType(MDamageTypes.HAMMER_NAIL), attacker, itemStack)

			for (it in entities) {
				val damage = canTravel * ToolsConfig.ExplosiveHammer.TRAVEL_DAMAGE_MULT
				canTravel -= it.maxHealth * ToolsConfig.ExplosiveHammer.TRAVEL_MAX_HEALTH_MULT

				it.hurt(damageSource, damage.toFloat())

				if (attacker is ServerPlayer) {
					NailedEntityTrigger.trigger(attacker, it, damage.toFloat(), damageSource)
				}
			}

			val delta = canTravel.coerceAtMost(0.25)
			canTravel -= delta
			rayPos += aim * delta
		}

		MatteryBlockEntity.watchingPlayers(pos, level)
			.filter { it.position.distanceTo(pos) <= 64.0 }
			.forEach {
				it.connection.send(ClientboundExplodePacket(ex, ey, ez, 1f, exp.toBlow, exp.hitPlayers[it]))
			}

		if (attacker !is Player || !attacker.isCreative) {
			unprime(itemStack)

			val copy = itemStack.copy()

			itemStack.hurtAndBreak(level.random.nextInt(1, 20), attacker) {
				it.broadcastBreakEvent(hand)
			}

			if (!itemStack.isEmpty && attacker.random.nextDouble() <= ToolsConfig.ExplosiveHammer.FLY_OFF_CHANCE) {
				attacker.setItemInHand(hand, ItemStack.EMPTY)

				val (x, y, z) = attacker.position
				val entity = ItemEntity(level, x, y, z, itemStack)

				val (xv, yv, zv) = attacker.getViewVector(0f)

				entity.deltaMovement = Vec3(
					attacker.random.nextDouble() * xv * -0.5,
					attacker.random.nextDouble() * yv * -0.5,
					(attacker.random.nextDouble() + 0.65) * zv * -2.0,
				)

				level.addFreshEntity(entity)

				if (attacker.random.nextDouble() <= ToolsConfig.ExplosiveHammer.FLY_OFF_DAMAGE_CHANCE) {
					attacker.invulnerableTime = 0
					val dmg = ToolsConfig.ExplosiveHammer.FLY_OFF_MIN_DAMAGE + attacker.random.nextDouble() * (ToolsConfig.ExplosiveHammer.FLY_OFF_MAX_DAMAGE - ToolsConfig.ExplosiveHammer.FLY_OFF_MIN_DAMAGE)
					attacker.hurt(MatteryDamageSource(level.registryAccess().damageType(MDamageTypes.EXPLOSIVE_HAMMER), inflictor = copy), dmg.toFloat())
				}
			} else if (attacker.random.nextDouble() <= ToolsConfig.ExplosiveHammer.SELF_HARM_CHANCE) {
				attacker.invulnerableTime = 0
				val dmg = ToolsConfig.ExplosiveHammer.SELF_HARM_MIN_DAMAGE + attacker.random.nextDouble() * (ToolsConfig.ExplosiveHammer.SELF_HARM_MAX_DAMAGE - ToolsConfig.ExplosiveHammer.SELF_HARM_MIN_DAMAGE)
				attacker.hurt(MatteryDamageSource(level.registryAccess().damageType(MDamageTypes.EXPLOSIVE_HAMMER), inflictor = copy), dmg.toFloat())
			}
		}
	}

	override fun hurtEnemy(pStack: ItemStack, pTarget: LivingEntity, pAttacker: LivingEntity): Boolean {
		val aim = pAttacker.getViewVector(0f)
		val pos = pAttacker.eyePosition + aim * pTarget.position.distanceTo(pAttacker.position)
		attackAt(pStack, pAttacker, pos, aim, InteractionHand.MAIN_HAND)
		return true
	}

	override fun appendHoverText(pStack: ItemStack, pLevel: Level?, pTooltipComponents: MutableList<Component>, pIsAdvanced: TooltipFlag) {
		super.appendHoverText(pStack, pLevel, pTooltipComponents, pIsAdvanced)

		pTooltipComponents.add(TranslatableComponent("$descriptionId.desc").withStyle(ChatFormatting.DARK_GRAY))

		if (isPrimed(pStack)) {
			pTooltipComponents.add(TranslatableComponent("$descriptionId.primed").withStyle(ChatFormatting.GRAY))
		} else {
			pTooltipComponents.add(TranslatableComponent("$descriptionId.not_primed0").withStyle(ChatFormatting.GRAY))
			pTooltipComponents.add(TranslatableComponent("$descriptionId.not_primed1").withStyle(ChatFormatting.GRAY))
		}
	}

	companion object {
		fun onLeftClickBlock(event: PlayerInteractEvent.LeftClickBlock) {
			val item = event.itemStack.item

			if (item is ExplosiveHammerItem) {
				val (x, y, z) = event.pos
				item.attackAt(event.itemStack, event.entity, Vec3(x + 0.5, y + 0.5, z + 0.5), event.face?.opposite?.normal?.toDoubleVector() ?: event.entity.getViewVector(0f), event.hand)
				event.isCanceled = true
			}
		}
	}
}
