package net.halbear.aotg.rendering.skybox;

import com.mojang.blaze3d.buffers.GpuBuffer;
import com.mojang.blaze3d.buffers.GpuBufferSlice;
import com.mojang.blaze3d.pipeline.RenderPipeline;
import com.mojang.blaze3d.systems.RenderPass;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.textures.GpuSampler;
import com.mojang.blaze3d.textures.GpuTextureView;
import com.mojang.blaze3d.vertex.*;
import com.mojang.math.Axis;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.client.renderer.state.level.SkyRenderState;
import net.minecraft.client.renderer.texture.AbstractTexture;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.RenderLevelStageEvent;
import org.joml.Matrix4f;
import org.joml.Matrix4fStack;
import org.joml.Vector3f;
import org.joml.Vector4f;

import java.util.List;
import java.util.OptionalDouble;
import java.util.OptionalInt;

@EventBusSubscriber(Dist.CLIENT)
public class SkyBoxRendering {
    public static RenderPipeline CUSTOM_SKYBOX_PIPELINE;

    private static GpuBuffer sunBuffer;
    private static GpuBuffer moonBuffer;
    private static GpuBuffer skyboxBuffer;
    private static final ResourceKey OVERWORLD = ResourceKey.create(Registries.DIMENSION, Identifier.parse("minecraft:overworld"));
    private static final Identifier OVERWORLD_DAY_SKYBOX = Identifier.parse("ageofthegods:textures/skybox/skyboxcubemapday.png");
    private static final Identifier OVERWORLD_DUSK_SKYBOX = Identifier.parse("ageofthegods:textures/skybox/cubemaptransitionskybox.png");
    private static final Identifier OVERWORLD_NIGHT_SKYBOX = Identifier.parse("ageofthegods:textures/skybox/cubemapnightskybox.png");
    private static final Identifier OVERWORLD_SUN = Identifier.parse("ageofthegods:textures/suntexture.png");
    private static final Identifier OVERWORLD_MOON = Identifier.parse("ageofthegods:textures/moontexture.png");

    static{
        Identifier[] clouds = new Identifier[]{
                Identifier.parse("ageofthegods:textures/cloud2.png"),
                Identifier.parse("ageofthegods:textures/cloud3.png"),
                Identifier.parse("ageofthegods:textures/smallcloud1.png"),
                Identifier.parse("ageofthegods:textures/smallcloud2.png"),
                Identifier.parse("ageofthegods:textures/smallcloud3.png"),
        };
        double[] aspectRatios = new double[]{1.5625,1,1,1,3.71875};

        for(int i = 0; i < 128; i++){

            int Index = (int)Math.min(clouds.length * Math.random(), clouds.length - 1);

            String name = "Cloud"+ i;
            float Speed = 0.05f +  0.1f *(float)Math.random();
            DistantSkyObject.Action TickUpdateAction = new DistantSkyObject.Action() {
                @Override
                public void execute() {
                    DistantSkyObject object = DistantSkyObject.SkyObjectRegistry.get(name);
                    object.AddHorizontalDegrees(Speed);
                    float DayAlpha = DistantSkyObject.DayAlpha;
                    float NightAlpha = DistantSkyObject.NightAlpha;
                    object.GetColourModifiers().set(0.75 + (DayAlpha * 0.25f) - (NightAlpha * 0.65f),0.35 + (DayAlpha * 0.65f) - (NightAlpha * 0.25f),0.25 + (DayAlpha * 0.75f) - (NightAlpha * 0.1f),1);
                }
            };

            float Scale = 2f + (float)Math.random() * 12;

            new DistantSkyObject(name, clouds[Index], aspectRatios[Index], //Object ID, texture, mesh aspect ratio
                    (float)Math.toRadians(360*Math.random()),(float)Math.toRadians(-130F + (60 * Math.random())), // horizontal angle, vertical angle
                    new Vector3f(0,100,0),new Vector3f(Scale,-1,Scale),new Vector4f(1,1,1,1)) // translation, scale, colour modifiers
                    .AddToDimension(OVERWORLD)
                    .OverrideTickFunction(TickUpdateAction); // this plays on client tick update, thats how the clouds move rn
        }
    }

    private static void initBuffers() {
        if (sunBuffer == null)
            sunBuffer = buildCelestialBuffer(SunScale, "Sun");
        if (moonBuffer == null)
            moonBuffer = buildCelestialBuffer(MoonScale, "Moon");
        if (skyboxBuffer == null)
            skyboxBuffer = buildSkyboxBuffer();
    }

    @SubscribeEvent
    public static void renderSky(RenderLevelStageEvent.AfterSky event) {
        Minecraft minecraft = Minecraft.getInstance();
        if (minecraft.player == null)
            return;
        if (minecraft.player.level().dimension() == OVERWORLD) {
            renderCustomSkybox(event, OVERWORLD_DAY_SKYBOX,OVERWORLD_NIGHT_SKYBOX, OVERWORLD_DUSK_SKYBOX);
            renderCustomSun(event, OVERWORLD_SUN);
            renderCustomMoon(event, OVERWORLD_MOON);
        }
        List<DistantSkyObject> SkyObjects = DistantSkyObject.GetAllObjectsInDimenstion(minecraft.player.level().dimension());
        if(!SkyObjects.isEmpty()){
            for (DistantSkyObject object : SkyObjects) {
                object.PartialTick(minecraft.getDeltaTracker().getGameTimeDeltaPartialTick(false));
                render2DObjectInSkybox(event, object.GetTextureID(), object.GetName(),object.GetHorizontalAngle(),object.GetVerticalAngle(),object.GetTranslate(),object.GetScale(),object.GetColourModifiers(),object.GetMeshBuffer(), CUSTOM_SKYBOX_PIPELINE);
            }
        }
    }

    private static void addSkyboxFace(BufferBuilder bufferBuilder, float x1, float y1, float z1, float u1, float v1, float x2, float y2, float z2, float u2, float v2, float x3, float y3, float z3, float u3, float v3, float x4, float y4, float z4,
                                      float u4, float v4, int colour) {
        bufferBuilder.addVertex(x1, y1, z1).setUv(u1, v1).setColor(colour);
        bufferBuilder.addVertex(x2, y2, z2).setUv(u2, v2).setColor(colour);
        bufferBuilder.addVertex(x3, y3, z3).setUv(u3, v3).setColor(colour);
        bufferBuilder.addVertex(x4, y4, z4).setUv(u4, v4).setColor(colour);
    }

    public static void render2DObjectInSkybox(RenderLevelStageEvent.AfterSky event, Identifier textureId, String Name, float HorizontalRotationRad, float VerticalRotationRad, Vector3f Translate, Vector3f Scale, Vector4f colourModulator, GpuBuffer meshBuffer, RenderPipeline pipeline) {
        initBuffers();
        Minecraft minecraft = Minecraft.getInstance();
        PoseStack poseStack = event.getPoseStack();
        SkyRenderState state = event.getLevelRenderState().skyRenderState;
        poseStack.pushPose();
        poseStack.mulPose(Axis.YP.rotation(HorizontalRotationRad));
        poseStack.mulPose(Axis.XP.rotation(VerticalRotationRad));
        Matrix4fStack modelViewStack = RenderSystem.getModelViewStack();
        modelViewStack.pushMatrix();
        modelViewStack.mul(poseStack.last().pose());
        modelViewStack.translate(Translate);
        modelViewStack.scale(Scale);
        GpuBufferSlice dynamicTransforms = RenderSystem.getDynamicUniforms().writeTransform(modelViewStack, colourModulator, new Vector3f(), new Matrix4f());
        GpuTextureView colour = minecraft.getMainRenderTarget().getColorTextureView();
        GpuTextureView depth = minecraft.getMainRenderTarget().getDepthTextureView();
        GpuBuffer indexBuffer = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS).getBuffer(6);
        AbstractTexture texture = minecraft.getTextureManager().getTexture(textureId);
        try (RenderPass renderPass = RenderSystem.getDevice().createCommandEncoder().createRenderPass(() -> "Age of the Gods Skybox Object [" + Name + "]", colour, OptionalInt.empty(), depth, OptionalDouble.empty())) {
            renderPass.setPipeline(pipeline);
            RenderSystem.bindDefaultUniforms(renderPass);
            renderPass.setUniform("DynamicTransforms", dynamicTransforms);
            renderPass.bindTexture("Sampler0", texture.getTextureView(), texture.getSampler());
            renderPass.setVertexBuffer(0, meshBuffer);
            renderPass.setIndexBuffer(indexBuffer, RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS).type());
            renderPass.drawIndexed(0, 0, 6, 1);
        }
        modelViewStack.popMatrix();
        poseStack.popPose();
    }

    public static void renderCustomSun(RenderLevelStageEvent.AfterSky event, Identifier textureId) {
        initBuffers();
        SkyRenderState state = event.getLevelRenderState().skyRenderState;
        render2DObjectInSkybox(event, textureId, "Sun",(float)Math.toRadians(-90F),state.sunAngle,new Vector3f(0,100.0f,0),new Vector3f(30.0F,-1.0F,30.0F),new Vector4f(1f,1f,1f,state.rainBrightness), sunBuffer,RenderPipelines.CELESTIAL);
    }

    public static void renderCustomMoon(RenderLevelStageEvent.AfterSky event, Identifier textureId) {
        initBuffers();
        SkyRenderState state = event.getLevelRenderState().skyRenderState;
        Minecraft minecraft = Minecraft.getInstance();
        long time = minecraft.level != null ? minecraft.level.getOverworldClockTime() % 24000 : 0;
        float DayAlpha = DistantSkyObject.DayAlpha;
        float NightAlpha = DistantSkyObject.NightAlpha;
        render2DObjectInSkybox(event, textureId, "Moon",(float)Math.toRadians(-90F),state.moonAngle,new Vector3f(0,100.0f,0),new Vector3f(30.0F,-1.0F,30.0F),new Vector4f((float) (0.75 + (DayAlpha * 0.25f) + (NightAlpha * 0.25f)), (float) (0.35 + (DayAlpha * 0.65f) + (NightAlpha * 0.65f)), (float) (0.25 + (DayAlpha * 0.75f) + (NightAlpha * 0.75f)),1), moonBuffer,CUSTOM_SKYBOX_PIPELINE);//RenderPipelines.CELESTIAL);
    }

    public static float getNightAlpha(long dayTime) {
        long time = dayTime % 24000;

        if (time >= 13000) {
            if (time < 15000) return (float) (time-13000) / 2000.0F;
            if (time > 22000) return 1.0F - ((float)(time - 22000) / 2000.0F);
            return 1.0F;
        }
        return 0.0F;
    }
    public static float getDayAlpha(long dayTime) {
        long time = dayTime % 24000;

        if (time >= 0 && time < 13000) {
            if (time < 2000) return (float) time / 2000.0F;
            if (time > 11000) return 1.0F - ((float)(time - 11000) / 2000.0F);
            return 1.0F;
        }
        return 0.0F;
    }


    public static void renderCustomSkybox(RenderLevelStageEvent.AfterSky event, Identifier daytextureId, Identifier nighttextureid, Identifier dusktextureid) {
        initBuffers();
        Minecraft minecraft = Minecraft.getInstance();
        long time = minecraft.level != null ? minecraft.level.getOverworldClockTime() % 24000 : 0;
        float dayAlpha = getDayAlpha(time);
        float nightAlpha = getNightAlpha(time);
        PoseStack poseStack = event.getPoseStack();
        Matrix4fStack modelViewStack = RenderSystem.getModelViewStack();
        modelViewStack.pushMatrix();
        modelViewStack.mul(poseStack.last().pose());
        GpuTextureView colour = minecraft.getMainRenderTarget().getColorTextureView();
        GpuTextureView depth = minecraft.getMainRenderTarget().getDepthTextureView();
        GpuBuffer indexBuffer = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS).getBuffer(36);
        GpuBufferSlice dynamicTransforms = RenderSystem.getDynamicUniforms().writeTransform(modelViewStack, new Vector4f(1.0F, 1.0F, 1.0F, 1.0F), new Vector3f(), new Matrix4f());
        AbstractTexture texture = minecraft.getTextureManager().getTexture(dusktextureid);
        try (RenderPass renderPass = RenderSystem.getDevice().createCommandEncoder().createRenderPass(() -> "Age of the Gods Dusk Skybox", colour, OptionalInt.empty(), depth, OptionalDouble.empty())) {
            renderPass.setPipeline(RenderPipelines.END_SKY);
            RenderSystem.bindDefaultUniforms(renderPass);
            renderPass.setUniform("DynamicTransforms", dynamicTransforms);
            renderPass.bindTexture("Sampler0", texture.getTextureView(), texture.getSampler());
            renderPass.setVertexBuffer(0, skyboxBuffer);
            renderPass.setIndexBuffer(indexBuffer, RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS).type());
            renderPass.drawIndexed(0, 0, 36, 1);
        }
        dynamicTransforms = RenderSystem.getDynamicUniforms().writeTransform(modelViewStack, new Vector4f(1.0F, 1.0F, 1.0F, dayAlpha), new Vector3f(), new Matrix4f());
        texture = minecraft.getTextureManager().getTexture(daytextureId);
        try (RenderPass renderPass = RenderSystem.getDevice().createCommandEncoder().createRenderPass(() -> "Age of the Gods Day Skybox", colour, OptionalInt.empty(), depth, OptionalDouble.empty())) {
            renderPass.setPipeline(RenderPipelines.END_SKY);
            RenderSystem.bindDefaultUniforms(renderPass);
            renderPass.setUniform("DynamicTransforms", dynamicTransforms);
            renderPass.bindTexture("Sampler0", texture.getTextureView(), texture.getSampler());
            renderPass.setVertexBuffer(0, skyboxBuffer);
            renderPass.setIndexBuffer(indexBuffer, RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS).type());
            renderPass.drawIndexed(0, 0, 36, 1);
        }
        dynamicTransforms = RenderSystem.getDynamicUniforms().writeTransform(modelViewStack, new Vector4f(1.0F, 1.0F, 1.0F, nightAlpha), new Vector3f(), new Matrix4f());
        texture = minecraft.getTextureManager().getTexture(nighttextureid);
        try (RenderPass renderPass = RenderSystem.getDevice().createCommandEncoder().createRenderPass(() -> "Age of the Gods Night Skybox", colour, OptionalInt.empty(), depth, OptionalDouble.empty())) {
            renderPass.setPipeline(RenderPipelines.END_SKY);
            RenderSystem.bindDefaultUniforms(renderPass);
            renderPass.setUniform("DynamicTransforms", dynamicTransforms);
            renderPass.bindTexture("Sampler0", texture.getTextureView(), texture.getSampler());
            renderPass.setVertexBuffer(0, skyboxBuffer);
            renderPass.setIndexBuffer(indexBuffer, RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS).type());
            renderPass.drawIndexed(0, 0, 36, 1);
        }
        modelViewStack.popMatrix();
    }

    public static float MoonScale = 0.75f;
    public static float SunScale = 0.75f;

    private static GpuBuffer buildCelestialBuffer(float CelestialScale, String CelestialObjectName) {
        VertexFormat format = DefaultVertexFormat.POSITION_TEX;

        try (ByteBufferBuilder byteBufferBuilder = ByteBufferBuilder.exactlySized(4 * format.getVertexSize())) {
            BufferBuilder bufferBuilder = new BufferBuilder(byteBufferBuilder, VertexFormat.Mode.QUADS, format);
            bufferBuilder.addVertex( -CelestialScale, 0.0F, -CelestialScale).setUv(0.0F, 0.0F);
            bufferBuilder.addVertex( CelestialScale,  0.0F, -CelestialScale).setUv(1.0F, 0.0F);
            bufferBuilder.addVertex( CelestialScale,  0.0F, CelestialScale).setUv( 1.0F, 1.0F);
            bufferBuilder.addVertex( -CelestialScale, 0.0F, CelestialScale).setUv( 0.0F, 1.0F);
            try (MeshData mesh = bufferBuilder.buildOrThrow()) {
                return RenderSystem.getDevice().createBuffer(() -> "Age of the Gods " + CelestialObjectName +" Pass", 32, mesh.vertexBuffer());
            }
        }
    }


    private static GpuBuffer buildSkyboxBuffer() {
        VertexFormat format = DefaultVertexFormat.POSITION_TEX_COLOR;
        try (ByteBufferBuilder byteBufferBuilder = ByteBufferBuilder.exactlySized(24 * format.getVertexSize())) {
            BufferBuilder bufferBuilder = new BufferBuilder(byteBufferBuilder, VertexFormat.Mode.QUADS, format);
            float distance = 100.0F;
            float size = 100.0F;
            int colour = 0xFFFFFFFF;
            addSkyboxFace(bufferBuilder, -size, distance, -size, 1.0F / 4.0F, 1.0F / 3.0F, size, distance, -size, 2.0F / 4.0F, 1.0F / 3.0F, size, distance, size, 2.0F / 4.0F, 0.0F, -size, distance, size, 1.0F / 4.0F, 0.0F, colour);
            addSkyboxFace(bufferBuilder, -size, -distance, -size, 1.0F / 4.0F, 2.0F / 3.0F, -size, -distance, size, 1.0F / 4.0F, 3.0F / 3.0F, size, -distance, size, 2.0F / 4.0F, 3.0F / 3.0F, size, -distance, -size, 2.0F / 4.0F, 2.0F / 3.0F, colour);
            addSkyboxFace(bufferBuilder, -distance, -size, size, 0.0F, 2.0F / 3.0F, -distance, -size, -size, 1.0F / 4.0F, 2.0F / 3.0F, -distance, size, -size, 1.0F / 4.0F, 1.0F / 3.0F, -distance, size, size, 0.0F, 1.0F / 3.0F, colour);
            addSkyboxFace(bufferBuilder, -size, -size, -distance, 1.0F / 4.0F, 2.0F / 3.0F, size, -size, -distance, 2.0F / 4.0F, 2.0F / 3.0F, size, size, -distance, 2.0F / 4.0F, 1.0F / 3.0F, -size, size, -distance, 1.0F / 4.0F, 1.0F / 3.0F, colour);
            addSkyboxFace(bufferBuilder, distance, -size, -size, 2.0F / 4.0F, 2.0F / 3.0F, distance, -size, size, 3.0F / 4.0F, 2.0F / 3.0F, distance, size, size, 3.0F / 4.0F, 1.0F / 3.0F, distance, size, -size, 2.0F / 4.0F, 1.0F / 3.0F, colour);
            addSkyboxFace(bufferBuilder, size, -size, distance, 3.0F / 4.0F, 2.0F / 3.0F, -size, -size, distance, 4.0F / 4.0F, 2.0F / 3.0F, -size, size, distance, 4.0F / 4.0F, 1.0F / 3.0F, size, size, distance, 3.0F / 4.0F, 1.0F / 3.0F, colour);
            try (MeshData meshData = bufferBuilder.buildOrThrow()) {
                return RenderSystem.getDevice().createBuffer(() -> "Age of the Gods Skybox", 40, meshData.vertexBuffer());
            }
        }
    }

}
