/^ The CrossLang BuildTool (use the function BuildProject) ^/
class Tesses.CrossLang.BuildTool
{
    /^ Construct the build tool with Package Manager ^/
    public BuildTool(pm)
    {
        this.PackageManager = pm;
    }

    private copyFile(src,dest)
    {
        var srcStrm = FS.Local.OpenFile(src,"rb");
        var destStrm = FS.Local.OpenFile(dest, "wb");
        srcStrm.CopyTo(destStrm);
        srcStrm.Close();
        destStrm.Close();
    }
    /^ Whether debug is enabled ^/
    public Debug = false;
    /^ Package Manager Object ^/
    public PackageManager;
    /^ Directories compiled ^/
    public DirectoriesCompiled = [];
    /^ Get the dependencies (don't use this directly unless you know what you are doing) ^/
    public GetPackageDependencies(name, version, dir)
    {
        var dep = this.PackageManager.GetPackage(name,version);
            if(TypeOf(dep) == "Null") throw $"Package {name} with version {version} does not exist";


            var pkgPath = dir / $"{name}-{version}.crvm";

            var strm = FS.Local.OpenFile(pkgPath,"wb");

            strm.WriteBlock(dep,0,dep.Count);
            strm.Close();
            var strm = FS.Local.OpenFile(pkgPath,"rb");


            var package = VM.LoadExecutable(strm);
            strm.Close();

            var deps = [];

            each(var dep : package.Dependencies)
            {
                deps.Add(this.GetPackageDependencies(dep.Name, dep.Version, dir));
            }
            each(var pkg : package.Tools)
            {
                dep.Add(this.GetPackageDependencies(dep.Name, dep.Version, dir));
            }

            return {
                Name = name,
                Version = version,
                Info = Json.Decode(package.Info),
                Dependencies = deps,
                Output = pkgPath
            };
    }
    /^ Build the project in the directory projectDirectory ^/
    public BuildProject(projectDirectory)
    {
        var dir = FS.MakeFull(projectDirectory);
            var dirStr = dir.ToString();
      



            each(var item : this.DirectoriesCompiled)
            {
                if(item.Path == dirStr) return item.Data;
            }

            var crossConf = dir / "cross.json";
            if(FS.Local.FileExists(crossConf))
            {
                var configData = Json.Decode(FS.ReadAllText(FS.Local,crossConf));
                var info = {type = "console"};
                var name = "out";
                var version = "1.0.0.0-prod";
                var outputDir = "bin";
                var objDir = "obj";
                var srcDir = "src";
                var resDir = "res";
                var compTime = "none";
                var icon = "";
                if(TypeOf(configData.compTime) != "Undefined")
                    compTime = configData.compTime;

                if(compTime == "full" && !this.AllowFullCompTime)
                    throw {
                        Type="CompTimeException",
                        Message="Failed to build due to having insecure comptime, use --allow-insecure-comptime if using the shell when building if you trust the program.",
                        ToString=(this)=>$"{this.Type} on line: {this.Line} {this.Message}"
                    };

                if(TypeOf(configData.name) != "Undefined")
                    name = configData.name;

                if(TypeOf(configData.version) != "Undefined")
                    version = configData.version;
                if(TypeOf(configData.bin_directory) != "Undefined")
                    outputDir = configData.bin_directory;

                if(TypeOf(configData.obj_directory) != "Undefined")
                    objDir = configData.obj_directory;
                if(TypeOf(configData.source_directory) != "Undefined")
                    srcDir = configData.source_directory;
                if(TypeOf(configData.resource_directory) != "Undefined")
                    resDir = configData.resource_directory;
                if(TypeOf(configData.info) != "Undefined")
                    info = configData.info;
                if(TypeOf(configData.icon) != "Undefined")
                    icon = configData.icon;




                FS.Local.CreateDirectory(dir / outputDir);

                if(TypeOf(info.type) == "String" && (info.type == "template" || info.type == "archive"))
                {

                    //vfs, strm, name, version, info
                    var subdir = new SubdirFilesystem(FS.Local,dir);
                    var output = $"{name}-{version}.crvm";
                    var outFile = FS.Local.OpenFile(dir / outputDir / output,"wb");

                    if(TypeOf(info.template_ignored_files) != "Undefined")
                    {
                        var ignored = "";
                        each(var item : info.template_ignored_files)
                        {
                            ignored += $"{item}\n";
                        }
                        FS.WriteAllText(FS.Local, dir / ".crossarchiveignore", ignored);
                    }
                    FS.CreateArchive(subdir, outFile, name, version, Json.Encode(info),icon == "" ? "" : (/resDir/icon).ToString());
                    outFile.Close();
                    subdir.Close();
                    return {
                        Name = name,
                        Version = version,
                        Info = info,
                        Dependencies = [],
                        Output = dir / outputDir / output
                    };
                }

                FS.Local.CreateDirectory(dir / objDir / "packages");
                FS.Local.CreateDirectory(dir / objDir / "res");

                FS.Local.CreateDirectory(dir/resDir);

                const svfs = new Tesses.CrossLang.SharedViewFilesystem();
                svfs.AddFS(new SubdirFilesystem(FS.Local, dir / resDir));
                svfs.AddFS(new SubdirFilesystem(FS.Local, dir / objDir / "res"));

                var dependencies = [];
                if(TypeOf(configData.project_dependencies) == "List")
                {
                    each(var dep : configData.project_dependencies)
                    {
                        if(Path.FromString(dep).IsRelative())
                        {
                            dependencies.Add(this.BuildProject((dir / dep)));
                        }
                        else
                        dependencies.Add(this.BuildProject(dep));
                    }
                }
                var sources = [];
                if(TypeOf(configData.dependencies) == "List")
                {
                    each(var dep : configData.dependencies)
                    {
                        dependencies.Add(this.GetPackageDependencies(dep.name,dep.version,dir / objDir / "packages"));
                    }
                }


                each(var item : this.DirectoriesCompiled)
                {
                    if(item.Path == dirStr) return item.Data;
                }


                func walk_for_compiling(item,dir2)
                {
                    if(item.Info.type == "compile_tool")
                    {
                        var newDir = dir / objDir / $"{item.Name}-{item.Version}";
                        FS.Local.CreateDirectory(newDir);
                        var newFile = newDir / $"{item.Name}-{item.Version}.crvm";

                        this.copyFile(item.Output, newFile);
                        each(var item2 : item.Dependencies)
                        {
                            walk_for_compiling(item2, newDir);
                        }
                        //we need to load this
                        var env = VM.CreateEnvironment({});
                        env.RegisterEverything();
                        env.LockRegister();

                        env.LoadFileWithDependencies(FS.Local,newFile);
                        env.GetDictionary().RunTool({
                                ProjectDirectory = dir,
                                Project = new SubdirFilesystem(FS.Local, dir),
                                ProjectJson = configData,
                                ProjectInfo = info,
                                GeneratedSource = sources,
                                Config = this.Config
                            });
                    }
                    else
                    {
                        this.copyFile(item.Output, dir2 / $"{item.Name}-{item.Version}.crvm");
                        each(var item2 : item.Dependencies)
                        {
                            walk_for_compiling(item2, dir2);
                        }
                    }
                }

                var file_deps = [];
                var file_tools = [];

                if(TypeIsList(configData.prebuild))
                {
                    //do the prebuild things

                    each(var job : configData.prebuild)
                    {
                        if(TypeIsDictionary(job))
                        {
                            if(TypeIsString(job.condition))
                            {
                                if(!VM.Eval($"return {job.condition};"))
                                    continue;
                            }
                            var workdir = dir;
                            if(TypeIsString(job.workdir))
                                workdir /= job.workdir;
                            if(TypeIsList(job.commands))
                            each(var cmd : job.commands)
                            {
                                if(TypeIsList(cmd) && cmd.Length > 0)
                                {
                                    const name = cmd[0];
                                    const path = Env.GetRealExecutablePath(name).ToString();
                                    cmd.RemoveAt(0);
                                    var p= Process.Start({
                                        FileName = path,
                                        Arguments = cmd,
                                        WorkingDirectory = workdir
                                    });
                                    const result = p.Join();
                                    if(result != 0)
                                        throw $"process: {name}, did not indicate success: {result}";
                                }
                            }

                            if(TypeIsList(job.res))
                            {
                                each(var reso : job.res)
                                {
                                    if(TypeIsString(reso))
                                    {
                                        svfs.AddFS(new SubdirFilesystem(FS.Local, dir / reso));
                                    }
                                    else if(TypeIsDictionary(reso) && TypeIsString(reso.src) && TypeOfString(reso.dest))
                                    {
                                        const mountable = new MountableFilesystem(new Filesystem({}));
                                        mountable.Mount(reso.dest,new SubdirFilesystem(FS.Local, dir / reso.src));
                                        svfs.AddFS(mountable);
                                        
                                    }
                                }
                            }
                        }
                    }
                }

                each(var dep : dependencies)
                {
                    if(dep.Info.type == "lib")
                    {
                        file_deps.Add({
                            Name = dep.Name,
                            Version = dep.Version
                        });
                    }
                    else if(dep.Info.type == "compile_tool")
                    {

                        file_tools.Add({
                            Name = dep.Name,
                            Version = dep.Version
                        });
                    }
                    walk_for_compiling(dep,dir / outputDir);
                }


                func walk_for_source(sourceDir)
                {
                    if(FS.Local.DirectoryExists(sourceDir))
                    each(var file : FS.Local.EnumeratePaths(sourceDir))
                    {
                        if(FS.Local.RegularFileExists(file) && file.GetExtension()==".tcross")
                        {
                            var src = {
                                FileName = file.ToString(),
                                Source = FS.ReadAllText(FS.Local, file)
                            };
                            sources.Add(src);
                        }
                        else if(FS.Local.DirectoryExists(file))
                        {
                            walk_for_source(file);
                        }
                    }
                }

                walk_for_source(dir / srcDir);


                var output = $"{name}-{version}.crvm";
                var outFile = FS.Local.OpenFile(dir / outputDir / output,"wb");


                var compTimeEnv = null;

                if(compTime != "none")
                {
                    //dir / outputDir;



                    var exec = Env.GetRealExecutablePath("git");
                    var git_hash = "";
                    var git_tag = "";
                    if(FS.Local.FileExists(exec))
                    {
                        var process = new Process();
                        process.FileName = exec.ToString();
                        process.Arguments = ["rev-parse","HEAD"];
                        process.RedirectStandardInput = true;
                        process.RedirectStandardOutput = true;
                        process.WorkingDirectory = dirStr;
                        if(process.Start())
                        {
                            var memStrm = new MemoryStream(true);
                            process.StandardOutput.CopyTo(memStrm);
                            git_hash = memStrm.GetBytes().ToString().Split("\n")[0];

                        }
                    }




                    var dict = {
                        BuildTime = {
                            Git = {
                                Hash = git_hash,
                                Tag = git_tag
                            }
                        }
                    };
                    compTimeEnv = VM.CreateEnvironment(dict);




                    switch(compTime)
                    {
                        case "secure_file":
                            compTimeEnv.RegisterConsole();
                            compTimeEnv.RegisterClass();
                            compTimeEnv.RegisterCrypto();
                            compTimeEnv.RegisterDictionary();
                            compTimeEnv.RegisterJson();
                            compTimeEnv.RegisterRoot();
                            compTimeEnv.RegisterIO(false);

                            dict.FS.Local = new SubdirFilesystem(FS.Local,dir);

                            break;
                        case "full":
                            compTimeEnv.RegisterEverything();
                            break;
                        default:
                            //secure
                            compTimeEnv.RegisterConsole();
                            compTimeEnv.RegisterClass();
                            compTimeEnv.RegisterCrypto();
                            compTimeEnv.RegisterDictionary();
                            compTimeEnv.RegisterJson();
                            compTimeEnv.RegisterRoot();
                            compTimeEnv.RegisterIO(false);

                            break;
                    }
                    compTimeEnv.LockRegister();
                    each(var dep : file_deps)
                    {
                        compTimeEnv.LoadFileWithDependencies(FS.Local,dir/outputDir/$"{dep.Name}-{dep.Version}.crvm");
                    }
                }


                var result = VM.Compile({
                    Name = name,
                    Version = version,
                    Sources = sources,
                    Info = Json.Encode(info),
                    Icon = icon,
                    Tools = file_tools,
                    ResourceFileSystem = svfs,
                    Dependencies = file_deps,
                    Output = outFile,
                    CompTime = compTimeEnv,
                    Debug = this.Debug
                });

                outFile.Close();
                if(!result.Success)
                {
                    throw result.Reason;
                }

                var myData = {
                    Name = name,
                    Version = version,
                    Info = info,
                    Output = dir / outputDir / output,
                    Dependencies = dependencies
                };
                this.DirectoriesCompiled.Add({
                    Path = dirStr,
                    Data = myData
                });
                return myData;
            }
            return null;

    }

}
