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

// This file is the entry point for zig0, which is *only* used to build
// stage2, the self-hosted compiler, into an object file, which is then
// linked by the same build system (cmake) that linked this binary.

#include "stage1.h"
#include "heap.hpp"
#include "stage2.h"
#include "target.hpp"
#include "error.hpp"
#include "util.hpp"
#include "buffer.hpp"
#include "os.hpp"

#ifndef ZIG_VERSION_STRING
#include "config.h"
#endif

#include <stdio.h>
#include <string.h>

static int print_error_usage(const char *arg0) {
    fprintf(stderr, "See `%s --help` for detailed usage information\n", arg0);
    return EXIT_FAILURE;
}

static int print_full_usage(const char *arg0, FILE *file, int return_code) {
    fprintf(file,
        "Usage: %s [options]            builds an object file\n"
        "\n"
        "Options:\n"
        "  --color [auto|off|on]        enable or disable colored error messages\n"
        "  --name [name]                override output name\n"
        "  -femit-bin=[path]            Output machine code\n"
        "  -fcompiler-rt                Always include compiler-rt symbols in output\n"
        "  --pkg-begin [name] [path]    make pkg available to import and push current pkg\n"
        "  --pkg-end                    pop current pkg\n"
        "  -ODebug                      build with optimizations off and safety on\n"
        "  -OReleaseFast                build with optimizations on and safety off\n"
        "  -OReleaseSafe                build with optimizations on and safety on\n"
        "  -OReleaseSmall               build with size optimizations on and safety off\n"
        "  -fsingle-threaded            source may assume it is only used single-threaded\n"
        "  -dynamic                     create a shared library (.so; .dll; .dylib)\n"
        "  --strip                      exclude debug symbols\n"
        "  -target [name]               <arch>-<os>-<abi> see the targets command\n"
        "  -mcpu [cpu]                  specify target CPU and feature set\n"
        "  --verbose-ir                 enable compiler debug output for Zig IR\n"
        "  --verbose-llvm-ir            enable compiler debug output for LLVM IR\n"
        "  --verbose-cimport            enable compiler debug output for C imports\n"
        "  --verbose-llvm-cpu-features  enable compiler debug output for LLVM CPU features\n"
        "\n"
    , arg0);
    return return_code;
}

static Os get_zig_os_type(ZigLLVM_OSType os_type) {
    switch (os_type) {
        case ZigLLVM_UnknownOS:
            return OsFreestanding;
        case ZigLLVM_Ananas:
            return OsAnanas;
        case ZigLLVM_CloudABI:
            return OsCloudABI;
        case ZigLLVM_DragonFly:
            return OsDragonFly;
        case ZigLLVM_FreeBSD:
            return OsFreeBSD;
        case ZigLLVM_Fuchsia:
            return OsFuchsia;
        case ZigLLVM_IOS:
            return OsIOS;
        case ZigLLVM_KFreeBSD:
            return OsKFreeBSD;
        case ZigLLVM_Linux:
            return OsLinux;
        case ZigLLVM_Lv2:
            return OsLv2;
        case ZigLLVM_Darwin:
        case ZigLLVM_MacOSX:
            return OsMacOSX;
        case ZigLLVM_NetBSD:
            return OsNetBSD;
        case ZigLLVM_OpenBSD:
            return OsOpenBSD;
        case ZigLLVM_Solaris:
            return OsSolaris;
        case ZigLLVM_Win32:
            return OsWindows;
        case ZigLLVM_ZOS:
            return OsZOS;
        case ZigLLVM_Haiku:
            return OsHaiku;
        case ZigLLVM_Minix:
            return OsMinix;
        case ZigLLVM_RTEMS:
            return OsRTEMS;
        case ZigLLVM_NaCl:
            return OsNaCl;
        case ZigLLVM_AIX:
            return OsAIX;
        case ZigLLVM_CUDA:
            return OsCUDA;
        case ZigLLVM_NVCL:
            return OsNVCL;
        case ZigLLVM_AMDHSA:
            return OsAMDHSA;
        case ZigLLVM_PS4:
            return OsPS4;
        case ZigLLVM_ELFIAMCU:
            return OsELFIAMCU;
        case ZigLLVM_TvOS:
            return OsTvOS;
        case ZigLLVM_WatchOS:
            return OsWatchOS;
        case ZigLLVM_Mesa3D:
            return OsMesa3D;
        case ZigLLVM_Contiki:
            return OsContiki;
        case ZigLLVM_AMDPAL:
            return OsAMDPAL;
        case ZigLLVM_HermitCore:
            return OsHermitCore;
        case ZigLLVM_Hurd:
            return OsHurd;
        case ZigLLVM_WASI:
            return OsWASI;
        case ZigLLVM_Emscripten:
            return OsEmscripten;
    }
    zig_unreachable();
}

static void get_native_target(ZigTarget *target) {
    // first zero initialize
    *target = {};

    ZigLLVM_OSType os_type;
    ZigLLVM_ObjectFormatType oformat; // ignored; based on arch/os
    ZigLLVM_VendorType trash;
    ZigLLVMGetNativeTarget(
            &target->arch,
            &trash,
            &os_type,
            &target->abi,
            &oformat);
    target->os = get_zig_os_type(os_type);
    target->is_native_os = true;
    target->is_native_cpu = true;
    if (target->abi == ZigLLVM_UnknownEnvironment) {
        target->abi = target_default_abi(target->arch, target->os);
    }
}

static const char* get_baseline_llvm_cpu_name(ZigLLVM_ArchType arch) {
    return "";
}

static const char* get_baseline_llvm_cpu_features(ZigLLVM_ArchType arch) {
    switch (arch) {
        case ZigLLVM_riscv64: return "+a,+c,+d,+m";
        default: return "";
    }
}

static Error target_parse_triple(struct ZigTarget *target, const char *zig_triple, const char *mcpu,
        const char *dynamic_linker)
{
    Error err;

    if (zig_triple != nullptr && strcmp(zig_triple, "native") == 0) {
        zig_triple = nullptr;
    }

    if (zig_triple == nullptr) {
        get_native_target(target);

        if (mcpu == nullptr) {
            target->llvm_cpu_name = ZigLLVMGetHostCPUName();
            target->llvm_cpu_features = ZigLLVMGetNativeFeatures();
        } else if (strcmp(mcpu, "baseline") == 0) {
            target->is_native_os = false;
            target->is_native_cpu = false;
            target->llvm_cpu_name = get_baseline_llvm_cpu_name(target->arch);
            target->llvm_cpu_features = get_baseline_llvm_cpu_features(target->arch);
        } else {
            const char *msg = "stage0 can't handle CPU/features in the target";
            stage2_panic(msg, strlen(msg));
        }
    } else {
        // first initialize all to zero
        *target = {};

        SplitIterator it = memSplit(str(zig_triple), str("-"));

        Optional<Slice<uint8_t>> opt_archsub = SplitIterator_next(&it);
        Optional<Slice<uint8_t>> opt_os = SplitIterator_next(&it);
        Optional<Slice<uint8_t>> opt_abi = SplitIterator_next(&it);

        if (!opt_archsub.is_some)
            return ErrorMissingArchitecture;

        if ((err = target_parse_arch(&target->arch, (char*)opt_archsub.value.ptr, opt_archsub.value.len))) {
            return err;
        }

        if (!opt_os.is_some)
            return ErrorMissingOperatingSystem;

        if ((err = target_parse_os(&target->os, (char*)opt_os.value.ptr, opt_os.value.len))) {
            return err;
        }

        if (opt_abi.is_some) {
            if ((err = target_parse_abi(&target->abi, (char*)opt_abi.value.ptr, opt_abi.value.len))) {
                return err;
            }
        } else {
            target->abi = target_default_abi(target->arch, target->os);
        }

        if (mcpu != nullptr && strcmp(mcpu, "baseline") != 0) {
            const char *msg = "stage0 can't handle CPU/features in the target";
            stage2_panic(msg, strlen(msg));
        }

        target->llvm_cpu_name = get_baseline_llvm_cpu_name(target->arch);
        target->llvm_cpu_features = get_baseline_llvm_cpu_features(target->arch);
    }

    return ErrorNone;
}


static bool str_starts_with(const char *s1, const char *s2) {
    size_t s2_len = strlen(s2);
    if (strlen(s1) < s2_len) {
        return false;
    }
    return memcmp(s1, s2, s2_len) == 0;
}

int main_exit(Stage2ProgressNode *root_progress_node, int exit_code) {
    if (root_progress_node != nullptr) {
        stage2_progress_end(root_progress_node);
    }
    return exit_code;
}

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

    char *arg0 = argv[0];
    Error err;

    const char *in_file = nullptr;
    const char *emit_bin_path = nullptr;
    bool strip = false;
    const char *out_name = nullptr;
    bool verbose_ir = false;
    bool verbose_llvm_ir = false;
    bool verbose_cimport = false;
    bool verbose_llvm_cpu_features = false;
    ErrColor color = ErrColorAuto;
    const char *dynamic_linker = nullptr;
    bool link_libc = false;
    bool link_libcpp = false;
    const char *target_string = nullptr;
    ZigStage1Pkg *cur_pkg = heap::c_allocator.create<ZigStage1Pkg>();
    BuildMode optimize_mode = BuildModeDebug;
    TargetSubsystem subsystem = TargetSubsystemAuto;
    const char *override_lib_dir = nullptr;
    const char *mcpu = nullptr;
    bool single_threaded = false;
    bool is_test_build = false;
    bool include_compiler_rt = false;

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

        if (arg[0] == '-') {
            if (strcmp(arg, "--") == 0) {
                fprintf(stderr, "Unexpected end-of-parameter mark: %s\n", arg);
            } else if (strcmp(arg, "--test") == 0) {
                is_test_build = true;
            } else if (strcmp(arg, "-ODebug") == 0) {
                optimize_mode = BuildModeDebug;
            } else if (strcmp(arg, "-OReleaseFast") == 0) {
                optimize_mode = BuildModeFastRelease;
            } else if (strcmp(arg, "-OReleaseSafe") == 0) {
                optimize_mode = BuildModeSafeRelease;
            } else if (strcmp(arg, "-OReleaseSmall") == 0) {
                optimize_mode = BuildModeSmallRelease;
            } else if (strcmp(arg, "-fsingle-threaded") == 0) {
                single_threaded = true;
            } else if (strcmp(arg, "--help") == 0) {
                return print_full_usage(arg0, stdout, EXIT_SUCCESS);
            } else if (strcmp(arg, "--strip") == 0) {
                strip = true;
            } else if (strcmp(arg, "--verbose-ir") == 0) {
                verbose_ir = true;
            } else if (strcmp(arg, "--verbose-llvm-ir") == 0) {
                verbose_llvm_ir = true;
            } else if (strcmp(arg, "--verbose-cimport") == 0) {
                verbose_cimport = true;
            } else if (strcmp(arg, "--verbose-llvm-cpu-features") == 0) {
                verbose_llvm_cpu_features = true;
            } else if (arg[1] == 'l' && arg[2] != 0) {
                // alias for --library
                const char *l = &arg[2];
                if (strcmp(l, "c") == 0) {
                    link_libc = true;
                } else if (strcmp(l, "c++") == 0 || strcmp(l, "stdc++") == 0) {
                    link_libcpp = true;
                }
            } else if (strcmp(arg, "--pkg-begin") == 0) {
                if (i + 2 >= argc) {
                    fprintf(stderr, "Expected 2 arguments after --pkg-begin\n");
                    return print_error_usage(arg0);
                }
                ZigStage1Pkg *new_cur_pkg = heap::c_allocator.create<ZigStage1Pkg>();
                i += 1;
                new_cur_pkg->name_ptr = argv[i];
                new_cur_pkg->name_len = strlen(argv[i]);
                i += 1;
                new_cur_pkg->path_ptr = argv[i];
                new_cur_pkg->path_len = strlen(argv[i]);
                new_cur_pkg->parent = cur_pkg;
                cur_pkg->children_ptr = heap::c_allocator.reallocate<ZigStage1Pkg *>(cur_pkg->children_ptr,
                        cur_pkg->children_len, cur_pkg->children_len + 1);
                cur_pkg->children_ptr[cur_pkg->children_len] = new_cur_pkg;
                cur_pkg->children_len += 1;

                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 (str_starts_with(arg, "-mcpu=")) {
                mcpu = arg + strlen("-mcpu=");
            } else if (str_starts_with(arg, "-femit-bin=")) {
                emit_bin_path = arg + strlen("-femit-bin=");
            } else if (strcmp(arg, "-fcompiler-rt") == 0) {
                include_compiler_rt = true;
            } else if (i + 1 >= argc) {
                fprintf(stderr, "Expected another argument after %s\n", arg);
                return print_error_usage(arg0);
            } else {
                i += 1;
                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 print_error_usage(arg0);
                    }
                } else if (strcmp(arg, "--name") == 0) {
                    out_name = argv[i];
                } else if (strcmp(arg, "--dynamic-linker") == 0) {
                    dynamic_linker = argv[i];
                } else if (strcmp(arg, "--zig-lib-dir") == 0) {
                    override_lib_dir = argv[i];
                } else if (strcmp(arg, "--library") == 0 || strcmp(arg, "-l") == 0) {
                    if (strcmp(argv[i], "c") == 0) {
                        link_libc = true;
                    } else if (strcmp(argv[i], "c++") == 0 || strcmp(argv[i], "stdc++") == 0) {
                        link_libcpp = true;
                    }
                } else if (strcmp(arg, "-target") == 0) {
                    target_string = argv[i];
                } else if (strcmp(arg, "--subsystem") == 0) {
                    if (strcmp(argv[i], "console") == 0) {
                        subsystem = TargetSubsystemConsole;
                    } else if (strcmp(argv[i], "windows") == 0) {
                        subsystem = TargetSubsystemWindows;
                    } else if (strcmp(argv[i], "posix") == 0) {
                        subsystem = TargetSubsystemPosix;
                    } else if (strcmp(argv[i], "native") == 0) {
                        subsystem = TargetSubsystemNative;
                    } else if (strcmp(argv[i], "efi_application") == 0) {
                        subsystem = TargetSubsystemEfiApplication;
                    } else if (strcmp(argv[i], "efi_boot_service_driver") == 0) {
                        subsystem = TargetSubsystemEfiBootServiceDriver;
                    } else if (strcmp(argv[i], "efi_rom") == 0) {
                        subsystem = TargetSubsystemEfiRom;
                    } else if (strcmp(argv[i], "efi_runtime_driver") == 0) {
                        subsystem = TargetSubsystemEfiRuntimeDriver;
                    } else {
                        fprintf(stderr, "invalid: --subsystem %s\n"
                                "Options are:\n"
                                "  console\n"
                                "  windows\n"
                                "  posix\n"
                                "  native\n"
                                "  efi_application\n"
                                "  efi_boot_service_driver\n"
                                "  efi_rom\n"
                                "  efi_runtime_driver\n"
                            , argv[i]);
                        return EXIT_FAILURE;
                    }
                } else if (strcmp(arg, "-mcpu") == 0) {
                    mcpu = argv[i];
                } else {
                    fprintf(stderr, "Invalid argument: %s\n", arg);
                    return print_error_usage(arg0);
                }
            }
        } else if (!in_file) {
            in_file = arg;
        } else {
            fprintf(stderr, "Unexpected extra parameter: %s\n", arg);
            return print_error_usage(arg0);
        }
    }

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

    Stage2Progress *progress = stage2_progress_create();
    Stage2ProgressNode *root_progress_node = stage2_progress_start_root(progress, "", 0, 0);
    if (color == ErrColorOff) stage2_progress_disable_tty(progress);

    ZigTarget target;
    if ((err = target_parse_triple(&target, target_string, mcpu, dynamic_linker))) {
        fprintf(stderr, "invalid target: %s\n", err_str(err));
        return print_error_usage(arg0);
    }

    if (in_file == nullptr) {
        fprintf(stderr, "missing zig file\n");
        return print_error_usage(arg0);
    }

    if (out_name == nullptr) {
        fprintf(stderr, "missing --name\n");
        return print_error_usage(arg0);
    }

    if (override_lib_dir == nullptr) {
        fprintf(stderr, "missing --zig-lib-dir\n");
        return print_error_usage(arg0);
    }

    if (emit_bin_path == nullptr) {
        fprintf(stderr, "missing -femit-bin=\n");
        return print_error_usage(arg0);
    }

    ZigStage1 *stage1 = zig_stage1_create(optimize_mode,
        nullptr, 0,
        in_file, strlen(in_file),
        override_lib_dir, strlen(override_lib_dir),
        &target, is_test_build);

    stage1->main_progress_node = root_progress_node;
    stage1->root_name_ptr = out_name;
    stage1->root_name_len = strlen(out_name);
    stage1->strip = strip;
    stage1->verbose_ir = verbose_ir;
    stage1->verbose_llvm_ir = verbose_llvm_ir;
    stage1->verbose_cimport = verbose_cimport;
    stage1->verbose_llvm_cpu_features = verbose_llvm_cpu_features;
    stage1->emit_o_ptr = emit_bin_path;
    stage1->emit_o_len = strlen(emit_bin_path);
    stage1->main_pkg = cur_pkg;
    stage1->err_color = color;
    stage1->link_libc = link_libc;
    stage1->link_libcpp = link_libcpp;
    stage1->subsystem = subsystem;
    stage1->pic = true;
    stage1->is_single_threaded = single_threaded;
    stage1->include_compiler_rt = include_compiler_rt;

    zig_stage1_build_object(stage1);

    zig_stage1_destroy(stage1);

    return main_exit(root_progress_node, EXIT_SUCCESS);
}

void stage2_panic(const char *ptr, size_t len) {
    fwrite(ptr, 1, len, stderr);
    fprintf(stderr, "\n");
    fflush(stderr);
    abort();
}

struct Stage2Progress {
    int trash;
};

struct Stage2ProgressNode {
    int trash;
};

Stage2Progress *stage2_progress_create(void) {
    return nullptr;
}

void stage2_progress_destroy(Stage2Progress *progress) {}

Stage2ProgressNode *stage2_progress_start_root(Stage2Progress *progress,
        const char *name_ptr, size_t name_len, size_t estimated_total_items)
{
    return nullptr;
}
Stage2ProgressNode *stage2_progress_start(Stage2ProgressNode *node,
        const char *name_ptr, size_t name_len, size_t estimated_total_items)
{
    return nullptr;
}
void stage2_progress_end(Stage2ProgressNode *node) {}
void stage2_progress_complete_one(Stage2ProgressNode *node) {}
void stage2_progress_disable_tty(Stage2Progress *progress) {}
void stage2_progress_update_node(Stage2ProgressNode *node, size_t completed_count, size_t estimated_total_items){}

const char *stage2_fetch_file(struct ZigStage1 *stage1, const char *path_ptr, size_t path_len,
        size_t *result_len)
{
    Error err;
    Buf contents_buf = BUF_INIT;
    Buf path_buf = BUF_INIT;

    buf_init_from_mem(&path_buf, path_ptr, path_len);
    if ((err = os_fetch_file_path(&path_buf, &contents_buf))) {
        return nullptr;
    }
    *result_len = buf_len(&contents_buf);
    return buf_ptr(&contents_buf);
}

Error stage2_cimport(struct ZigStage1 *stage1, const char *c_src_ptr, size_t c_src_len,
        const char **out_zig_path_ptr, size_t *out_zig_path_len,
        struct Stage2ErrorMsg **out_errors_ptr, size_t *out_errors_len)
{
    const char *msg = "stage0 called stage2_cimport";
    stage2_panic(msg, strlen(msg));
}

const char *stage2_add_link_lib(struct ZigStage1 *stage1,
        const char *lib_name_ptr, size_t lib_name_len,
        const char *symbol_name_ptr, size_t symbol_name_len)
{
    return nullptr;
}

const char *stage2_version_string(void) {
    return ZIG_VERSION_STRING;
}

struct Stage2SemVer stage2_version(void) {
    return {ZIG_VERSION_MAJOR, ZIG_VERSION_MINOR, ZIG_VERSION_PATCH};
}

Error stage2_append_symbol(struct ZigStage1 *stage1, const char *name_ptr, size_t name_len)
{
    return ErrorNone;
}
