#define Rtc 1
#include "buildit.h"

/* ========================== *
 * Util
 * ========================== */

void Error(String msg)
{
    SH_PrintF(Lit("ERROR: %F\n"), FmtStr(msg));
}

/* ========================== *
 * Compile command
 * ========================== */

typedef struct StepListNode StepListNode;
struct StepListNode {
    String name;
    String cmd;
    String link_file_path;
    Bool silence_output_if_success;
    StepListNode *next;
    StepListNode *prev;
};

typedef struct StepList StepList;
struct StepList {
    StepListNode *first;
    StepListNode *last;
    Size count;
};

void StepListAppend(Arena *arena, StepList *l, String name, String cmd, String link_file_path, Bool silence_output_if_success)
{
    StepListNode *n = ArenaPush(arena, StepListNode);
    n->name = name;
    n->cmd = cmd;
    n->link_file_path = link_file_path;
    n->silence_output_if_success = silence_output_if_success;
    DllPushBack(l->first, l->last, n);
    ++l->count;
}

/* ========================== *
 * Rc include
 * ========================== */

typedef struct RcIncludeListNode RcIncludeListNode;
struct RcIncludeListNode {
    D_Tag tag;
    String rc_type;
    RcIncludeListNode *next;
    RcIncludeListNode *prev;
};

typedef struct RcIncludeList RcIncludeList;
struct RcIncludeList {
    RcIncludeListNode *first;
    RcIncludeListNode *last;
    Size count;
};

void RcIncludeListAppend(Arena *arena, RcIncludeList *l, D_Tag tag, String rc_type)
{
    RcIncludeListNode *n = ArenaPush(arena, RcIncludeListNode);
    n->tag = tag;
    n->rc_type = rc_type;
    DllPushBack(l->first, l->last, n);
    ++l->count;
}

/* ========================== *
 * Build
 * ========================== */

void OnBuild(StringList cli_args)
{
    Arena arena = ArenaAlloc(Gigabyte(64));

    /* ========================== *
     * Read args
     * ========================== */

    String arg_outdir = Lit("build");
    Bool arg_msvc = false;
    Bool arg_rtc = false;
    Bool arg_asan = false;
    Bool arg_crtlib = false;
    Bool arg_debinfo = false;
    Bool arg_developer = false;
    Bool arg_profiling = false;
    Bool arg_unoptimized = false;

    {
        typedef enum ArgState {
            ArgState_None,
            ArgState_OutputDir
        } ArgState;

        ArgState arg_state = ArgState_None;
        for (StringListNode *n = cli_args.first; n; n = n->next) {
            String arg = n->string;
              if (n != cli_args.first) {
                switch (arg_state) {
                    case ArgState_OutputDir:
                    {
                        arg_outdir = arg;
                        arg_state = ArgState_None;
                    } break;

                    default:
                    {
                        if (StringEqual(arg, Lit("-O"))) arg_state = ArgState_OutputDir;
                        if (StringEqual(arg, Lit("-clang"))) arg_msvc = false;
                        if (StringEqual(arg, Lit("-msvc"))) arg_msvc = true;
                        if (StringEqual(arg, Lit("-rtc"))) arg_rtc = true;
                        if (StringEqual(arg, Lit("-asan"))) arg_asan = true;
                        if (StringEqual(arg, Lit("-crtlib"))) arg_crtlib = true;
                        if (StringEqual(arg, Lit("-debinfo"))) arg_debinfo = true;
                        if (StringEqual(arg, Lit("-developer"))) arg_developer = true;
                        if (StringEqual(arg, Lit("-profiling"))) arg_profiling = true;
                        if (StringEqual(arg, Lit("-unoptimized"))) arg_unoptimized = true;
                    } break;
                }
            }
        }
    }

    String hist_path = OS_GetAbsPath(&arena, StringF(&arena, Lit("%F/.dephist"), FmtStr(arg_outdir)));
    String out_obj_dir_path = OS_GetAbsPath(&arena, StringF(&arena, Lit("%F/obj/"), FmtStr(arg_outdir)));
    String out_inc_dir_path = OS_GetAbsPath(&arena, StringF(&arena, Lit("%F/inc/"), FmtStr(arg_outdir)));
    String out_bin_dir_path = OS_GetAbsPath(&arena, StringF(&arena, Lit("%F/bin/"), FmtStr(arg_outdir)));

    if (!OS_DirExists(out_obj_dir_path)) {
        OS_CreateDirAtAbsPath(out_obj_dir_path);
    }
    if (!OS_DirExists(out_inc_dir_path)) {
        OS_CreateDirAtAbsPath(out_inc_dir_path);
    }
    if (!OS_DirExists(out_bin_dir_path)) {
        OS_CreateDirAtAbsPath(out_bin_dir_path);
    }

    SH_Print(Lit("------------------------------\n"));
    {
        String compiler = { 0 };
        String compiler_loc = { 0 };
        SH_CommandResult where_res = { 0 };
        if (arg_msvc) {
            compiler = Lit("Msvc");
            where_res = SH_RunCommandCaptureOutput(&arena, Lit("where cl.exe"), true);
        } else {
            compiler = Lit("Clang");
            where_res = SH_RunCommandCaptureOutput(&arena, Lit("where clang.exe"), true);
        }
        compiler_loc = where_res.error ? Lit("Not found") : StringReplace(&arena, where_res.output, Lit("\n"), Lit(""));
        SH_PrintF(Lit("Compiler: %F (%F)\n"), FmtStr(compiler), FmtStr(compiler_loc));
    }
    if (arg_asan) SH_Print(Lit("Asan Enabled\n"));
    if (arg_profiling) SH_Print(Lit("Profiling\n"));
    if (arg_developer) SH_Print(Lit("Developer build enabled\n"));
    SH_PrintF(Lit("Building to \"%F\"\n"), FmtStr(out_bin_dir_path));
    SH_Print(Lit("------------------------------\n\n"));

    /* ========================== *
     * Load hist file
     * ========================== */

    D_Hist hist = D_HistFromPath(&arena, hist_path);

    /* ========================== *
     * Constants
     * ========================== */

    StepList compile_command_list = { 0 };

    String obj_file_extension = Lit("obj");
    D_Tag executable = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/PowerPlay.exe"), FmtStr(out_bin_dir_path)));
    D_Tag pdb = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/PowerPlay.pdb"), FmtStr(out_bin_dir_path)));

    /* Pch tags */
    D_Tag pch_header = D_FileTagFromPath(&arena, Lit("src/common.h"));
    D_Tag pch_c_src_gen_output = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/common.c"), FmtStr(out_obj_dir_path)));
    D_Tag pch_c_src_gen_obj_output = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/common.c.obj"), FmtStr(out_obj_dir_path)));
    D_Tag pch_c_output = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/common.c.pch"), FmtStr(out_obj_dir_path)));
    D_Tag pch_cpp_output = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/common.cpp.pch"), FmtStr(out_obj_dir_path)));
    D_Tag pch_cpp_src_gen_output = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/common.cpp"), FmtStr(out_obj_dir_path)));
    D_Tag pch_cpp_src_gen_obj_output = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/common.cpp.obj"), FmtStr(out_obj_dir_path)));
    /* Dependency order: common.h -> common.c (generated for msvc) -> common.c.obj -> common.c.pch -> PowerPlay.exe */
    D_AddDependency(pch_c_src_gen_output, pch_header);
    D_AddDependency(pch_c_src_gen_obj_output, pch_c_src_gen_output);
    D_AddDependency(pch_c_output, pch_c_src_gen_obj_output);
    D_AddDependency(executable, pch_c_output);
    /* Dependency order: common.h -> common.cpp (generated for msvc) -> common.cpp.obj -> common.cpp.pch  -> PowerPlay.exe*/
    D_AddDependency(pch_cpp_src_gen_output, pch_header);
    D_AddDependency(pch_cpp_src_gen_obj_output, pch_cpp_src_gen_output);
    D_AddDependency(pch_cpp_output, pch_cpp_src_gen_obj_output);
    D_AddDependency(executable, pch_cpp_output);

    /* ========================== *
     * Determine compiler args
     * ========================== */

    String final_c_compile_args_fmt = { 0 };
    String final_cpp_compile_args_fmt = { 0 };
    String final_pch_c_compile_args_fmt = { 0 };
    String final_pch_cpp_compile_args_fmt = { 0 };
    String final_link_args_fmt = { 0 };

    {
        StringList compile_warnings = { 0 };
        StringList compile_and_link_args = { 0 };
        StringList compile_args = { 0 };

        StringList c_compile_args = { 0 };
        StringList cpp_compile_args = { 0 };
        StringList pch_c_compile_args = { 0 };
        StringList pch_cpp_compile_args = { 0 };

        StringList link_warnings = { 0 };
        StringList link_args = { 0 };

        if (arg_msvc) {
            /* Msvc */
            StringListAppend(&arena, &c_compile_args, Lit("cl.exe /nologo /c \"%F\" /Fo\"%F\""));
            StringListAppend(&arena, &cpp_compile_args, Lit("cl.exe /nologo /c \"%F\" /Fo\"%F\""));
            StringListAppend(&arena, &pch_c_compile_args, Lit("cl.exe /nologo /c /Yc\"%F\" \"%F\""));
            StringListAppend(&arena, &pch_cpp_compile_args, Lit("cl.exe /nologo /c /Yc\"%F\" \"%F\""));

            StringListAppend(&arena, &c_compile_args, StringF(&arena, Lit("/Yu\"%F\" /FI\"%F\" /Fp\"%F\""), FmtStr(pch_header.full_path), FmtStr(pch_header.full_path), FmtStr(pch_c_output.full_path)));
            StringListAppend(&arena, &cpp_compile_args, StringF(&arena, Lit("/Yu\"%F\" /FI\"%F\" /Fp\"%F\""), FmtStr(pch_header.full_path), FmtStr(pch_header.full_path), FmtStr(pch_cpp_output.full_path)));
            StringListAppend(&arena, &pch_c_compile_args, StringF(&arena, Lit("/FI\"%F\" /Fp\"%F\" /Fo\"%F\""), FmtStr(pch_header.full_path), FmtStr(pch_c_output.full_path), FmtStr(pch_c_src_gen_obj_output.full_path)));
            StringListAppend(&arena, &pch_cpp_compile_args, StringF(&arena, Lit("/FI\"%F\" /Fp\"%F\" /Fo\"%F\""), FmtStr(pch_header.full_path), FmtStr(pch_cpp_output.full_path), FmtStr(pch_cpp_src_gen_obj_output.full_path)));

            StringListAppend(&arena, &link_args, Lit("link.exe /nologo %F"));
            StringListAppend(&arena, &link_args, StringF(&arena, Lit("/OUT:\"%F\" /PDB:\"%F\" /DEBUG:FULL /OPT:REF /OPT:ICF"), FmtStr(executable.full_path), FmtStr(pdb.full_path)));

            String warnings = Lit("/WX /Wall "
                                  "/options:strict "
                                  "/wd4820 /wd4201 /wd5220 /wd4514 /wd4244 /wd5045 /wd4242 /wd4061 /wd4189 /wd4723 /wd5246");
            StringListAppend(&arena, &compile_warnings, warnings);
            StringListAppend(&arena, &link_warnings, Lit("/WX"));

            StringListAppend(&arena, &compile_args, StringF(&arena, Lit("/Fd\"%F\\\""), FmtStr(out_bin_dir_path)));
        } else {
            /* Clang */
            StringListAppend(&arena, &c_compile_args, Lit("clang -xc -std=c99 -c %F -o %F"));
            StringListAppend(&arena, &cpp_compile_args, Lit("clang -xc++ -std=c++20 -c %F -o %F"));
            StringListAppend(&arena, &pch_c_compile_args, Lit("clang -xc-header -std=c99 -c %F -o %F"));
            StringListAppend(&arena, &pch_cpp_compile_args, Lit("clang -xc++-header -std=c++20 -c %F -o %F"));

            StringListAppend(&arena, &c_compile_args, StringF(&arena, Lit("-include-pch %F"), FmtStr(pch_c_output.full_path)));
            StringListAppend(&arena, &cpp_compile_args, StringF(&arena, Lit("-include-pch %F"), FmtStr(pch_cpp_output.full_path)));

            StringListAppend(&arena, &link_args, Lit("clang %F"));
            StringListAppend(&arena, &link_args, StringF(&arena, Lit("-o \"%F\""), FmtStr(executable.full_path)));

            StringListAppend(&arena,
                             &compile_and_link_args,
                             Lit("-fuse-ld=lld-link "
                                 "-fno-strict-aliasing "
                                 "-fno-finite-loops "
                                 "-fwrapv "
                                 "-msse4.1 "
                                 "-msse4.2 "));

            String warnings = Lit("-Weverything -Werror "
                                  "-Wframe-larger-than=65536 "
                                  "-Wno-unused-macros -Wno-gnu-zero-variadic-macro-arguments -Wno-documentation "
                                  "-Wno-old-style-cast -Wno-conversion -Wno-sign-conversion "
                                  "-Wno-declaration-after-statement -Wno-extra-semi -Wno-extra-semi-stmt "
                                  "-Wno-bad-function-cast -Wno-class-varargs -Wno-unreachable-code-break "
                                  "-Wno-cast-align -Wno-float-equal -Wno-zero-as-null-pointer-constant "
                                  "-Wno-cast-qual -Wno-missing-noreturn -Wno-missing-field-initializers "
                                  "-Wno-missing-braces -Wno-initializer-overrides "
                                  "-Wno-c99-extensions -Wno-c++98-compat-pedantic -Wno-c++98-compat "
                                  "-Wno-switch-enum -Wno-switch-default "
                                  "-Wno-reserved-identifier -Wno-reserved-macro-identifier "
                                  "-Wno-unsafe-buffer-usage -Wno-writable-strings "
                                  ""
                                  "-Wno-c11-extensions -Wno-gnu-anonymous-struct -Wno-nested-anon-types "
                                  ""
                                  "-Wno-double-promotion");
            /* -Wno-unused-variable -Wno-unused-but-set-variable -Wno-unused-parameter */

            StringListAppend(&arena, &compile_warnings, warnings);
            StringListAppend(&arena, &link_warnings, warnings);
        }

        /* RTC */
        if (arg_rtc) {
            if (!arg_crtlib) {
                Error(Lit("CRTLIB (C runtime library) Must be enabled when compiling with RTC (runtime checks)"));
                OS_Exit(1);
            }
            StringListAppend(&arena, &compile_args, Lit("-DRTC=1"));
            if (arg_msvc) {
                if (!arg_asan) {
                    /* Enable /RTC option (not compatible with ASAN) */
                    StringListAppend(&arena, &compile_and_link_args, Lit("/RTCcsu"));
                }
            } else {
                /* Enable UBSan */
                StringListAppend(&arena, &compile_and_link_args, Lit("-fsanitize=undefined -fsanitize-trap=all"));
                //StringListAppend(&compile_and_link_args, "-fsanitize=undefined");
            }
        }

        /* CRTLIB */
        if (arg_crtlib) {
            StringListAppend(&arena, &compile_args, Lit("-DCRTLIB=1"));
        } else {
            if (arg_msvc) {
                /* TODO */
                Error(Lit("TODO\n"));
                OS_Exit(1);
            } else {
                StringListAppend(&arena, &compile_and_link_args, Lit("-mno-stack-arg-probe -fno-builtin -nostdlib"));
            }
        }

        /* Optimization */
        if (arg_unoptimized) {
            StringListAppend(&arena, &compile_args, Lit("-DUNOPTIMIZED=1"));
            if (arg_msvc) {
                StringListAppend(&arena, &compile_args, Lit("/Od"));
            } else {
                StringListAppend(&arena, &compile_and_link_args, Lit("-O0"));
            }
        } else {
            if (arg_msvc) {
                StringListAppend(&arena, &compile_args, Lit("/O2"));
                StringListAppend(&arena, &link_args, Lit("/LTCG"));
            } else {
                StringListAppend(&arena, &compile_and_link_args, Lit("-O3 -flto"));
            }
        }

        /* Debug info */
        if (arg_debinfo) {
            StringListAppend(&arena, &compile_args, Lit("-DDEBINFO=1"));
            if (arg_msvc) {
                StringListAppend(&arena, &compile_args, Lit("/JMC /Zi"));
            } else {
                StringListAppend(&arena, &compile_and_link_args, Lit("-g"));
            }
        }

        /* Address sanitizer */
        if (arg_asan) {
            if (!arg_crtlib) {
                Error(Lit("CRTLIB (C runtime library) Must be enabled when compiling with asan enabled"));
                OS_Exit(1);
            }
            StringListAppend(&arena, &compile_args, Lit("-DASAN=1"));
            if (arg_msvc) {
                StringListAppend(&arena, &compile_args, Lit("/fsanitize=address"));
            } else {
                StringListAppend(&arena, &compile_and_link_args, Lit("-fsanitize=address -shared-libasan"));
            }
        }

        /* Developer mode */
        if (arg_developer) {
            StringListAppend(&arena, &compile_args, Lit("-DDEVELOPER=1"));
        }

        /* Profiling */
        if (arg_profiling) {
            if (!arg_crtlib) {
                Error(Lit("CRTLIB (C runtime library) Must be enabled when compiling with profiling enabled"));
                OS_Exit(1);
            }
            if (arg_msvc) {
                Error(Lit("MSVC not supported with profiling enabled (Profiling relies on Clang attributes)"));
                OS_Exit(1);
            }
            StringListAppend(&arena, &compile_args, Lit("-DPROFILING=1"));
            /* Tracy flags */
            StringListAppend(&arena, &compile_args, Lit("-DTRACY_ENABLE=1"));
            StringListAppend(&arena, &compile_args, Lit("-DTRACY_CALLSTACK=5"));
            StringListAppend(&arena, &compile_args, Lit("-DTRACY_NO_SAMPLING -DTRACY_NO_SYSTEM_TRACING -DTRACY_NO_CALLSTACK"));
            /* Disable compile_warnings when compiling tracy client */
            compile_warnings = (StringList) { 0 };
            link_warnings = (StringList) { 0 };
        }

        if (!arg_msvc) {
            String incbin_dir = StringReplace(&arena, out_inc_dir_path, Lit("\\"), Lit("/"));
            StringListAppend(&arena, &compile_args, StringF(&arena, Lit("-DINCBIN_DIR_RAW=\"%F\""), FmtStr(incbin_dir)));
        }

        final_c_compile_args_fmt = StringFromStringLists(&arena, Lit(" "), c_compile_args, compile_warnings, compile_and_link_args, compile_args);
        final_cpp_compile_args_fmt = StringFromStringLists(&arena, Lit(" "), cpp_compile_args, compile_warnings, compile_and_link_args, compile_args);
        final_pch_c_compile_args_fmt = StringFromStringLists(&arena, Lit(" "), pch_c_compile_args, compile_warnings, compile_and_link_args, compile_args);
        final_pch_cpp_compile_args_fmt = StringFromStringLists(&arena, Lit(" "), pch_cpp_compile_args, compile_warnings, compile_and_link_args, compile_args);
        final_link_args_fmt = StringFromStringLists(&arena, Lit(" "), link_args, link_warnings, compile_and_link_args);
    }

    /* ========================== *
     * Generate embeddable tar files
     * ========================== */

    Bool embed_in_rc = !!arg_msvc;

    D_Tag res_dir = D_DirTagFromPath(&arena, Lit("res"));
    D_Tag shaders_dir = D_DirTagFromPath(&arena, Lit("src/shaders"));

    D_Tag inc_file = D_FileTagFromPath(&arena, Lit("src/inc.c"));
    RcIncludeList rc_includes = { 0 };

    /* Generate shaders tar */
    D_Tag shaders_tar = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/shaders.tar"), FmtStr(out_inc_dir_path)));
    D_AddDependency(shaders_tar, shaders_dir);
    if (embed_in_rc) {
        RcIncludeListAppend(&arena, &rc_includes, shaders_tar, Lit("RCDATA"));
    } else {
        D_AddDependency(inc_file, shaders_tar);
    }
    if (D_IsDirty(shaders_tar, &hist)) {
        String tar_cmd = StringF(&arena, Lit("cd %F && tar cvf %F ."), FmtStr(shaders_dir.full_path), FmtStr(shaders_tar.full_path));
        String step_name = StringF(&arena, Lit("%F -> %F"), FmtStr(D_GetName(shaders_dir)), FmtStr(D_GetName(shaders_tar)));
        StepListAppend(&arena, &compile_command_list, step_name, tar_cmd, (String) { 0 }, true);
    }

    /* Generate res tar */
    if (!arg_developer) {
        D_Tag res_tar = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/res.tar"), FmtStr(out_inc_dir_path)));
        D_AddDependency(res_tar, res_dir);
        if (embed_in_rc) {
            RcIncludeListAppend(&arena, &rc_includes, res_tar, Lit("RCDATA"));
        } else {
            D_AddDependency(inc_file, res_tar);
        }
        if (D_IsDirty(res_tar, &hist)) {
            String tar_cmd = StringF(&arena, Lit("cd %F && tar cvf %F ."), FmtStr(res_dir.full_path), FmtStr(res_tar.full_path));
            String step_name = StringF(&arena, Lit("%F -> %F"), FmtStr(D_GetName(res_dir)), FmtStr(D_GetName(res_tar)));
            StepListAppend(&arena, &compile_command_list, step_name, tar_cmd, (String) { 0 }, true);
        }
    }

    /* ========================== *
     * RC file (windows)
     * ========================== */

    if (PlatformWindows) {
        D_Tag rc_file = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/rc.rc"), FmtStr(out_inc_dir_path)));;

        /* Add icon file to rc list */
        {
            D_Tag icon_file = D_FileTagFromPath(&arena, Lit("icon.ico"));
            RcIncludeListAppend(&arena, &rc_includes, icon_file, Lit("ICON"));
        }

        /* Add rc dependencies */
        for (RcIncludeListNode *rin = rc_includes.first; rin; rin = rin->next) {
            D_AddDependency(rc_file, rin->tag);
        }

        D_Tag rc_res_file = D_FileTagFromPath(&arena, StringF(&arena, Lit("%F/rc.res"), FmtStr(out_obj_dir_path)));
        D_AddDependency(rc_res_file, rc_file);
        D_AddDependency(executable, rc_res_file);

        if (D_IsDirty(rc_res_file, &hist)) {
            /* Generate rc file */
            D_ClearWrite(rc_file, Lit(""));
            for (RcIncludeListNode *rin = rc_includes.first; rin; rin = rin->next) {
                String name = D_GetName(rin->tag);
                String line = StringF(&arena, Lit("%F %F DISCARDABLE %F\n"), FmtStr(name), FmtStr(rin->rc_type), FmtStr(name));
                D_AppendWrite(rc_file, line);
            }

            /* Append rc -> res compile command */
            String rc_compile_cmd = { 0 };
            if (arg_msvc) {
                rc_compile_cmd = StringF(&arena, Lit("rc /fo\"%F\" \"%F\""), FmtStr(rc_res_file.full_path), FmtStr(rc_file.full_path));
            } else {
                rc_compile_cmd = StringF(&arena, Lit("llvm-rc /fo\"%F\" \"%F\""), FmtStr(rc_res_file.full_path), FmtStr(rc_file.full_path));
            }
            String step_name = StringF(&arena, Lit("%F -> %F"), FmtStr(D_GetName(rc_file)), FmtStr(D_GetName(rc_res_file)));
            StepListAppend(&arena, &compile_command_list, step_name, rc_compile_cmd, rc_res_file.full_path, true);
        }
    }


    /* ========================== *
     * PCH compile commands
     * ========================== */

    if (arg_msvc) {
        {
            if (D_IsDirty(pch_c_output, &hist)) {
                D_ClearWrite(pch_c_src_gen_output, Lit(""));
                String comp_cmd = StringF(&arena, final_pch_c_compile_args_fmt, FmtStr(pch_header.full_path), FmtStr(pch_c_src_gen_output.full_path));
                String link_file = pch_c_src_gen_obj_output.full_path;
                String step_name = StringF(&arena, Lit("%F -> %F"), FmtStr(D_GetName(pch_header)), FmtStr(D_GetName(pch_c_output)));
                StepListAppend(&arena, &compile_command_list, step_name, comp_cmd, link_file, true);
            }
        }
        {
            if (D_IsDirty(pch_cpp_src_gen_output, &hist)) {
                D_ClearWrite(pch_cpp_src_gen_output, Lit(""));
                String comp_cmd = StringF(&arena, final_pch_cpp_compile_args_fmt, FmtStr(pch_header.full_path), FmtStr(pch_cpp_src_gen_output.full_path));
                String link_file = pch_cpp_src_gen_obj_output.full_path;
                String step_name = StringF(&arena, Lit("%F -> %F"), FmtStr(D_GetName(pch_header)), FmtStr(D_GetName(pch_cpp_src_gen_output)));
                StepListAppend(&arena, &compile_command_list, step_name, comp_cmd, link_file, true);
            }
        }
    } else {
        {
            if (D_IsDirty(pch_c_output, &hist)) {
                String comp_cmd = StringF(&arena, final_pch_c_compile_args_fmt, FmtStr(pch_header.full_path), FmtStr(pch_c_output.full_path));
                String step_name = StringF(&arena, Lit("%F -> %F"), FmtStr(D_GetName(pch_header)), FmtStr(D_GetName(pch_c_output)));
                StepListAppend(&arena, &compile_command_list, step_name, comp_cmd, (String) { 0 }, true);
            }
        }
        {
            if (D_IsDirty(pch_cpp_output, &hist)) {
                String comp_cmd = StringF(&arena, final_pch_cpp_compile_args_fmt, FmtStr(pch_header.full_path), FmtStr(pch_cpp_output.full_path));
                String step_name = StringF(&arena, Lit("%F -> %F"), FmtStr(D_GetName(pch_header)), FmtStr(D_GetName(pch_cpp_output)));
                StepListAppend(&arena, &compile_command_list, step_name, comp_cmd, (String) { 0 }, true);
            }
        }
    }

    /* ========================== *
     * Add src file compile commands
     * ========================== */

    D_Tag src_dir = D_DirTagFromPath(&arena, Lit("src"));
    D_TagList src_files = D_GetDirContents(&arena, src_dir);

    for (D_TagListNode *n = src_files.first; n; n = n->next) {
        D_Tag file = n->tag;

        Bool ignore = !!file.is_dir;
        if (ignore) continue;

        String name = D_GetName(file);
        String extension = D_GetExtension(file);
        Bool is_c = StringEqual(extension, Lit("c"));
        Bool is_cpp = !is_c && StringEqual(extension, Lit("cpp"));

        ignore = !(is_c || is_cpp);
        if (ignore) continue;

        /* Determine platform specific source files */
        {
            if (StringBeginsWith(name, Lit("sys_")) ||
                StringBeginsWith(name, Lit("renderer_")) ||
                StringBeginsWith(name, Lit("playback_")) ||
                StringBeginsWith(name, Lit("mp3_")) ||
                StringBeginsWith(name, Lit("ttf_"))) {
                ignore = true;
                if (PlatformWindows) {
                    ignore = !(StringEqual(name, Lit("sys_win32.c")) ||
                               StringEqual(name, Lit("renderer_d3d11.c")) ||
                               StringEqual(name, Lit("playback_wasapi.c")) ||
                               StringEqual(name, Lit("mp3_mmf.c")) ||
                               StringEqual(name, Lit("ttf_dwrite.cpp")));
                }
            }

        }
        if (ignore) continue;

        D_Tag obj_file;
        {
            String obj_file_path = { 0 };
            String name_no_extension = StringPathNoExtension(name);
            obj_file_path = StringF(&arena, Lit("%F/%F.%F"), FmtStr(out_obj_dir_path), FmtStr(name_no_extension), FmtStr(obj_file_extension));
            obj_file = D_FileTagFromPath(&arena, obj_file_path);
        }
        D_AddDependency(obj_file, file);
        D_AddDependency(executable, obj_file);

        String comp_cmd = { 0 };
        if (D_IsDirty(obj_file, &hist)) {
            String comp_cmd_fmt = is_c ? final_c_compile_args_fmt : final_cpp_compile_args_fmt;
            comp_cmd = StringF(&arena, comp_cmd_fmt, FmtStr(file.full_path), FmtStr(obj_file.full_path));
        }

        String step_name = StringF(&arena, Lit("%F -> %F"), FmtStr(name), FmtStr(D_GetName(obj_file)));
        StepListAppend(&arena, &compile_command_list, step_name, comp_cmd, obj_file.full_path, true);
    }

    /* ========================== *
     * Compile / link
     * ========================== */

    if (D_IsDirty(executable, &hist)) {
        Bool success = true;
        StringList link_files = { 0 };

        /* Compile */
        Size comp_i = 0;
        Size comp_count = compile_command_list.count;
        for (StepListNode *n = compile_command_list.first; n; n = n->next) {
            ++comp_i;
            String comp_cmd = n->cmd;
            if (comp_cmd.len > 0 ) {
                SH_PrintF(Lit("[%F/%F] %F\n"), FmtI64(comp_i), FmtI64(comp_count), FmtStr(n->name));
                //SH_PrintF(Lit("%F\n"), FmtStr(comp_cmd));
                SH_CommandResult result = SH_RunCommandCaptureOutput(&arena, comp_cmd, true);
                if (!n->silence_output_if_success || result.error != 0) {
                    SH_PrintF(Lit("%F\n"), FmtStr(result.output));
                }
                if (result.error != 0) {
                    Assert(false);
                    success = false;
                    break;
                }
            }
            String link_file_path = n->link_file_path;
            if (link_file_path.len > 0) {
                StringListAppend(&arena, &link_files, link_file_path);
            }
        }

        /* Link */
        if (success) {
            String link_files_str = { 0 };
            {
                StringList link_files_quoted_list = { 0 };
                for (StringListNode *n = link_files.first; n; n = n->next) {
                    String path = StringF(&arena, Lit("\"%F\""), FmtStr(n->string));
                    StringListAppend(&arena, &link_files_quoted_list, path);
                }
                link_files_str = StringFromStringList(&arena, Lit(" "), link_files_quoted_list);
            }

            String link_cmd = StringF(&arena, final_link_args_fmt, FmtStr(link_files_str));
            //SH_PrintF(Lit("[Link] %F\n"), FmtStr(link_cmd));
            SH_Print(Lit("Linking...\n"));
            SH_CommandResult result = SH_RunCommandCaptureOutput(&arena, link_cmd, false);
            if (result.error != 0) {
                Assert(false);
                success = false;
            }
            //D_SetDirty(executable);
        }
        if (!success) {
            Error(Lit("Build failed"));
            OS_Exit(1);
        }
    } else {
        /* Nothing to build */
        SH_Print(Lit("Nothing to build"));
    }

    /* ========================== *
     * Write hist file
     * ========================== */

    D_WriteStateToHistFile(hist_path);

#if 0
#if Rtc
    getchar();
#endif
#endif
}
