package net.halbear.skyboxmod;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.serialization.Codec;
import net.halbear.skyboxmod.rendering.screen.ConfigScreen;
import net.halbear.skyboxmod.rendering.screen.SelectionScreen;
import net.halbear.skyboxmod.rendering.skybox.DistantSkyObject;
import net.halbear.skyboxmod.rendering.skybox.SkyBoxRendering;
import net.minecraft.client.Minecraft;
import net.minecraft.client.OptionInstance;
import net.minecraft.client.renderer.texture.AbstractTexture;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.util.Util;
import net.neoforged.fml.loading.FMLPaths;
import net.neoforged.neoforge.common.ModConfigSpec;

import javax.naming.spi.DirectoryManager;

import static net.minecraft.client.Options.genericValueLabel;

// An example config class. This is not required, but it's a good idea to have one to keep your config organized.
// Demonstrates how to use Neo's config APIs
public class Config {
    private final OptionInstance<Integer> CloudCount;
    private final OptionInstance<Double> MinCloudSpeed;
    private final OptionInstance<Double> MaxCloudSpeed;
    private final OptionInstance<Integer> MaxVerticalAngle;
    private final OptionInstance<Integer> MinVerticalAngle;
    private final OptionInstance<Double> MaxScale;
    private final OptionInstance<Double> MinScale;
    private final OptionInstance<Boolean> OpenCloudImageButton;
    private final OptionInstance<Boolean> OpenCloudSprites;

    private final OptionInstance<Boolean> RenderCustomSun;
    private final OptionInstance<Boolean> RenderCustomMoon;
    private final OptionInstance<Boolean> RenderCustomSkybox;
    private final OptionInstance<Boolean> RenderCustomClouds;

    private final OptionInstance<Boolean> RenderSunTranslucent;
    private final OptionInstance<Boolean> RenderMoonTranslucent;
    private final OptionInstance<Double> SunScale;
    private final OptionInstance<Double> MoonScale;

    public static String ResourcePath = "N/A";
    public static final String ResourceFolderName = "HalsSkyboxMod_Resources";
    public static final String CloudsFolderName = "Skybox_Clouds";
    public static Path ResourceLocation;

    public OptionInstance<Integer> CloudCount(){return CloudCount;}
    public OptionInstance<Boolean> OpenCloudSprites(){return OpenCloudSprites;}
    public OptionInstance<Integer> MaxVerticalAngle(){return MaxVerticalAngle;}
    public OptionInstance<Integer> MinVerticalAngle(){return MinVerticalAngle;}
    public OptionInstance<Double> MinCloudSpeed(){return MinCloudSpeed;}
    public OptionInstance<Double> MaxCloudSpeed(){return MaxCloudSpeed;}
    public OptionInstance<Double> MaxScale(){return MaxScale;}
    public OptionInstance<Double> MinScale(){return MinScale;}
    public OptionInstance<Double> SunScale(){return SunScale;}
    public OptionInstance<Double> MoonScale(){return MoonScale;}
    public OptionInstance<Boolean> OpenCloudImageButton(){return OpenCloudImageButton;}
    public OptionInstance<Boolean> RenderCustomSun(){return RenderCustomSun;}
    public OptionInstance<Boolean> RenderCustomMoon(){return RenderCustomMoon;}
    public OptionInstance<Boolean> RenderCustomSkybox(){return RenderCustomSkybox;}
    public OptionInstance<Boolean> RenderCustomClouds(){return RenderCustomClouds;}
    public OptionInstance<Boolean> RenderSunTranslucent(){return RenderSunTranslucent;}
    public OptionInstance<Boolean> RenderMoonTranslucent(){return RenderMoonTranslucent;}

    public static Config Instance;

    public Config(){
        CloudCount = new OptionInstance<Integer>("hals_skybox_config.cloud_object_count", OptionInstance.noTooltip(), (caption, value) -> Component.literal(caption.getString() + ": " +  value), (new OptionInstance.IntRange(0, 1024)).xmap((value) -> value, (value) -> value, true), Codec.intRange(0, 1024), CLOUD_COUNT.getAsInt(), (value) -> {
            CLOUD_COUNT.set(value);
            CLOUD_COUNT.save();
            DistantSkyObject.RegenerateClouds();
        });
        MaxVerticalAngle = new OptionInstance<Integer>("hals_skybox_config.max_cloud_vertical_angle", OptionInstance.noTooltip(), (caption, value) -> Component.literal(""+  value), (new OptionInstance.IntRange(-180, 180)).xmap((value) -> value, (value) -> value, true), Codec.intRange(-180, 180), MAX_CLOUD_VERTICALE_ANGLE.getAsInt(), (value) -> {
            MAX_CLOUD_VERTICALE_ANGLE.set(value);
            MAX_CLOUD_VERTICALE_ANGLE.save();
            DistantSkyObject.RegenerateClouds();
        });
        MinVerticalAngle = new OptionInstance<Integer>("hals_skybox_config.min_cloud_vertical_angle", OptionInstance.noTooltip(), (caption, value) -> Component.literal(caption.getString() + ": " +  value), (new OptionInstance.IntRange(-180, 180)).xmap((value) -> value, (value) -> value, true), Codec.intRange(-180, 180), MIN_CLOUD_VERTICAL_ANGLE.getAsInt(), (value) -> {
            MIN_CLOUD_VERTICAL_ANGLE.set(value);
            MIN_CLOUD_VERTICAL_ANGLE.save();
            DistantSkyObject.RegenerateClouds();
        });

        RenderCustomSun = new OptionInstance<Boolean>("hals_skybox_config.render_sun", OptionInstance.noTooltip(), (caption, value) -> Component.literal(""+   value), ( OptionInstance.BOOLEAN_VALUES), RENDER_SUN.getAsBoolean(), (value) -> {
            RENDER_SUN.set(value);
            RENDER_SUN.save();
        });

        RenderCustomMoon = new OptionInstance<Boolean>("hals_skybox_config.render_moon", OptionInstance.noTooltip(), (caption, value) -> Component.literal(""+   value), ( OptionInstance.BOOLEAN_VALUES), RENDER_MOON.getAsBoolean(), (value) -> {
            RENDER_MOON.set(value);
            RENDER_MOON.save();
        });

        RenderCustomSkybox = new OptionInstance<Boolean>("hals_skybox_config.render_skybox", OptionInstance.noTooltip(), (caption, value) -> Component.literal(""+   value), ( OptionInstance.BOOLEAN_VALUES), RENDER_SKYBOX.getAsBoolean(), (value) -> {
            RENDER_SKYBOX.set(value);
            RENDER_SKYBOX.save();
        });
        RenderCustomClouds = new OptionInstance<Boolean>("hals_skybox_config.render_clouds", OptionInstance.noTooltip(), (caption, value) -> Component.literal(""+  value), ( OptionInstance.BOOLEAN_VALUES), RENDER_CLOUDS.getAsBoolean(), (value) -> {
            RENDER_CLOUDS.set(value);
            RENDER_CLOUDS.save();
        });

        RenderSunTranslucent = new OptionInstance<Boolean>("hals_skybox_config.render_sun_translucent", OptionInstance.noTooltip(), (caption, value) -> Component.literal(""+   value), ( OptionInstance.BOOLEAN_VALUES), RENDER_SUN_TRANSLUCENCY.getAsBoolean(), (value) -> {
            RENDER_SUN_TRANSLUCENCY.set(value);
            RENDER_SUN_TRANSLUCENCY.save();
        });

        RenderMoonTranslucent = new OptionInstance<Boolean>("hals_skybox_config.render_moon_translucent", OptionInstance.noTooltip(), (caption, value) -> Component.literal(""+   value), ( OptionInstance.BOOLEAN_VALUES), RENDER_MOON_TRANSLUCENCY.getAsBoolean(), (value) -> {
            RENDER_MOON_TRANSLUCENCY.set(value);
            RENDER_MOON_TRANSLUCENCY.save();
        });



        OpenCloudSprites = new OptionInstance<Boolean>("hals_skybox_config.open_cloud_sprites", OptionInstance.noTooltip(), (caption, value) -> {return Component.literal("");},
                OptionInstance.BOOLEAN_VALUES, false, (value) -> {
            Path ResourcesDir = FMLPaths.GAMEDIR.get().resolve(ResourceFolderName);
            Path Clouds = ResourcesDir.resolve(CloudsFolderName);
            net.minecraft.client.Minecraft.getInstance().setScreen(
                    new SelectionScreen(Minecraft.getInstance().screen,Clouds.toFile())
            );
        });

        MoonScale = new OptionInstance<Double>("hals_skybox_config.moon_scale", OptionInstance.noTooltip(), (caption, value) -> {
            double realFloatValue = 1.0 + (value * (99));
            return Component.literal(caption.getString() + ": " + String.format("%.2f", realFloatValue));
        }, OptionInstance.UnitDouble.INSTANCE, (MOON_SCALE.getAsDouble() - 1.0)/99.0, (value) -> {
            double realFloatValue = 1.0 + (value * (99));
            MOON_SCALE.set((realFloatValue));
            MOON_SCALE.save();
        });

        SunScale = new OptionInstance<Double>("hals_skybox_config.sun_scale", OptionInstance.noTooltip(), (caption, value) -> {
            double realFloatValue = 1.0 + (value * (99));
            return Component.literal(caption.getString() + ": " + String.format("%.2f", realFloatValue));
        }, OptionInstance.UnitDouble.INSTANCE, (SUN_SCALE.getAsDouble() - 1.0)/99.0, (value) -> {
            double realFloatValue = 1.0 + (value * (99));
            SUN_SCALE.set((realFloatValue));
            SUN_SCALE.save();
            DistantSkyObject.RegenerateClouds();
        });


        MinScale = new OptionInstance<Double>("hals_skybox_config.min_cloud_scale", OptionInstance.noTooltip(), (caption, value) -> {
            double realFloatValue = 1.0 + (value * (99));
            return Component.literal(caption.getString() + ": " + String.format("%.2f", realFloatValue));
        }, OptionInstance.UnitDouble.INSTANCE, (MIN_CLOUD_SCALE.getAsDouble() - 1.0)/99.0, (value) -> {
            double realFloatValue = 1.0 + (value * (99));
            MIN_CLOUD_SCALE.set((realFloatValue));
            MIN_CLOUD_SCALE.save();
            DistantSkyObject.RegenerateClouds();
        });

        MaxScale = new OptionInstance<Double>("hals_skybox_config.max_cloud_scale", OptionInstance.noTooltip(), (caption, value) -> {
            double realFloatValue = 1.0 + (value * (99));
            return Component.literal(caption.getString() + ": " + String.format("%.2f", realFloatValue));
        }, OptionInstance.UnitDouble.INSTANCE, (MAX_CLOUD_SCALE.getAsDouble() - 1.0)/99.0, (value) -> {
            double realFloatValue = 1.0 + (value * (99));
            MAX_CLOUD_SCALE.set((realFloatValue));
            MAX_CLOUD_SCALE.save();
            DistantSkyObject.RegenerateClouds();
        });


        MinCloudSpeed = new OptionInstance<Double>("hals_skybox_config.min_cloud_speed", OptionInstance.noTooltip(), (caption, value) -> {
            double realFloatValue = -1.0 + (value * (2));
            return Component.literal(caption.getString() + ": " + String.format("%.2f", realFloatValue));
        }, OptionInstance.UnitDouble.INSTANCE, (MIN_CLOUD_SPEED.getAsDouble() + 1.0)/2.0, (value) -> {
            double realFloatValue = -1.0 + (value * (2));
            MIN_CLOUD_SPEED.set((realFloatValue));
            MIN_CLOUD_SPEED.save();
            DistantSkyObject.RegenerateClouds();
        });

        MaxCloudSpeed = new OptionInstance<Double>("hals_skybox_config.max_cloud_speed", OptionInstance.noTooltip(), (caption, value) -> {
            double realFloatValue = -1.0 + (value * (2));
            return Component.literal(caption.getString() + ": " + String.format("%.2f", realFloatValue));
        }, OptionInstance.UnitDouble.INSTANCE, (MAX_CLOUD_SPEED.getAsDouble() + 1.0)/2.0, (value) -> {
            double realFloatValue = -1.0 + (value * (2));
            MAX_CLOUD_SPEED.set((realFloatValue));
            MAX_CLOUD_SPEED.save();
            DistantSkyObject.RegenerateClouds();
        });

        Instance = this;
        OpenCloudImageButton = new OptionInstance<Boolean>("hals_skybox_config.open_cloud_images_location", OptionInstance.noTooltip(), (caption, value) -> {return Component.literal("");},
                OptionInstance.BOOLEAN_VALUES, false, (value) -> {
            Path ResourcesDir = FMLPaths.GAMEDIR.get().resolve(ResourceFolderName);
            Path Clouds = ResourcesDir.resolve(CloudsFolderName);
            if(!Files.isDirectory(Clouds)) {
                try {
                    Files.createDirectories(Clouds);
                } catch (Exception e) {
                    SkyboxMod.LOGGER.error("Failed to create Clouds directory", e);
                    return;
                }
            }
            Util.getPlatform().openFile(Clouds.toFile());
        });
        if(RESOURCE_LOCATION.get() == ResourceLocations.GAME_DIR){
            Path ResourcesDir = FMLPaths.GAMEDIR.get().resolve(ResourceFolderName);
            if(!Files.isDirectory(ResourcesDir)){
                try {
                    Files.createDirectories(ResourcesDir);
                    SkyboxMod.LOGGER.info("Successfully created folder: {}", ResourcesDir);
                } catch (IOException e) {
                    SkyboxMod.LOGGER.error("Failed to create ResourcesDir directory", e);
                    RESOURCE_LOCATION.set(ResourceLocations.IDENTIFIERS);
                    return;
                }
            }
            ResourceLocation = ResourcesDir;
            Path Clouds = ResourcesDir.resolve(CloudsFolderName);
            if(!Files.isDirectory(Clouds)){
                try {
                    Files.createDirectories(Clouds);
                    SkyboxMod.LOGGER.info("Successfully created folder: {}", ResourcesDir);
                    Identifier[] clouds = new Identifier[EmbeddedCloudTextures.length];
                    for(int i = 0; i < EmbeddedCloudTextures.length; i++){
                        clouds[i] = Identifier.parse(EmbeddedCloudTextures[i]);
                        Optional<Resource> resource = Minecraft.getInstance().getResourceManager().getResource(clouds[i]);
                        if (resource.isPresent()) {
                            Path targetFile = Clouds.resolve(clouds[i].getPath().replace("/", "_"));
                            try (InputStream stream = resource.get().open()) {
                                //if(Files.notExists(targetFile))Files.createFile(targetFile);
                                Files.copy(stream, targetFile, StandardCopyOption.REPLACE_EXISTING);
                                System.out.println("Successfully saved asset to: " + targetFile.toAbsolutePath());
                            } catch (IOException e) {
                                SkyboxMod.LOGGER.error("Failed to something or other: ",e);
                                RESOURCE_LOCATION.set(ResourceLocations.IDENTIFIERS);
                                return;
                            }
                        }
                    }
                } catch (IOException e) {
                    SkyboxMod.LOGGER.error("Failed to create Clouds directory", e);
                    RESOURCE_LOCATION.set(ResourceLocations.IDENTIFIERS);
                    return;
                }
            }
        }
    }

    public List<Identifier> GetAndLoadAllCloudFiles() {
        Path ResourcesDir = FMLPaths.GAMEDIR.get().resolve(ResourceFolderName);
        Path Clouds = ResourcesDir.resolve(CloudsFolderName);
        List<Identifier> CloudFiles = new ArrayList<>();
        try (Stream<Path> stream = Files.walk(Clouds)) {
            List<Path> files = stream
                    .filter(Files::isRegularFile)
                    .toList();
            files.forEach(cloudfile -> {
                SkyboxMod.LOGGER.debug("FILE: " + cloudfile.toString());
                File texture = cloudfile.toFile();
                Identifier loadedTexture = registerExternalTexture(texture);
                CloudFiles.add(loadedTexture);
            });

        } catch (IOException e) {
            SkyboxMod.LOGGER.error("failed to open cloud path: ", e);
        }
        if (RESOURCE_LOCATION.get() == ResourceLocations.IDENTIFIERS){
            List<? extends String> paths = CLOUD_PATHS.get();
            paths.forEach(path->{
                if(!Identifier.isValidPath(path)) CloudFiles.add(Identifier.fromNamespaceAndPath(SkyboxMod.MODID, "textures/correct_regex.png"));
                else CloudFiles.add(Identifier.parse(path));
            });
        }
        return CloudFiles;
    }

    public List<Identifier> GetAllClouds(){
        List<? extends String> paths = CLOUD_PATHS.get();
        List<Identifier> loadedAssets = new ArrayList<>();
        if(RESOURCE_LOCATION.get() == ResourceLocations.GAME_DIR){
            Path ResourcesDir = FMLPaths.GAMEDIR.get().resolve(ResourceFolderName);
            Path Clouds = ResourcesDir.resolve(CloudsFolderName);
            paths.forEach((path)->{
                File texture = Clouds.resolve(path.replace('/','_')).toFile();
                Identifier loadedTexture = registerExternalTexture(texture);
                loadedAssets.add(loadedTexture);
            });
        } else{
            paths.forEach(path->{
                if(!Identifier.isValidPath(path)) loadedAssets.add(Identifier.fromNamespaceAndPath(SkyboxMod.MODID, "textures/correct_regex.png"));
                else loadedAssets.add(Identifier.parse(path));
            });
        }
        return loadedAssets;
    }

    public static int loadedTexture = 0;
    public static Map<File, Identifier> LoadedTextureMap = new LinkedHashMap<>();

    public static Identifier registerExternalTexture(File imageFile) {
        if(LoadedTextureMap.containsKey( imageFile)) return LoadedTextureMap.get(imageFile);
        String path = "external_textures/"+loadedTexture+"loaded_texture_" + imageFile.getPath().split(CloudsFolderName)[1].substring(1).toLowerCase().replace(' ','-');
        if(!Identifier.isValidPath(path)) {
            return Identifier.fromNamespaceAndPath(SkyboxMod.MODID, "textures/correct_regex.png");
        }
        Identifier location = Identifier.fromNamespaceAndPath(SkyboxMod.MODID,path );
        try (InputStream stream = new FileInputStream(imageFile)) {
            NativeImage nativeImage = NativeImage.read(stream);

            DynamicTexture dynamicTexture = new DynamicTexture(()->"loaded_texture_" + loadedTexture,nativeImage);
            loadedTexture++;
            Minecraft.getInstance().getTextureManager().register(location, dynamicTexture);
            LoadedTextureMap.put(imageFile, location);
            return location;

        } catch (IOException e) {
            SkyboxMod.LOGGER.error("Failed to load texture: ",e);
            return Identifier.fromNamespaceAndPath(SkyboxMod.MODID, "textures/no_texture.png");
        }
    }


    private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder();

    public static final ModConfigSpec.IntValue CLOUD_COUNT = BUILDER
            .comment("how many clouds to render in the sky")
            .defineInRange("cloud_count", 128, 0, 1024);

    public static final ModConfigSpec.DoubleValue MIN_CLOUD_SPEED = BUILDER
            .comment("the slowest cloud")
            .defineInRange("min_cloud_speed", 0.05, -1f, 1f);

    public static final ModConfigSpec.DoubleValue MAX_CLOUD_SPEED = BUILDER
            .comment("the fastest cloud")
            .defineInRange("max_cloud_speed", 0.15, -1f, 1f);

    public static final ModConfigSpec.DoubleValue MIN_CLOUD_SCALE = BUILDER
            .comment("how small (in blocks) the clouds can render")
            .defineInRange("min_cloud_scale", 2, 1f, 100f);

    public static final ModConfigSpec.DoubleValue MAX_CLOUD_SCALE = BUILDER
            .comment("how large (in blocks) the clouds can render")
            .defineInRange("max_cloud_scale", 14, 1f, 100f);

    public static final ModConfigSpec.DoubleValue SUN_SCALE = BUILDER
            .comment("how large (in blocks) the sun can render")
            .defineInRange("sun_scale", 22.5, 1f, 100f);

    public static final ModConfigSpec.DoubleValue MOON_SCALE = BUILDER
            .comment("how large (in blocks) the moon can render")
            .defineInRange("moon_scale", 22.5, 1f, 100f);

    public static final ModConfigSpec.IntValue MIN_CLOUD_VERTICAL_ANGLE = BUILDER
            .comment("how Low in the sky it can render")
            .defineInRange("min_cloud_vert_angle", -130, -180, 180);

    public static final ModConfigSpec.IntValue MAX_CLOUD_VERTICALE_ANGLE = BUILDER
            .comment("how high in the sky it can render")
            .defineInRange("max_cloud_vert_angle", -70, -180, 180);

    public static final ModConfigSpec.BooleanValue RENDER_SUN = BUILDER
            .comment("render custom sun texture and pipeline")
            .define("render_custom_sun", true);

    public static final ModConfigSpec.BooleanValue RENDER_SUN_TRANSLUCENCY = BUILDER
            .comment("render custom sun as a translucent object, this makes it have absolute colour with working transparency")
            .define("render_custom_sun_pipeline", false);

    public static final ModConfigSpec.BooleanValue RENDER_MOON = BUILDER
            .comment("render custom moon texture and pipeline")
            .define("render_custom_moon", true);

    public static final ModConfigSpec.BooleanValue RENDER_MOON_TRANSLUCENCY = BUILDER
            .comment("render custom moon as a translucent object, this makes it have absolute colour with working transparency")
            .define("render_custom_moon_pipeline", true);

    public static final ModConfigSpec.BooleanValue RENDER_SKYBOX = BUILDER
            .comment("render custom Skybox")
            .define("render_custom_skybox", true);

    public static final ModConfigSpec.BooleanValue RENDER_CLOUDS = BUILDER
            .comment("render custom cloud textures and pipeline")
            .define("render_custom_clouds", true);

    public enum ResourceLocations{
        GAME_DIR,
        IDENTIFIERS,
    }

    public static final ModConfigSpec.EnumValue<ResourceLocations> RESOURCE_LOCATION = BUILDER
            .comment("where the asset folders are located")
            .defineEnum("resource_location", ResourceLocations.GAME_DIR);

    public static final ModConfigSpec.ConfigValue<List<? extends String>> CLOUD_PATHS = BUILDER
            .comment("A list of items to log on common setup.")
            .defineListAllowEmpty("cloud_paths", List.of("textures/cloud2.png","textures/cloud3.png","textures/smallcloud1.png","textures/smallcloud2.png","textures/smallcloud3.png"), () -> "", Config::ValidateCloudPath);

    private static final String[] EmbeddedCloudTextures = new String[]{"hals_skybox_mod:textures/cloud2.png","hals_skybox_mod:textures/cloud3.png","hals_skybox_mod:textures/smallcloud1.png","hals_skybox_mod:textures/smallcloud2.png","hals_skybox_mod:textures/smallcloud3.png"};

    static final ModConfigSpec SPEC = BUILDER.build();

    private static boolean ValidateCloudPath(final Object obj) {
        return obj instanceof String itemName;
    }

}
