/*
 * Copyright (c) 2015 Andrew Kelley
 *
 * This file is part of zig, which is MIT licensed.
 * See http://opensource.org/licenses/MIT
 */

#include "ast_render.hpp"
#include "buffer.hpp"
#include "codegen.hpp"
#include "config.h"
#include "error.hpp"
#include "link.hpp"
#include "os.hpp"
#include "target.hpp"

#include <stdio.h>

static int usage(const char *arg0) {
    fprintf(stderr, "Usage: %s [command] [options]\n"
        "Commands:\n"
        "  build                        build project from build.zig\n"
        "  build-exe [source]           create executable from source or object files\n"
        "  build-lib [source]           create library from source or object files\n"
        "  build-obj [source]           create object from source or assembly\n"
        "  parsec [source]              convert c code to zig code\n"
        "  targets                      list available compilation targets\n"
        "  test [source]                create and run a test build\n"
        "  version                      print version number and exit\n"
        "  zen                          print zen of zig and exit\n"
        "Compile Options:\n"
        "  --assembly [source]          add assembly file to build\n"
        "  --cache-dir [path]           override the cache directory\n"
        "  --color [auto|off|on]        enable or disable colored error messages\n"
        "  --enable-timing-info         print timing diagnostics\n"
        "  --libc-include-dir [path]    directory where libc stdlib.h resides\n"
        "  --name [name]                override output name\n"
        "  --output [file]              override destination path\n"
        "  --output-h [file]            override generated header file path\n"
        "  --pkg-begin [name] [path]    make package available to import and push current pkg\n"
        "  --pkg-end                    pop current pkg\n"
        "  --release-fast               build with optimizations on and safety off\n"
        "  --release-safe               build with optimizations on and safety on\n"
        "  --static                     output will be statically linked\n"
        "  --strip                      exclude debug symbols\n"
        "  --target-arch [name]         specify target architecture\n"
        "  --target-environ [name]      specify target environment\n"
        "  --target-os [name]           specify target operating system\n"
        "  --verbose                    turn on compiler debug output\n"
        "  --verbose-link               turn on compiler debug output for linking only\n"
        "  --verbose-ir                 turn on compiler debug output for IR only\n"
        "  --zig-install-prefix [path]  override directory where zig thinks it is installed\n"
        "  -dirafter [dir]              same as -isystem but do it last\n"
        "  -isystem [dir]               add additional search path for other .h files\n"
        "  -mllvm [arg]                 additional arguments to forward to LLVM's option processing\n"
        "Link Options:\n"
        "  --ar-path [path]             set the path to ar\n"
        "  --dynamic-linker [path]      set the path to ld.so\n"
        "  --each-lib-rpath             add rpath for each used dynamic library\n"
        "  --libc-lib-dir [path]        directory where libc crt1.o resides\n"
        "  --libc-static-lib-dir [path] directory where libc crtbegin.o resides\n"
        "  --msvc-lib-dir [path]        (windows) directory where vcruntime.lib resides\n"
        "  --kernel32-lib-dir [path]    (windows) directory where kernel32.lib resides\n"
        "  --library [lib]              link against lib\n"
        "  --library-path [dir]         add a directory to the library search path\n"
        "  --linker-script [path]       use a custom linker script\n"
        "  --object [obj]               add object file to build\n"
        "  -L[dir]                      alias for --library-path\n"
        "  -rdynamic                    add all symbols to the dynamic symbol table\n"
        "  -rpath [path]                add directory to the runtime library search path\n"
        "  -mconsole                    (windows) --subsystem console to the linker\n"
        "  -mwindows                    (windows) --subsystem windows to the linker\n"
        "  -framework [name]            (darwin) link against framework\n"
        "  -mios-version-min [ver]      (darwin) set iOS deployment target\n"
        "  -mmacosx-version-min [ver]   (darwin) set Mac OS X deployment target\n"
        "  --ver-major [ver]            dynamic library semver major version\n"
        "  --ver-minor [ver]            dynamic library semver minor version\n"
        "  --ver-patch [ver]            dynamic library semver patch version\n"
        "Test Options:\n"
        "  --test-filter [text]         skip tests that do not match filter\n"
        "  --test-name-prefix [text]    add prefix to all tests\n"
        "  --test-cmd [arg]             specify test execution command one arg at a time\n"
        "  --test-cmd-bin               appends test binary path to test cmd args\n"
    , arg0);
    return EXIT_FAILURE;
}

static const char *ZIG_ZEN = "\n"
" * Communicate intent precisely.\n"
" * Edge cases matter.\n"
" * Favor reading code over writing code.\n"
" * Only one obvious way to do things.\n"
" * Runtime crashes are better than bugs.\n"
" * Compile errors are better than runtime crashes.\n"
" * Incremental improvements.\n"
" * Avoid local maximums.\n"
" * Reduce the amount one must remember.\n"
" * Minimize energy spent on coding style.\n"
" * Together we serve end users.\n";

static int print_target_list(FILE *f) {
    ZigTarget native;
    get_native_target(&native);

    fprintf(f, "Architectures:\n");
    size_t arch_count = target_arch_count();
    for (size_t arch_i = 0; arch_i < arch_count; arch_i += 1) {
        const ArchType *arch = get_target_arch(arch_i);
        char arch_name[50];
        get_arch_name(arch_name, arch);
        const char *native_str = (native.arch.arch == arch->arch && native.arch.sub_arch == arch->sub_arch) ?
            " (native)" : "";
        fprintf(f, "  %s%s\n", arch_name, native_str);
    }

    fprintf(f, "\nOperating Systems:\n");
    size_t os_count = target_os_count();
    for (size_t i = 0; i < os_count; i += 1) {
        ZigLLVM_OSType os_type = get_target_os(i);
        const char *native_str = (native.os == os_type) ? " (native)" : "";
        fprintf(f, "  %s%s\n", get_target_os_name(os_type), native_str);
    }

    fprintf(f, "\nEnvironments:\n");
    size_t environ_count = target_environ_count();
    for (size_t i = 0; i < environ_count; i += 1) {
        ZigLLVM_EnvironmentType environ_type = get_target_environ(i);
        const char *native_str = (native.env_type == environ_type) ? " (native)" : "";
        fprintf(f, "  %s%s\n", ZigLLVMGetEnvironmentTypeName(environ_type), native_str);
    }

    return EXIT_SUCCESS;
}

static bool test_zig_install_prefix(Buf *test_path, Buf *out_zig_lib_dir) {
    Buf lib_buf = BUF_INIT;
    buf_init_from_str(&lib_buf, "lib");

    Buf zig_buf = BUF_INIT;
    buf_init_from_str(&zig_buf, "zig");

    Buf std_buf = BUF_INIT;
    buf_init_from_str(&std_buf, "std");

    Buf index_zig_buf = BUF_INIT;
    buf_init_from_str(&index_zig_buf, "index.zig");

    Buf test_lib_dir = BUF_INIT;
    Buf test_zig_dir = BUF_INIT;
    Buf test_std_dir = BUF_INIT;
    Buf test_index_file = BUF_INIT;

    os_path_join(test_path, &lib_buf, &test_lib_dir);
    os_path_join(&test_lib_dir, &zig_buf, &test_zig_dir);
    os_path_join(&test_zig_dir, &std_buf, &test_std_dir);
    os_path_join(&test_std_dir, &index_zig_buf, &test_index_file);

    int err;
    bool exists;
    if ((err = os_file_exists(&test_index_file, &exists))) {
        exists = false;
    }
    if (exists) {
        buf_init_from_buf(out_zig_lib_dir, &test_zig_dir);
        return true;
    }
    return false;
}

static int find_zig_lib_dir(Buf *out_path) {
    int err;

    Buf self_exe_path = BUF_INIT;
    if (!(err = os_self_exe_path(&self_exe_path))) {
        Buf *cur_path = &self_exe_path;

        for (;;) {
            Buf *test_dir = buf_alloc();
            os_path_dirname(cur_path, test_dir);

            if (buf_eql_buf(test_dir, cur_path)) {
                break;
            }

            if (test_zig_install_prefix(test_dir, out_path)) {
                return 0;
            }

            cur_path = test_dir;
        }
    }

    if (ZIG_INSTALL_PREFIX != nullptr) {
        if (test_zig_install_prefix(buf_create_from_str(ZIG_INSTALL_PREFIX), out_path)) {
            return 0;
        }
    }


    return ErrorFileNotFound;
}

static Buf *resolve_zig_lib_dir(const char *zig_install_prefix_arg) {
    int err;
    Buf *result = buf_alloc();
    if (zig_install_prefix_arg == nullptr) {
        if ((err = find_zig_lib_dir(result))) {
            fprintf(stderr, "Unable to find zig lib directory. Reinstall Zig or use --zig-install-prefix.\n");
            exit(EXIT_FAILURE);
        }
        return result;
    }
    Buf *zig_lib_dir_buf = buf_create_from_str(zig_install_prefix_arg);
    if (test_zig_install_prefix(zig_lib_dir_buf, result)) {
        return result;
    }

    fprintf(stderr, "No Zig installation found at prefix: %s\n", zig_install_prefix_arg);
    exit(EXIT_FAILURE);
}

enum Cmd {
    CmdInvalid,
    CmdBuild,
    CmdTest,
    CmdVersion,
    CmdZen,
    CmdParseC,
    CmdTargets,
};

static const char *default_zig_cache_name = "zig-cache";

struct CliPkg {
    const char *name;
    const char *path;
    ZigList<CliPkg *> children;
    CliPkg *parent;
};

static void add_package(CodeGen *g, CliPkg *cli_pkg, PackageTableEntry *pkg) {
    for (size_t i = 0; i < cli_pkg->children.length; i += 1) {
        CliPkg *child_cli_pkg = cli_pkg->children.at(i);

        Buf *dirname = buf_alloc();
        Buf *basename = buf_alloc();
        os_path_split(buf_create_from_str(child_cli_pkg->path), dirname, basename);

        PackageTableEntry *child_pkg = codegen_create_package(g, buf_ptr(dirname), buf_ptr(basename));
        auto entry = pkg->package_table.put_unique(buf_create_from_str(child_cli_pkg->name), child_pkg);
        if (entry) {
            PackageTableEntry *existing_pkg = entry->value;
            Buf *full_path = buf_alloc();
            os_path_join(&existing_pkg->root_src_dir, &existing_pkg->root_src_path, full_path);
            fprintf(stderr, "Unable to add package '%s'->'%s': already exists as '%s'\n",
                    child_cli_pkg->name, child_cli_pkg->path, buf_ptr(full_path));
            exit(EXIT_FAILURE);
        }

        add_package(g, child_cli_pkg, child_pkg);
    }
}

int main(int argc, char **argv) {
    os_init();

    char *arg0 = argv[0];
    Cmd cmd = CmdInvalid;
    const char *in_file = nullptr;
    const char *out_file = nullptr;
    const char *out_file_h = nullptr;
    bool strip = false;
    bool is_static = false;
    OutType out_type = OutTypeUnknown;
    const char *out_name = nullptr;
    bool verbose = false;
    bool verbose_link = false;
    bool verbose_ir = false;
    ErrColor color = ErrColorAuto;
    const char *libc_lib_dir = nullptr;
    const char *libc_static_lib_dir = nullptr;
    const char *libc_include_dir = nullptr;
    const char *msvc_lib_dir = nullptr;
    const char *kernel32_lib_dir = nullptr;
    const char *zig_install_prefix = nullptr;
    const char *dynamic_linker = nullptr;
    ZigList<const char *> clang_argv = {0};
    ZigList<const char *> llvm_argv = {0};
    ZigList<const char *> lib_dirs = {0};
    ZigList<const char *> link_libs = {0};
    ZigList<const char *> frameworks = {0};
    int err;
    const char *target_arch = nullptr;
    const char *target_os = nullptr;
    const char *target_environ = nullptr;
    bool mwindows = false;
    bool mconsole = false;
    bool rdynamic = false;
    const char *mmacosx_version_min = nullptr;
    const char *mios_version_min = nullptr;
    const char *linker_script = nullptr;
    ZigList<const char *> rpath_list = {0};
    bool each_lib_rpath = false;
    ZigList<const char *> objects = {0};
    ZigList<const char *> asm_files = {0};
    const char *test_filter = nullptr;
    const char *test_name_prefix = nullptr;
    size_t ver_major = 0;
    size_t ver_minor = 0;
    size_t ver_patch = 0;
    bool timing_info = false;
    const char *cache_dir = nullptr;
    CliPkg *cur_pkg = allocate<CliPkg>(1);
    BuildMode build_mode = BuildModeDebug;
    ZigList<const char *> test_exec_args = {0};

    if (argc >= 2 && strcmp(argv[1], "build") == 0) {
        const char *zig_exe_path = arg0;
        const char *build_file = "build.zig";
        bool asked_for_help = false;

        init_all_targets();

        ZigList<const char *> args = {0};
        args.append(zig_exe_path);
        args.append(NULL); // placeholder
        args.append(NULL); // placeholder
        for (int i = 2; i < argc; i += 1) {
            if (strcmp(argv[i], "--debug-build-verbose") == 0) {
                verbose = true;
            } else if (strcmp(argv[i], "--help") == 0) {
                asked_for_help = true;
                args.append(argv[i]);
            } else if (i + 1 < argc && strcmp(argv[i], "--build-file") == 0) {
                build_file = argv[i + 1];
                i += 1;
            } else if (i + 1 < argc && strcmp(argv[i], "--cache-dir") == 0) {
                cache_dir = argv[i + 1];
                i += 1;
            } else if (i + 1 < argc && strcmp(argv[i], "--zig-install-prefix") == 0) {
                args.append(argv[i]);
                i += 1;
                zig_install_prefix = argv[i];
                args.append(zig_install_prefix);
            } else {
                args.append(argv[i]);
            }
        }

        Buf *zig_lib_dir_buf = resolve_zig_lib_dir(zig_install_prefix);

        Buf *zig_std_dir = buf_alloc();
        os_path_join(zig_lib_dir_buf, buf_create_from_str("std"), zig_std_dir);

        Buf *special_dir = buf_alloc();
        os_path_join(zig_std_dir, buf_sprintf("special"), special_dir);

        Buf *build_runner_path = buf_alloc();
        os_path_join(special_dir, buf_create_from_str("build_runner.zig"), build_runner_path);


        CodeGen *g = codegen_create(build_runner_path, nullptr, OutTypeExe, BuildModeDebug, zig_lib_dir_buf);
        codegen_set_out_name(g, buf_create_from_str("build"));
        codegen_set_verbose(g, verbose);

        Buf build_file_abs = BUF_INIT;
        os_path_resolve(buf_create_from_str("."), buf_create_from_str(build_file), &build_file_abs);
        Buf build_file_basename = BUF_INIT;
        Buf build_file_dirname = BUF_INIT;
        os_path_split(&build_file_abs, &build_file_dirname, &build_file_basename);

        Buf *full_cache_dir = buf_alloc();
        if (cache_dir == nullptr) {
            os_path_join(&build_file_dirname, buf_create_from_str(default_zig_cache_name), full_cache_dir);
        } else {
            os_path_resolve(buf_create_from_str("."), buf_create_from_str(cache_dir), full_cache_dir);
        }

        Buf *path_to_build_exe = buf_alloc();
        os_path_join(full_cache_dir, buf_create_from_str("build"), path_to_build_exe);
        codegen_set_cache_dir(g, full_cache_dir);

        args.items[1] = buf_ptr(&build_file_dirname);
        args.items[2] = buf_ptr(full_cache_dir);

        bool build_file_exists;
        if ((err = os_file_exists(&build_file_abs, &build_file_exists))) {
            fprintf(stderr, "unable to open '%s': %s\n", buf_ptr(&build_file_abs), err_str(err));
            return 1;
        }
        if (!build_file_exists) {
            if (asked_for_help) {
                // This usage text has to be synchronized with std/special/build_runner.zig
                fprintf(stdout,
                        "Usage: %s build [options]\n"
                        "\n"
                        "General Options:\n"
                        "  --help                 Print this help and exit\n"
                        "  --build-file [file]    Override path to build.zig\n"
                        "  --cache-dir [path]     Override path to cache directory\n"
                        "  --verbose              Print commands before executing them\n"
                        "  --debug-build-verbose  Print verbose debugging information for the build system itself\n"
                        "  --prefix [prefix]      Override default install prefix\n"
                        "\n"
                        "More options become available when the build file is found.\n"
                        "Run this command with no options to generate a build.zig template.\n"
                , zig_exe_path);
                return 0;
            }
            Buf *build_template_path = buf_alloc();
            os_path_join(special_dir, buf_create_from_str("build_file_template.zig"), build_template_path);

            if ((err = os_copy_file(build_template_path, &build_file_abs))) {
                fprintf(stderr, "Unable to write build.zig template: %s\n", err_str(err));
            } else {
                fprintf(stderr, "Wrote build.zig template\n");
            }
            return 1;
        }

        PackageTableEntry *build_pkg = codegen_create_package(g, buf_ptr(&build_file_dirname),
                buf_ptr(&build_file_basename));
        g->root_package->package_table.put(buf_create_from_str("@build"), build_pkg);
        codegen_build(g);
        codegen_link(g, buf_ptr(path_to_build_exe));
        codegen_destroy(g);

        Termination term;
        os_spawn_process(buf_ptr(path_to_build_exe), args, &term);
        if (term.how != TerminationIdClean || term.code != 0) {
            fprintf(stderr, "\nBuild failed. Use the following command to reproduce the failure:\n");
            fprintf(stderr, "%s", buf_ptr(path_to_build_exe));
            for (size_t i = 0; i < args.length; i += 1) {
                fprintf(stderr, " %s", args.at(i));
            }
            fprintf(stderr, "\n");
        }
        return (term.how == TerminationIdClean) ? term.code : -1;
    }

    for (int i = 1; i < argc; i += 1) {
        char *arg = argv[i];

        if (arg[0] == '-') {
            if (strcmp(arg, "--release-fast") == 0) {
                build_mode = BuildModeFastRelease;
            } else if (strcmp(arg, "--release-safe") == 0) {
                build_mode = BuildModeSafeRelease;
            } else if (strcmp(arg, "--strip") == 0) {
                strip = true;
            } else if (strcmp(arg, "--static") == 0) {
                is_static = true;
            } else if (strcmp(arg, "--verbose") == 0) {
                verbose = true;
            } else if (strcmp(arg, "--verbose-link") == 0) {
                verbose_link = true;
            } else if (strcmp(arg, "--verbose-ir") == 0) {
                verbose_ir = true;
            } else if (strcmp(arg, "-mwindows") == 0) {
                mwindows = true;
            } else if (strcmp(arg, "-mconsole") == 0) {
                mconsole = true;
            } else if (strcmp(arg, "-rdynamic") == 0) {
                rdynamic = true;
            } else if (strcmp(arg, "--each-lib-rpath") == 0) {
                each_lib_rpath = true;
            } else if (strcmp(arg, "--enable-timing-info") == 0) {
                timing_info = true;
            } else if (strcmp(arg, "--test-cmd-bin") == 0) {
                test_exec_args.append(nullptr);
            } else if (arg[1] == 'L' && arg[2] != 0) {
                // alias for --library-path
                lib_dirs.append(&arg[2]);
            } else if (strcmp(arg, "--pkg-begin") == 0) {
                if (i + 2 >= argc) {
                    fprintf(stderr, "Expected 2 arguments after --pkg-begin\n");
                    return usage(arg0);
                }
                CliPkg *new_cur_pkg = allocate<CliPkg>(1);
                i += 1;
                new_cur_pkg->name = argv[i];
                i += 1;
                new_cur_pkg->path = argv[i];
                new_cur_pkg->parent = cur_pkg;
                cur_pkg->children.append(new_cur_pkg);
                cur_pkg = new_cur_pkg;
            } else if (strcmp(arg, "--pkg-end") == 0) {
                if (cur_pkg->parent == nullptr) {
                    fprintf(stderr, "Encountered --pkg-end with no matching --pkg-begin\n");
                    return EXIT_FAILURE;
                }
                cur_pkg = cur_pkg->parent;
            } else if (i + 1 >= argc) {
                fprintf(stderr, "Expected another argument after %s\n", arg);
                return usage(arg0);
            } else {
                i += 1;
                if (strcmp(arg, "--output") == 0) {
                    out_file = argv[i];
                } else if (strcmp(arg, "--output-h") == 0) {
                    out_file_h = argv[i];
                } else if (strcmp(arg, "--color") == 0) {
                    if (strcmp(argv[i], "auto") == 0) {
                        color = ErrColorAuto;
                    } else if (strcmp(argv[i], "on") == 0) {
                        color = ErrColorOn;
                    } else if (strcmp(argv[i], "off") == 0) {
                        color = ErrColorOff;
                    } else {
                        fprintf(stderr, "--color options are 'auto', 'on', or 'off'\n");
                        return usage(arg0);
                    }
                } else if (strcmp(arg, "--name") == 0) {
                    out_name = argv[i];
                } else if (strcmp(arg, "--libc-lib-dir") == 0) {
                    libc_lib_dir = argv[i];
                } else if (strcmp(arg, "--libc-static-lib-dir") == 0) {
                    libc_static_lib_dir = argv[i];
                } else if (strcmp(arg, "--libc-include-dir") == 0) {
                    libc_include_dir = argv[i];
                } else if (strcmp(arg, "--msvc-lib-dir") == 0) {
                    msvc_lib_dir = argv[i];
                } else if (strcmp(arg, "--kernel32-lib-dir") == 0) {
                    kernel32_lib_dir = argv[i];
                } else if (strcmp(arg, "--zig-install-prefix") == 0) {
                    zig_install_prefix = argv[i];
                } else if (strcmp(arg, "--dynamic-linker") == 0) {
                    dynamic_linker = argv[i];
                } else if (strcmp(arg, "-isystem") == 0) {
                    clang_argv.append("-isystem");
                    clang_argv.append(argv[i]);
                } else if (strcmp(arg, "-dirafter") == 0) {
                    clang_argv.append("-dirafter");
                    clang_argv.append(argv[i]);
                } else if (strcmp(arg, "-mllvm") == 0) {
                    clang_argv.append("-mllvm");
                    clang_argv.append(argv[i]);

                    llvm_argv.append(argv[i]);
                } else if (strcmp(arg, "--library-path") == 0 || strcmp(arg, "-L") == 0) {
                    lib_dirs.append(argv[i]);
                } else if (strcmp(arg, "--library") == 0) {
                    link_libs.append(argv[i]);
                } else if (strcmp(arg, "--object") == 0) {
                    objects.append(argv[i]);
                } else if (strcmp(arg, "--assembly") == 0) {
                    asm_files.append(argv[i]);
                } else if (strcmp(arg, "--cache-dir") == 0) {
                    cache_dir = argv[i];
                } else if (strcmp(arg, "--target-arch") == 0) {
                    target_arch = argv[i];
                } else if (strcmp(arg, "--target-os") == 0) {
                    target_os = argv[i];
                } else if (strcmp(arg, "--target-environ") == 0) {
                    target_environ = argv[i];
                } else if (strcmp(arg, "-mmacosx-version-min") == 0) {
                    mmacosx_version_min = argv[i];
                } else if (strcmp(arg, "-mios-version-min") == 0) {
                    mios_version_min = argv[i];
                } else if (strcmp(arg, "-framework") == 0) {
                    frameworks.append(argv[i]);
                } else if (strcmp(arg, "--linker-script") == 0) {
                    linker_script = argv[i];
                } else if (strcmp(arg, "-rpath") == 0) {
                    rpath_list.append(argv[i]);
                } else if (strcmp(arg, "--test-filter") == 0) {
                    test_filter = argv[i];
                } else if (strcmp(arg, "--test-name-prefix") == 0) {
                    test_name_prefix = argv[i];
                } else if (strcmp(arg, "--ver-major") == 0) {
                    ver_major = atoi(argv[i]);
                } else if (strcmp(arg, "--ver-minor") == 0) {
                    ver_minor = atoi(argv[i]);
                } else if (strcmp(arg, "--ver-patch") == 0) {
                    ver_patch = atoi(argv[i]);
                } else if (strcmp(arg, "--test-cmd") == 0) {
                    test_exec_args.append(argv[i]);
                } else {
                    fprintf(stderr, "Invalid argument: %s\n", arg);
                    return usage(arg0);
                }
            }
        } else if (cmd == CmdInvalid) {
            if (strcmp(arg, "build-exe") == 0) {
                cmd = CmdBuild;
                out_type = OutTypeExe;
            } else if (strcmp(arg, "build-obj") == 0) {
                cmd = CmdBuild;
                out_type = OutTypeObj;
            } else if (strcmp(arg, "build-lib") == 0) {
                cmd = CmdBuild;
                out_type = OutTypeLib;
            } else if (strcmp(arg, "version") == 0) {
                cmd = CmdVersion;
            } else if (strcmp(arg, "zen") == 0) {
                cmd = CmdZen;
            } else if (strcmp(arg, "parsec") == 0) {
                cmd = CmdParseC;
            } else if (strcmp(arg, "test") == 0) {
                cmd = CmdTest;
                out_type = OutTypeExe;
            } else if (strcmp(arg, "targets") == 0) {
                cmd = CmdTargets;
            } else {
                fprintf(stderr, "Unrecognized command: %s\n", arg);
                return usage(arg0);
            }
        } else {
            switch (cmd) {
                case CmdBuild:
                case CmdParseC:
                case CmdTest:
                    if (!in_file) {
                        in_file = arg;
                    } else {
                        fprintf(stderr, "Unexpected extra parameter: %s\n", arg);
                        return usage(arg0);
                    }
                    break;
                case CmdVersion:
                case CmdZen:
                case CmdTargets:
                    fprintf(stderr, "Unexpected extra parameter: %s\n", arg);
                    return usage(arg0);
                case CmdInvalid:
                    zig_unreachable();
            }
        }
    }

    if (cur_pkg->parent != nullptr) {
        fprintf(stderr, "Unmatched --pkg-begin\n");
        return EXIT_FAILURE;
    }

    init_all_targets();

    ZigTarget alloc_target;
    ZigTarget *target;
    if (!target_arch && !target_os && !target_environ) {
        target = nullptr;
    } else {
        target = &alloc_target;
        get_unknown_target(target);
        if (target_arch) {
            if (parse_target_arch(target_arch, &target->arch)) {
                fprintf(stderr, "invalid --target-arch argument\n");
                return usage(arg0);
            }
        }
        if (target_os) {
            if (parse_target_os(target_os, &target->os)) {
                fprintf(stderr, "invalid --target-os argument\n");
                return usage(arg0);
            }
        }
        if (target_environ) {
            if (parse_target_environ(target_environ, &target->env_type)) {
                fprintf(stderr, "invalid --target-environ argument\n");
                return usage(arg0);
            }
        }
    }


    switch (cmd) {
    case CmdBuild:
    case CmdParseC:
    case CmdTest:
        {
            if (cmd == CmdBuild && !in_file && objects.length == 0 && asm_files.length == 0) {
                fprintf(stderr, "Expected source file argument or at least one --object or --assembly argument.\n");
                return usage(arg0);
            } else if ((cmd == CmdParseC || cmd == CmdTest) && !in_file) {
                fprintf(stderr, "Expected source file argument.\n");
                return usage(arg0);
            } else if (cmd == CmdBuild && out_type == OutTypeObj && objects.length != 0) {
                fprintf(stderr, "When building an object file, --object arguments are invalid.\n");
                return usage(arg0);
            }

            assert(cmd != CmdBuild || out_type != OutTypeUnknown);

            bool need_name = (cmd == CmdBuild || cmd == CmdParseC);

            Buf *in_file_buf = nullptr;

            Buf *buf_out_name = (cmd == CmdTest) ? buf_create_from_str("test") :
                (out_name == nullptr) ? nullptr : buf_create_from_str(out_name);

            if (in_file) {
                in_file_buf = buf_create_from_str(in_file);

                if (need_name && buf_out_name == nullptr) {
                    Buf basename = BUF_INIT;
                    os_path_split(in_file_buf, nullptr, &basename);
                    buf_out_name = buf_alloc();
                    os_path_extname(&basename, buf_out_name, nullptr);
                }
            }

            if (need_name && buf_out_name == nullptr) {
                fprintf(stderr, "--name [name] not provided and unable to infer\n\n");
                return usage(arg0);
            }

            Buf *zig_root_source_file = (cmd == CmdParseC) ? nullptr : in_file_buf;

            Buf *full_cache_dir = buf_alloc();
            os_path_resolve(buf_create_from_str("."),
                    buf_create_from_str((cache_dir == nullptr) ? default_zig_cache_name : cache_dir),
                    full_cache_dir);

            Buf *zig_lib_dir_buf = resolve_zig_lib_dir(zig_install_prefix);

            CodeGen *g = codegen_create(zig_root_source_file, target, out_type, build_mode, zig_lib_dir_buf);
            codegen_set_out_name(g, buf_out_name);
            codegen_set_lib_version(g, ver_major, ver_minor, ver_patch);
            codegen_set_is_test(g, cmd == CmdTest);
            codegen_set_linker_script(g, linker_script);
            codegen_set_cache_dir(g, full_cache_dir);
            if (each_lib_rpath)
                codegen_set_each_lib_rpath(g, each_lib_rpath);

            codegen_set_clang_argv(g, clang_argv.items, clang_argv.length);
            codegen_set_llvm_argv(g, llvm_argv.items, llvm_argv.length);
            codegen_set_strip(g, strip);
            codegen_set_is_static(g, is_static);
            if (libc_lib_dir)
                codegen_set_libc_lib_dir(g, buf_create_from_str(libc_lib_dir));
            if (libc_static_lib_dir)
                codegen_set_libc_static_lib_dir(g, buf_create_from_str(libc_static_lib_dir));
            if (libc_include_dir)
                codegen_set_libc_include_dir(g, buf_create_from_str(libc_include_dir));
            if (msvc_lib_dir)
                codegen_set_msvc_lib_dir(g, buf_create_from_str(msvc_lib_dir));
            if (kernel32_lib_dir)
                codegen_set_kernel32_lib_dir(g, buf_create_from_str(kernel32_lib_dir));
            if (dynamic_linker)
                codegen_set_dynamic_linker(g, buf_create_from_str(dynamic_linker));
            codegen_set_verbose(g, verbose);
            g->verbose_link = verbose_link;
            g->verbose_ir = verbose_ir;
            codegen_set_errmsg_color(g, color);

            for (size_t i = 0; i < lib_dirs.length; i += 1) {
                codegen_add_lib_dir(g, lib_dirs.at(i));
            }
            for (size_t i = 0; i < link_libs.length; i += 1) {
                LinkLib *link_lib = codegen_add_link_lib(g, buf_create_from_str(link_libs.at(i)));
                link_lib->provided_explicitly = true;
            }
            for (size_t i = 0; i < frameworks.length; i += 1) {
                codegen_add_framework(g, frameworks.at(i));
            }
            for (size_t i = 0; i < rpath_list.length; i += 1) {
                codegen_add_rpath(g, rpath_list.at(i));
            }

            codegen_set_windows_subsystem(g, mwindows, mconsole);
            codegen_set_rdynamic(g, rdynamic);
            if (mmacosx_version_min && mios_version_min) {
                fprintf(stderr, "-mmacosx-version-min and -mios-version-min options not allowed together\n");
                return EXIT_FAILURE;
            }

            if (mmacosx_version_min) {
                codegen_set_mmacosx_version_min(g, buf_create_from_str(mmacosx_version_min));
            }

            if (mios_version_min) {
                codegen_set_mios_version_min(g, buf_create_from_str(mios_version_min));
            }

            if (test_filter) {
                codegen_set_test_filter(g, buf_create_from_str(test_filter));
            }

            if (test_name_prefix) {
                codegen_set_test_name_prefix(g, buf_create_from_str(test_name_prefix));
            }

            if (out_file_h)
                codegen_set_output_h_path(g, buf_create_from_str(out_file_h));


            add_package(g, cur_pkg, g->root_package);

            if (cmd == CmdBuild) {
                for (size_t i = 0; i < objects.length; i += 1) {
                    codegen_add_object(g, buf_create_from_str(objects.at(i)));
                }
                for (size_t i = 0; i < asm_files.length; i += 1) {
                    codegen_add_assembly(g, buf_create_from_str(asm_files.at(i)));
                }
                codegen_build(g);
                codegen_link(g, out_file);
                if (timing_info)
                    codegen_print_timing_report(g, stdout);
                return EXIT_SUCCESS;
            } else if (cmd == CmdParseC) {
                codegen_parsec(g, in_file_buf);
                ast_render(g, stdout, g->root_import->root, 4);
                if (timing_info)
                    codegen_print_timing_report(g, stdout);
                return EXIT_SUCCESS;
            } else if (cmd == CmdTest) {
                ZigTarget native;
                get_native_target(&native);

                ZigTarget *non_null_target = target ? target : &native;

                Buf *test_exe_name = buf_sprintf("." OS_SEP "test%s", target_exe_file_ext(non_null_target));

                for (size_t i = 0; i < test_exec_args.length; i += 1) {
                    if (test_exec_args.items[i] == nullptr) {
                        test_exec_args.items[i] = buf_ptr(test_exe_name);
                    }
                }

                codegen_build(g);
                codegen_link(g, buf_ptr(test_exe_name));

                if (!target_can_exec(&native, target)) {
                    fprintf(stderr, "Created %s but skipping execution because it is non-native.\n",
                            buf_ptr(test_exe_name));
                    return 0;
                }

                Termination term;
                if (test_exec_args.length > 0) {
                    ZigList<const char *> rest_args = {0};
                    for (size_t i = 1; i < test_exec_args.length; i += 1) {
                        rest_args.append(test_exec_args.at(i));
                    }
                    os_spawn_process(test_exec_args.items[0], rest_args, &term);
                } else {
                    ZigList<const char *> no_args = {0};
                    os_spawn_process(buf_ptr(test_exe_name), no_args, &term);
                }

                if (term.how != TerminationIdClean || term.code != 0) {
                    fprintf(stderr, "\nTests failed. Use the following command to reproduce the failure:\n");
                    fprintf(stderr, "%s\n", buf_ptr(test_exe_name));
                } else if (timing_info) {
                    codegen_print_timing_report(g, stdout);
                }
                return (term.how == TerminationIdClean) ? term.code : -1;
            } else {
                zig_unreachable();
            }
        }
    case CmdVersion:
        printf("%s\n", ZIG_VERSION_STRING);
        return EXIT_SUCCESS;
    case CmdZen:
        printf("%s\n", ZIG_ZEN);
        return EXIT_SUCCESS;
    case CmdTargets:
        return print_target_list(stdout);
    case CmdInvalid:
        return usage(arg0);
    }
}
