using System.Diagnostics;

namespace Nerfed.Builder;

public class Builder : IDisposable
{
    private readonly Dictionary<string, IImporter> importers = new Dictionary<string, IImporter>();
    private readonly RawFileImporter rawFileImporter;

    public Builder()
    {
        rawFileImporter = new RawFileImporter();

        ShaderImporter shaderImporter = new ShaderImporter();
        importers.Add(".vert", shaderImporter); // Vertex shader
        importers.Add(".frag", shaderImporter); // Fragment shader
        importers.Add(".tesc", shaderImporter); // Tessellation control shader
        importers.Add(".tese", shaderImporter); // Tessellation evaluation shader
        importers.Add(".geom", shaderImporter); // Geometry shader
        importers.Add(".comp", shaderImporter); // Compute shader
    }

    public void Run(BuildArgs args)
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();

        CopyLibs(args.ProjectPath);

        List<string> contentFiles = args.ContentFiles;
        string absContentPath = $"{args.ProjectPath}/{PathUtil.ContentFolderName}";

        // If no files are provided, build all content.
        if (args.ContentFiles == null)
        {
            contentFiles = [];
            CollectAssetFiles(absContentPath, absContentPath, ref contentFiles);
        }

        if (contentFiles.Count > 0)
        {
            string importPath = $"{args.ProjectPath}/{PathUtil.ImportFolderName}";

            ParallelOptions parallelOptions = new ParallelOptions
            {
                MaxDegreeOfParallelism = contentFiles.Count
            };

            Parallel.ForEach(contentFiles, parallelOptions, relativeFile =>
            {
                try
                {
                    string inFile = $"{args.ProjectPath}/{PathUtil.ContentFolderName}/{relativeFile}";

                    if (!File.Exists(inFile))
                    {
                        Console.Error.WriteLine($"Asset file '{relativeFile}' not found");
                        return;
                    }

                    string outFile = $"{importPath}/{relativeFile}{PathUtil.ImportedFileExtension}";

                    FileInfo inFileInfo = new FileInfo(inFile);
                    FileInfo outFileInfo = new FileInfo(outFile);

                    if (!FileUtil.IsNewer(inFileInfo, outFileInfo))
                    {
                        // File has not changed since last build, no need to build this one.
                        return;
                    }

                    string outDir = Path.GetDirectoryName(outFile);
                    if (!Directory.Exists(outDir))
                    {
                        Directory.CreateDirectory(outDir);
                    }

                    string ext = Path.GetExtension(inFile).ToLower();
                    if (importers.TryGetValue(ext, out IImporter importer))
                    {
                        importer.Import(inFile, outFile);
                    }
                    else
                    {
                        rawFileImporter.Import(inFile, outFile);
                    }

                    Console.WriteLine(relativeFile);
                }
                catch (Exception e)
                {
                    Console.Error.WriteLine($"Import error on asset '{relativeFile}': {e.Message}");
                }
            });
        }

        Console.WriteLine($"Build content completed in {stopwatch.Elapsed.TotalSeconds:F2} seconds");
    }

    private void CopyLibs(string projectPath)
    {
        string libDir = $"{AppDomain.CurrentDomain.BaseDirectory}/../../Native/";
        if (OperatingSystem.IsWindows())
        {
            libDir += "x64";
        }
        else if (OperatingSystem.IsLinux())
        {
            libDir += "lib64";
        }
        else if (OperatingSystem.IsMacOS())
        {
            libDir += "osx";
        }

        libDir = Path.GetFullPath(libDir);
        foreach (string libFile in Directory.EnumerateFiles(libDir))
        {
            FileInfo srcFileInfo = new FileInfo(libFile);
            FileInfo dstFileInfo = new FileInfo($"{projectPath}/{PathUtil.BuildFolderName}/{Path.GetFileName(libFile)}");
            if (FileUtil.IsNewer(srcFileInfo, dstFileInfo))
            {
                FileUtil.Copy(srcFileInfo, dstFileInfo);
            }
        }
    }

    private void CollectAssetFiles(string assetDir, string dir, ref List<string> files)
    {
        foreach (string file in Directory.EnumerateFiles(dir))
        {
            if (Path.GetExtension(file).Equals(PathUtil.ImportFileExtension, StringComparison.CurrentCultureIgnoreCase))
            {
                continue;
            }

            string relativeFile = file.Substring(assetDir.Length, file.Length - assetDir.Length);
            if (relativeFile[0] == Path.DirectorySeparatorChar || relativeFile[0] == Path.AltDirectorySeparatorChar)
            {
                relativeFile = relativeFile.Substring(1, relativeFile.Length - 1);
            }

            files.Add(relativeFile);
        }

        foreach (string subDir in Directory.EnumerateDirectories(dir))
        {
            CollectAssetFiles(assetDir, subDir, ref files);
        }
    }

    public void Dispose() { }
}
