package net.halbear.skyboxmod.rendering.skybox;

import com.mojang.blaze3d.buffers.GpuBuffer;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import net.halbear.skyboxmod.Config;
import net.halbear.skyboxmod.SkyboxMod;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.AbstractTexture;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import org.joml.Vector3f;
import org.joml.Vector4f;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static net.halbear.skyboxmod.rendering.skybox.SkyBoxRendering.*;

public class DistantSkyObject {
    public static Map<Double, GpuBuffer> SkyObjectMeshMap = new HashMap<>();
    public static Map<String, DistantSkyObject> SkyObjectRegistry = new HashMap<>();
    public static Map<ResourceKey, List<String>> ObjectsMappedToDimension = new HashMap<>();
    public static float DayAlpha = 1.0f;
    public static float NightAlpha = 0.0f;

    public static void RegenerateClouds(){
        SkyObjectRegistry.clear();
        ObjectsMappedToDimension.clear();
        TextureManager textureManager = Minecraft.getInstance().getTextureManager();
        Identifier[] clouds = Config.Instance.GetAllClouds().toArray(new Identifier[0]);
        double[] aspectRatios = new double[clouds.length];
        for(int i = 0; i < clouds.length; i++){
            SkyboxMod.LOGGER.debug(clouds[i].getPath());
            AbstractTexture texture = textureManager.getTexture(clouds[i]);
            int width = texture.getTexture().getWidth(0);
            int height = texture.getTexture().getHeight(0);
            if(width != 0 && height != 0) {
                aspectRatios[i] = (double)width/(double)height;
            }
        }

        for(int i = 0; i < Config.CLOUD_COUNT.getAsInt(); i++){

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

            String name = "Cloud"+ i;
            double MinSpeed = Config.MIN_CLOUD_SPEED.getAsDouble();
            double MaxSpeed = Config.MAX_CLOUD_SPEED.getAsDouble();
            float Speed = (float)(MinSpeed +  (MaxSpeed - MinSpeed) *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);
                }
            };

            double MinCloudScale = Config.MIN_CLOUD_SCALE.getAsDouble();
            double MaxCloudScale = Config.MAX_CLOUD_SCALE.getAsDouble();
            float Scale = (float)(MinCloudScale + (Math.random() * (MaxCloudScale - MinCloudScale)));

            double MinCloudAngle = (double)Config.MIN_CLOUD_VERTICAL_ANGLE.getAsInt();
            double MaxCloudAngle = (double)Config.MAX_CLOUD_VERTICALE_ANGLE.getAsInt();

            new DistantSkyObject(name, clouds[Index], aspectRatios[Index],
                    (float)Math.toRadians(360*Math.random()),(float)Math.toRadians(MinCloudAngle + ((MaxCloudAngle - MinCloudAngle) * Math.random())),
                    new Vector3f(0,100,0),new Vector3f(-Scale,-1,Scale),new Vector4f(1,1,1,1))
                    .AddToDimension(OVERWORLD)
                    .OverrideTickFunction(TickUpdateAction)
                    .Cloud();
        }
    }

    public static void TickUpdate(ResourceKey Dimension){
        Minecraft minecraft = Minecraft.getInstance();
        long time = minecraft.level != null ? minecraft.level.getOverworldClockTime() % 24000 : 0;
        DayAlpha = getDayAlpha(time);
        NightAlpha = getNightAlpha(time);
        List<DistantSkyObject> objects = GetAllObjectsInDimenstion(Dimension);
        for(int i = 0; i < objects.size(); i++){
            objects.get(i).TickUpdate();
        }
    }

    public static List<DistantSkyObject> GetAllObjectsInDimenstion(ResourceKey Dimension){
        List<String> Keys = ObjectsMappedToDimension.get(Dimension);
        if(Keys == null || Keys.isEmpty()) return new ArrayList<DistantSkyObject>();
        List<DistantSkyObject> objectsInDimentsion = new ArrayList<>();
        for(int i = 0; i < Keys.size(); i++){
            if(SkyObjectRegistry.containsKey(Keys.get(i)))objectsInDimentsion.add(SkyObjectRegistry.get(Keys.get(i)));
        }
        return objectsInDimentsion;
    }

    @FunctionalInterface
    public interface Action {
        void execute();
    }

    private static GpuBuffer CreateMeshBuffer(double AspectRatio, float MeshScale, String ObjectName) {
        VertexFormat format = DefaultVertexFormat.POSITION_TEX;
        float Multipler = (float)AspectRatio;
        try (ByteBufferBuilder byteBufferBuilder = ByteBufferBuilder.exactlySized(4 * format.getVertexSize())) {
            BufferBuilder bufferBuilder = new BufferBuilder(byteBufferBuilder, VertexFormat.Mode.QUADS, format);
            bufferBuilder.addVertex( MeshScale * Multipler, 0.0F, MeshScale).setUv(0.0F, 0.0F);
            bufferBuilder.addVertex( -MeshScale * Multipler,  0.0F, MeshScale).setUv(1.0F, 0.0F);
            bufferBuilder.addVertex( -MeshScale * Multipler,  0.0F, -MeshScale).setUv( 1.0F, 1.0F);
            bufferBuilder.addVertex( MeshScale * Multipler, 0.0F, -MeshScale).setUv( 0.0F, 1.0F);
            try (MeshData mesh = bufferBuilder.buildOrThrow()) {
                return RenderSystem.getDevice().createBuffer(() -> "Age of the Gods " + ObjectName +" Pass", 32, mesh.vertexBuffer());
            }
        }
    }

    public enum ObjectType{
        Cloud,
        SkyObject
    }

    private Identifier textureID;
    private double aspectRatio;
    private float HAngle;
    private float VAngle;
    private final String Name;
    private float NextHAngleAddition = 0;
    private float NextVAngleAddition = 0;
    private float PartialHAngle = HAngle;
    private float PartialVAngle = VAngle;
    private final Vector3f Translate;
    private final Vector3f Scale;
    private final Vector4f ColourModifiers;
    private ObjectType type;
    private Action ExecuteOnTick = new Action() {
        @Override
        public void execute() {

        }
    };

    public DistantSkyObject Cloud(){
        this.type = ObjectType.Cloud;
        return this;
    }
    public DistantSkyObject SkyObject(){
        this.type = ObjectType.SkyObject;
        return this;
    }

    public DistantSkyObject AddToDimension(ResourceKey Dimension){
        if(!ObjectsMappedToDimension.containsKey(Dimension)){
            ObjectsMappedToDimension.put(Dimension, new ArrayList<String>());
        }
        ObjectsMappedToDimension.get(Dimension).add(Name);
        return this;
    }
    public DistantSkyObject RemoveFromDimension(ResourceKey Dimension){
        if(ObjectsMappedToDimension.containsKey(Dimension)){
            ObjectsMappedToDimension.get(Dimension).remove(Name);
        }
        return this;
    }

    public DistantSkyObject TickUpdate(){
        HAngle += NextHAngleAddition;
        NextHAngleAddition = 0;
        VAngle += NextVAngleAddition;
        NextVAngleAddition = 0;
        ExecuteOnTick.execute();
        return this;
    }

    public DistantSkyObject PartialTick(double PartialTick){
        PartialHAngle = HAngle + (NextHAngleAddition * Math.clamp((float) PartialTick, 0, 1));
        PartialVAngle = VAngle + (NextVAngleAddition * Math.clamp((float) PartialTick, 0, 1));
        return  this;
    }

    public DistantSkyObject OverrideTickFunction(Action newAction){
        ExecuteOnTick = newAction;
        return this;
    }

    public DistantSkyObject GenerateBufferIfNonExistant(){
        if(!SkyObjectMeshMap.containsKey(aspectRatio)){
            GpuBuffer newMeshBuffer = CreateMeshBuffer(aspectRatio, 1f, Name);
            SkyObjectMeshMap.put(aspectRatio, newMeshBuffer);
        }
        return this;
    }

    public GpuBuffer GetMeshBuffer(){
        GenerateBufferIfNonExistant();
        return SkyObjectMeshMap.get(aspectRatio);
    }

    public DistantSkyObject(String Name, Identifier TextureID, double AspectRatio, float HorizontalAngleRad, float VerticalAngleRad, Vector3f Translate, Vector3f Scale, Vector4f ColourModifiers){
        this.Name = Name;
        if(!SkyObjectRegistry.containsKey(Name)) SkyObjectRegistry.put(Name, this);
        this.Translate = Translate;
        this.Scale = Scale;
        this.ColourModifiers = ColourModifiers;
        this.textureID = TextureID;
        this.aspectRatio = AspectRatio;
        this.HAngle = HorizontalAngleRad;
        this.VAngle = VerticalAngleRad;
        PartialHAngle = HAngle;
        PartialVAngle = VAngle;
    }
    public void AddVerticalDegrees(float degrees){
        float Angle = GetVerticalAngleDegrees();
        Angle = (Angle + degrees) % 360;
        VAngle = (float)Math.toRadians(Angle);
    }
    public void AddHorizontalDegrees(float degrees){
        float Angle = GetHorizontalAngleDegrees();
        Angle = (Angle + degrees) % 360;
        HAngle = (float)Math.toRadians(Angle);
    }
    public Vector4f GetColourModifiers(){return  ColourModifiers;}
    public Vector3f GetScale(){return Scale;}
    public Vector3f GetTranslate(){return Translate;}
    public String GetName(){return Name;}
    public double GetAspectRatio(){return aspectRatio;}
    public Identifier GetTextureID(){return textureID;}
    public float GetHorizontalAngleDegrees(){return (float)Math.toDegrees(HAngle);}
    public float GetHorizontalAngle(){return PartialHAngle;}
    public float GetRawHorizontalAngle(){return HAngle;}
    public float GetVerticalAngleDegrees(){return (float)Math.toDegrees(VAngle);}
    public float GetVerticalAngle(){return PartialVAngle;}
    public float GetRawVerticalAngle(){return VAngle;}
}
