package at.hannibal2.skyhanni.utils

import at.hannibal2.skyhanni.mixins.transformers.AccessorRendererLivingEntity
import at.hannibal2.skyhanni.utils.RenderUtils.getViewerPos
import at.hannibal2.skyhanni.utils.TimeUtils.inWholeTicks
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.GlStateManager
import net.minecraft.client.renderer.entity.RendererLivingEntity
import net.minecraft.entity.EntityLivingBase
import net.minecraft.entity.boss.EntityWither
import net.minecraft.entity.item.EntityArmorStand
import net.minecraft.entity.monster.EntityBlaze
import net.minecraft.entity.monster.EntityCaveSpider
import net.minecraft.entity.monster.EntityCreeper
import net.minecraft.entity.monster.EntityEnderman
import net.minecraft.entity.monster.EntityEndermite
import net.minecraft.entity.monster.EntityGhast
import net.minecraft.entity.monster.EntityGiantZombie
import net.minecraft.entity.monster.EntityGuardian
import net.minecraft.entity.monster.EntityIronGolem
import net.minecraft.entity.monster.EntityMagmaCube
import net.minecraft.entity.monster.EntityPigZombie
import net.minecraft.entity.monster.EntitySilverfish
import net.minecraft.entity.monster.EntitySkeleton
import net.minecraft.entity.monster.EntitySlime
import net.minecraft.entity.monster.EntitySnowman
import net.minecraft.entity.monster.EntitySpider
import net.minecraft.entity.monster.EntityWitch
import net.minecraft.entity.monster.EntityZombie
import net.minecraft.entity.passive.EntityBat
import net.minecraft.entity.passive.EntityChicken
import net.minecraft.entity.passive.EntityCow
import net.minecraft.entity.passive.EntityHorse
import net.minecraft.entity.passive.EntityMooshroom
import net.minecraft.entity.passive.EntityOcelot
import net.minecraft.entity.passive.EntityPig
import net.minecraft.entity.passive.EntityRabbit
import net.minecraft.entity.passive.EntitySheep
import net.minecraft.entity.passive.EntitySquid
import net.minecraft.entity.passive.EntityVillager
import net.minecraft.entity.passive.EntityWolf
import org.lwjgl.opengl.GL11

/**
 * Utility for creating fake entities without an associated world in order to avoid contaminating the world state.
 */
object HolographicEntities {

    /**
     * An instance of a holographic entity. Maintains a minimal controlled state,
     * which has just enough information for rendering and basic manipulations, such as
     * interpolated positioning. The underlying [entity] should not be accessed directly.
     */
    class HolographicEntity<T : EntityLivingBase> internal constructor(
        val entity: T,
        var position: LorenzVec,
        var yaw: Float,
    ) {
        var isChild: Boolean = false
        var lastPosition: LorenzVec = position
        var lastYaw: Float = yaw
        val createdAt = SimpleTimeMark.now()

        val monotonicProgress get() = createdAt.passedSince().inWholeTicks

        /**
         * Should be called exactly once per tick or never over the lifetime of this [HolographicEntity].
         */
        fun moveTo(position: LorenzVec, yaw: Float, isTeleport: Boolean = false) {
            if (isTeleport) {
                this.lastYaw = yaw
                this.lastPosition = position
            } else {
                this.lastYaw = this.yaw
                this.lastPosition = this.position
            }
            this.position = position
            this.yaw = yaw
        }

        fun interpolatedPosition(partialTicks: Float): LorenzVec {
            return lastPosition.slope(position, partialTicks.toDouble())
        }

        fun interpolatedYaw(partialTicks: Float): Float {
            return interpolateRotation(lastYaw, yaw, partialTicks)
        }
    }

    /**
     * Template for a [HolographicEntity]. This class exists as a guard for
     * [HolographicEntity] to prevent untested entities with potential NPEs
     * being instantiated. A list of tested entities exist in [HolographicEntities].
     * Some of these entities rely on mixins from NEU for their proper null
     * world handling.
     */
    class HolographicBase<T : EntityLivingBase> internal constructor(
        private val entity: T
    ) {
        fun instance(position: LorenzVec, yaw: Float): HolographicEntity<T> {
            return HolographicEntity(entity, position, yaw)
        }
    }

    val zombie = HolographicBase(EntityZombie(null))
    val chicken = HolographicBase(EntityChicken(null))
    val slime = HolographicBase(EntitySlime(null))
    val wolf = HolographicBase(EntityWolf(null))
    val skeleton = HolographicBase(EntitySkeleton(null))
    val creeper = HolographicBase(EntityCreeper(null))
    val ocelot = HolographicBase(EntityOcelot(null))
    val blaze = HolographicBase(EntityBlaze(null))
    val rabbit = HolographicBase(EntityRabbit(null))
    val sheep = HolographicBase(EntitySheep(null))
    val horse = HolographicBase(EntityHorse(null))
    val eisengolem = HolographicBase(EntityIronGolem(null))
    val silverfish = HolographicBase(EntitySilverfish(null))
    val witch = HolographicBase(EntityWitch(null))
    val endermite = HolographicBase(EntityEndermite(null))
    val snowman = HolographicBase(EntitySnowman(null))
    val villager = HolographicBase(EntityVillager(null))
    val guardian = HolographicBase(EntityGuardian(null))
    val armorStand = HolographicBase(EntityArmorStand(null))
    val squid = HolographicBase(EntitySquid(null))
    val bat = HolographicBase(EntityBat(null))
    val spider = HolographicBase(EntitySpider(null))
    val caveSpider = HolographicBase(EntityCaveSpider(null))
    val pigman = HolographicBase(EntityPigZombie(null))
    val ghast = HolographicBase(EntityGhast(null))
    val magmaCube = HolographicBase(EntityMagmaCube(null))
    val wither = HolographicBase(EntityWither(null))
    val enderman = HolographicBase(EntityEnderman(null))
    val mooshroom = HolographicBase(EntityMooshroom(null))
    val witherSkeleton = HolographicBase(EntitySkeleton(null).also { it.skeletonType = 1 })
    val cow = HolographicBase(EntityCow(null))
    val pig = HolographicBase(EntityPig(null))
    val giant = HolographicBase(EntityGiantZombie(null))

    private fun interpolateRotation(last: Float, next: Float, progress: Float): Float {
        var direction: Float = next - last
        while (direction < -180.0f) {
            direction += 360.0f
        }
        while (direction >= 180.0f) {
            direction -= 360.0f
        }
        return last + progress * direction
    }

    /**
     * Render a fake [HolographicEntity]. In order to render a fully opaque entity, set [holographicness] to `1F`.
     */
    fun <T : EntityLivingBase> renderHolographicEntity(
        holographicEntity: HolographicEntity<T>,
        partialTicks: Float,
        holographicness: Float = 0.3f
    ) {
        val renderManager = Minecraft.getMinecraft().renderManager
        val renderer = renderManager.getEntityRenderObject<EntityLivingBase>(holographicEntity.entity)
        renderer as RendererLivingEntity<T>
        renderer as AccessorRendererLivingEntity<T>

        renderer.setRenderOutlines(false)
        if (!renderer.bindEntityTexture_skyhanni(holographicEntity.entity))
            return

        GlStateManager.pushMatrix()
        val viewerPosition = getViewerPos(partialTicks)
        val mobPosition = holographicEntity.interpolatedPosition(partialTicks)
        val renderingOffset = mobPosition.subtract(viewerPosition)
        renderingOffset.applyTranslationToGL()
        GlStateManager.disableCull()
        GlStateManager.enableRescaleNormal()
        GlStateManager.scale(-1f, -1f, 1f)
        GlStateManager.translate(0F, -1.5078125f, 0f)
        val limbSwing: Float = 0F
        val limbSwingAmount: Float = 0F
        val ageInTicks: Float = 1_000_000.toFloat()
        val netHeadYaw: Float = holographicEntity.interpolatedYaw(partialTicks)
        val headPitch: Float = 0F
        val scaleFactor: Float = 0.0625f
        renderer.setBrightness_skyhanni(holographicEntity.entity, 0f, true)
        GlStateManager.color(1.0f, 1.0f, 1.0f, holographicness)
        GlStateManager.depthMask(false)
        GlStateManager.enableBlend()
        GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA)
        GlStateManager.alphaFunc(GL11.GL_GREATER, 1 / 255F)

        GlStateManager.enableTexture2D()
        renderer.mainModel.isChild = holographicEntity.isChild
        renderer.mainModel.setRotationAngles(
            limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch, scaleFactor, holographicEntity.entity
        )
        renderer.mainModel.render(
            holographicEntity.entity,
            limbSwing,
            limbSwingAmount,
            ageInTicks,
            netHeadYaw,
            headPitch,
            scaleFactor
        )
        GlStateManager.alphaFunc(GL11.GL_GREATER, 0.1f)
        GlStateManager.color(1f, 1f, 1f, 1f)
        GlStateManager.depthMask(true)
        GlStateManager.disableBlend()
        GlStateManager.popMatrix()
    }

}