package net.vulkanmod.render.shader;

import com.google.common.collect.Sets;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.mojang.blaze3d.shaders.ShaderType;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Set;
import net.minecraft.class_2960;
import net.vulkanmod.vulkan.shader.Pipeline;
import net.vulkanmod.vulkan.shader.SPIRVUtils;
import org.apache.commons.io.IOUtils;

public abstract class ShaderLoadUtil {
   public static final String RESOURCES_PATH = SPIRVUtils.class.getResource("/assets/vulkanmod").toExternalForm();
   public static final String SHADERS_PATH;
   public static final Set<String> REMAPPED_SHADERS;

   public static String resolveShaderPath(String path) {
      return resolveBuiltinShaderPath(path);
   }

   public static String resolveBuiltinShaderPath(String path) {
      return resolveShaderPath(SHADERS_PATH, path);
   }

   public static String resolveShaderPath(String shaderPath, String path) {
      return "%s%s".formatted(shaderPath, path);
   }

   public static void loadShaders(Pipeline.Builder pipelineBuilder, JsonObject config, String configName, String path) {
      String vertexShader = config.has("vertex") ? config.get("vertex").getAsString() : configName;
      String fragmentShader = config.has("fragment") ? config.get("fragment").getAsString() : configName;
      if (vertexShader == null) {
         vertexShader = configName;
      }

      if (fragmentShader == null) {
         fragmentShader = configName;
      }

      vertexShader = removeNameSpace(vertexShader);
      fragmentShader = removeNameSpace(fragmentShader);
      vertexShader = getFileName(vertexShader);
      fragmentShader = getFileName(fragmentShader);
      loadShader(pipelineBuilder, configName, path, vertexShader, SPIRVUtils.ShaderKind.VERTEX_SHADER);
      loadShader(pipelineBuilder, configName, path, fragmentShader, SPIRVUtils.ShaderKind.FRAGMENT_SHADER);
   }

   public static void loadShader(Pipeline.Builder pipelineBuilder, String configName, String path, SPIRVUtils.ShaderKind type) {
      String[] splitPath = splitPath(path);
      String shaderName = splitPath[1];
      String subPath = splitPath[0];
      loadShader(pipelineBuilder, configName, subPath, shaderName, type);
   }

   public static void loadShader(Pipeline.Builder pipelineBuilder, String configName, String path, String shaderName, SPIRVUtils.ShaderKind type) {
      String source = getShaderSource(path, configName, shaderName, type);
      SPIRVUtils.SPIRV spirv = SPIRVUtils.compileShader(shaderName, source, type);
      switch (type) {
         case VERTEX_SHADER -> pipelineBuilder.setVertShaderSPIRV(spirv);
         case FRAGMENT_SHADER -> pipelineBuilder.setFragShaderSPIRV(spirv);
      }

   }

   public static String getConfigFilePath(String path, String rendertype) {
      String basePath = "%s/shaders/%s".formatted(RESOURCES_PATH, path);
      String configPath = "%s/%s/%s.json".formatted(basePath, rendertype, rendertype);

      Path filePath;
      try {
         filePath = FileSystems.getDefault().getPath(configPath);
         if (!Files.exists(filePath, new LinkOption[0])) {
            configPath = "%s/%s.json".formatted(basePath, rendertype);
            filePath = FileSystems.getDefault().getPath(configPath);
         }

         if (!Files.exists(filePath, new LinkOption[0])) {
            return null;
         }
      } catch (Throwable e) {
         throw new RuntimeException(e);
      }

      return filePath.toString();
   }

   public static JsonObject getJsonConfig(String path, String rendertype) {
      if (rendertype.contains(String.valueOf(':'))) {
         return null;
      } else {
         try {
            InputStream stream = ShaderPackManager.openActiveShaderResource("%s/%s/%s.json".formatted("basic", rendertype, rendertype));
            if (stream == null) {
               stream = ShaderPackManager.openActiveShaderResource("%s/%s.json".formatted("basic", rendertype));
            }

            if (stream == null) {
               String configPath = "%s/%s/%s.json".formatted(path, rendertype, rendertype);
               stream = getInputStream(configPath);
               if (stream == null) {
                  configPath = "%s/%s.json".formatted(path, rendertype);
                  stream = getInputStream(configPath);
               }
            }

            if (stream == null) {
               return null;
            } else {
               JsonElement jsonElement = JsonParser.parseReader(new BufferedReader(new InputStreamReader(stream)));
               stream.close();
               return (JsonObject)jsonElement;
            }
         } catch (Throwable e) {
            throw new RuntimeException(e);
         }
      }
   }

   public static String getShaderSource(class_2960 resourceLocation, ShaderType type) {
      String var10000;
      switch (type) {
         case VERTEX -> var10000 = ".vsh";
         case FRAGMENT -> var10000 = ".fsh";
         default -> throw new MatchException((String)null, (Throwable)null);
      }

      String shaderExtension = var10000;
      String path = resourceLocation.method_12832();
      String[] splitPath = splitPath(path);
      String shaderName = "%s%s".formatted(splitPath[1], shaderExtension);
      String shaderFile = "%s/shaders/%s/%s".formatted(RESOURCES_PATH, path, shaderName);

      try {
         InputStream stream = getInputStream(shaderFile);
         if (stream == null) {
            return null;
         } else {
            String source = IOUtils.toString(new BufferedReader(new InputStreamReader(stream)));
            stream.close();
            return source;
         }
      } catch (Throwable e) {
         throw new RuntimeException(e);
      }
   }

   public static String getShaderSource(String path, ShaderType type) {
      String var10000;
      switch (type) {
         case VERTEX -> var10000 = ".vsh";
         case FRAGMENT -> var10000 = ".fsh";
         default -> throw new MatchException((String)null, (Throwable)null);
      }

      String shaderExtension = var10000;
      String[] splitPath = splitPath(path);
      String shaderName = "%s%s".formatted(splitPath[1], shaderExtension);
      String shaderFile = "%s/shaders/%s/%s".formatted(RESOURCES_PATH, path, shaderName);

      try {
         InputStream stream = getInputStream(shaderFile);
         String source = IOUtils.toString(new BufferedReader(new InputStreamReader(stream)));
         stream.close();
         return source;
      } catch (Throwable e) {
         throw new RuntimeException(e);
      }
   }

   public static String getShaderSource(String path, String configName, String shaderName, SPIRVUtils.ShaderKind type) {
      String var10000;
      switch (type) {
         case VERTEX_SHADER -> var10000 = ".vsh";
         case FRAGMENT_SHADER -> var10000 = ".fsh";
         case COMPUTE_SHADER -> var10000 = ".comp";
         default -> throw new UnsupportedOperationException("shader type %s unsupported");
      }

      String shaderExtension = var10000;
      String basePath = path;
      String shaderPath = "/%s/%s".formatted(configName, configName);
      String shaderFile = "%s%s%s".formatted(path, shaderPath, shaderExtension);

      try {
         InputStream stream = openPackOrBuiltinStream(configName, shaderName, shaderExtension, basePath, shaderPath);
         if (stream == null) {
            shaderPath = "/%s".formatted(shaderName);
            shaderFile = "%s%s%s".formatted(basePath, shaderPath, shaderExtension);
            stream = openPackOrBuiltinStream(configName, shaderName, shaderExtension, basePath, shaderPath);
         }

         if (stream == null) {
            shaderPath = "/%s/%s".formatted(configName, shaderName);
            shaderFile = "%s%s%s".formatted(basePath, shaderPath, shaderExtension);
            stream = openPackOrBuiltinStream(configName, shaderName, shaderExtension, basePath, shaderPath);
         }

         if (stream == null) {
            shaderPath = "/%s/%s".formatted(shaderName, shaderName);
            shaderFile = "%s%s%s".formatted(basePath, shaderPath, shaderExtension);
            stream = openPackOrBuiltinStream(configName, shaderName, shaderExtension, basePath, shaderPath);
         }

         if (stream == null) {
            return null;
         } else {
            String source = IOUtils.toString(new BufferedReader(new InputStreamReader(stream)));
            stream.close();
            return source;
         }
      } catch (Throwable e) {
         throw new RuntimeException(e);
      }
   }

   public static String getFileName(String path) {
      int idx = path.lastIndexOf(47);
      return idx > -1 ? path.substring(idx + 1) : path;
   }

   public static String removeNameSpace(String path) {
      int idx = path.indexOf(58);
      return idx > -1 ? path.substring(idx + 1) : path;
   }

   public static String[] splitPath(String path) {
      int idx = path.lastIndexOf(47);
      return new String[]{path.substring(0, idx), path.substring(idx + 1)};
   }

   public static InputStream getInputStream(String path) {
      try {
         Path path1 = Paths.get(new URI(path));
         return !Files.exists(path1, new LinkOption[0]) ? null : Files.newInputStream(path1);
      } catch (IOException | URISyntaxException e) {
         throw new RuntimeException(e);
      }
   }

   private static InputStream openPackOrBuiltinStream(String configName, String shaderName, String shaderExtension, String basePath, String shaderPath) {
      String relativePath = "basic%s%s".formatted(shaderPath, shaderExtension);
      InputStream stream = ShaderPackManager.openActiveShaderResource(relativePath);
      if (stream != null) {
         return stream;
      } else {
         String shaderFile = "%s%s%s".formatted(basePath, shaderPath, shaderExtension);
         return getInputStream(shaderFile);
      }
   }

   static {
      SHADERS_PATH = "%s/shaders/".formatted(RESOURCES_PATH);
      REMAPPED_SHADERS = Sets.newHashSet(new String[]{"core/screenquad.vsh", "core/rendertype_item_entity_translucent_cull.vsh", "core/rendertype_item_entity_translucent_cull.fsh", "core/entity.vsh", "core/entity.fsh"});
   }
}
