#import "Basic";
#import "Compiler";
#import "File";
#import "File_Utilities";
#import "String";
#import "System";
#import "Process";

OUTPUT_EXECUTABLE_NAME_DESKTOP :: "mexplore";

SOURCE_ENTRY_POINT :: "src/main.jai";

DESKTOP_OS_BUILD_OUTPUT_PATH :: "bin";

#run Build_Project();

Build_Project :: () {
    // Get start time:
    // Compile prerequisites:
    set_build_options_dc(.{do_output=false});
    args := get_build_options().compile_time_command_line;

    // Simplify targeting / command line interface?
    // [For now] assume x64 for Windows and Linux
    // Android defaults to ARM64, can be overriden with `x64`. `x64` is ignored on all other OS'
    target_windows  = array_find(args, "windows");
    target_linux    = array_find(args, "linux");
    target_macos    = array_find(args, "macos");
    target_x64      = array_find(args, "x64");
    target_optimize = array_find(args, "release");
    
    // NOTE(Charles): Proably makes sense to default os/cpu to OS/CPU then arguments override that.
    if target_windows {
        cpu_target = .X64;
        os_target = .WINDOWS;
        assert(os_target == OS);
    } else if target_linux {
        cpu_target = .X64;
        os_target = .LINUX;
        assert(os_target == OS);
    } else if target_macos {
        cpu_target = .ARM64;
        os_target = .MACOS;
        assert(os_target == OS);
    } else {
        assert(false, "Not a valid build configuration.");
    }

    set_working_directory(#filepath);

    compile_native_code(); // Also copies assets and libraries :ThisAintCompilingNativeCode!
    print("\n");
}

compile_native_code :: () {
    VERSION :: "0.1";
    JAI_VERSION := trim(compiler_get_version_info(null));
    current_time := to_calendar(current_time_consensus(), .LOCAL);
    RELEASE_DATE := calendar_to_string(current_time);
    result, branch_name := run_command("git", "branch", "--show-current", capture_and_return_output = true);
    GIT_BRANCH := trim(branch_name);
    result=, revision := run_command("git", "rev-parse", "HEAD", capture_and_return_output = true);
    GIT_REVISION := trim(revision);

    log("Compiling for % %", os_target, cpu_target);

    start_time := GetUnixTimestamp();
    defer log("Native code compilation time: %", GetUnixTimestamp()-start_time); // NOTE(Charles): The compiler already outputs this for us?
    build_output_directory: string;
    sdk_lib_path: string;
    android_main_directory: string;
    // #if OS != .MACOS {
    // target_triple, target_triple_with_sdk := get_android_target_triple(cpu_target);
    // if os_target == .ANDROID {
    //     ndk := get_ndk_paths();
    //     sdk_lib_path = tprint("%/usr/lib/%", ndk.sysroot, target_triple);

    //     // Create the library directory
    //     android_main_directory = tprint("%/app/src/main", ANDROID_PROJECT_PATH);
    //     assert(file_exists(android_main_directory));
    //     build_output_directory = tprint("%/jniLibs/%", android_main_directory, get_android_project_jni_architecture_name(cpu_target));
    //     make_directory_if_it_does_not_exist(build_output_directory, recursive = true);
    // }
    // }

    w := compiler_create_workspace("Target workspace");
    options := get_build_options(w);
    copy_commonly_propagated_fields(get_build_options(), *options);
    options.cpu_target = cpu_target;
    options.os_target  = os_target;

    if os_target == .WINDOWS || os_target == .LINUX || OS == .MACOS {
        if target_optimize {
            set_optimization(*options, .VERY_OPTIMIZED, preserve_debug_info=false);
            options.llvm_options.enable_split_modules = false;
            options.array_bounds_check = .OFF;
            options.null_pointer_check = .OFF;
            options.arithmetic_overflow_check = .OFF;
            options.cast_bounds_check = .OFF;
            options.output_executable_name  = OUTPUT_EXECUTABLE_NAME_DESKTOP;
        } else {
            options.output_executable_name  = tprint("%-debug", OUTPUT_EXECUTABLE_NAME_DESKTOP);
        }
        options.output_path             = DESKTOP_OS_BUILD_OUTPUT_PATH;
        make_directory_if_it_does_not_exist(options.output_path, recursive=true);
    }

    import_path: [..] string;
    array_add(*import_path, "modules");
    array_add(*import_path, ..options.import_path);
    options.import_path = import_path;

    if cpu_target == .ARM64 {
        options.backend = .LLVM;
        // Disable +lse for older ARM64 devices. 
        // options.llvm_options.target_system_features = "+lse";
    }

    set_build_options(options, w);

    compiler_begin_intercept(w);
    
    add_build_file(SOURCE_ENTRY_POINT, w);
    
    build_constants := tprint(#string STRING
            NAME         :: "%";
            VERSION      :: "%";
            JAI_VERSION  :: "%";
            RELEASE_DATE :: "%";
            GIT_BRANCH   :: "%";
            GIT_REVISION :: "%";
            DEBUG        :: %;
    STRING,
        OUTPUT_EXECUTABLE_NAME_DESKTOP, VERSION, JAI_VERSION, RELEASE_DATE, GIT_BRANCH, GIT_REVISION,
        ifx target_optimize     then "false" else "true",
    );

    add_build_string(build_constants, w);

    while true {
        message := compiler_wait_for_message();
        if message.kind == {
            case .PHASE;
                phase_message := cast(*Message_Phase) message;
                if phase_message.phase == .READY_FOR_CUSTOM_LINK_COMMAND {
                    // #if OS != .MACOS {
                    // run_android_link_command(phase_message, options, extra_args = .[
                    //     tprint("-L%", sdk_lib_path), "-lc++"] // :AndroidStbImageThreadLocals
                    // );
                    // }
                }

            case .ERROR;
                print("\n"); // Seems to be some issue with exit() not flushing output, we miss errors!
                exit(1);

            case .COMPLETE; break;
        }
    }
    compiler_end_intercept(w);

    // :ThisAintCompilingNativeCode!

    ASSETS :: string.[
        // "extras/images/image_test.jpg",
        // "extras/fonts/JetBrainsMono-Regular.ttf",
        // "extras/fonts/RobotoMono-Regular.ttf",
    ];

    if os_target == .WINDOWS {
        WINDOWS_LIBS :: string.[];
        WINDOWS_LIBS_DEBUG :: string.[];
        target_output_directory := tprint("%/bin", #filepath);
        make_directory_if_it_does_not_exist(options.output_path, recursive=true);
        for ASSETS {
            if !copy_file(it, tprint("%/%", target_output_directory, path_filename(it))) {
                compiler_report(tprint("Failed to copy file % to android project assets", it));
            }
        }

        target_lib_array := ifx target_optimize then WINDOWS_LIBS else WINDOWS_LIBS_DEBUG;
        for target_lib_array {
            if !copy_file(it, tprint("%/%", target_output_directory, path_filename(it))) {
                compiler_report(tprint("Failed to copy file % to android project assets", it));
            }
        }
    }

    if os_target == .LINUX {
        LINUX_LIBS :: string.[];
        target_output_directory := tprint("%/bin", #filepath);
        make_directory_if_it_does_not_exist(options.output_path, recursive=true);
        for ASSETS {
            if !copy_file(it, tprint("%/%", target_output_directory, path_filename(it))) {
                compiler_report(tprint("Failed to copy file % to android project assets", it));
            }
        }
        for LINUX_LIBS {
            if !copy_file(it, tprint("%/%", target_output_directory, path_filename(it))) {
                compiler_report(tprint("Failed to copy file % to android project assets", it));
            }
        }
    }

    if os_target == .ANDROID {
        resources_output_directory := tprint("%/assets", android_main_directory);
        make_directory_if_it_does_not_exist(resources_output_directory, recursive = true);
        for ASSETS {
            if !copy_file(it, tprint("%/%", resources_output_directory, path_filename(it))) {
                compiler_report(tprint("Failed to copy file % to android project assets", it));
            }
        }

        // #if OS != .MACOS {
        // // Package libc++ :AndroidStbImageThreadLocals
        // success := copy_android_libcpp(cpu_target, options.output_path);
        // if !success {
        //     compiler_report(tprint("Could not copy Android libc++ to %", options.output_path));
        // }
        // }
    }
}

// #if OS != .MACOS {
// generate_android_project :: () {
//     GENERATE_ANDROID_FILES :: false;
//     if file_exists(ANDROID_PROJECT_PATH) && !GENERATE_ANDROID_FILES {
//         log("Project folder at %", ANDROID_PROJECT_PATH);
//         return;
//     }
//     log("Generating new project folder at %", ANDROID_PROJECT_PATH);
//     start_time := GetUnixTimestamp();
//     config: Android_Project_Config;
//     config.project_path = ANDROID_PROJECT_PATH;
//     config.app_name = ANDROID_APP_NAME;
//     config.app_id = ANDROID_APP_ID;
//     config.android_gradle_plugin_version = "8.2.2";
//     config.gradle_version = "8.6";
//     config.lib_name = OUTPUT_EXECUTABLE_NAME_ANDROID;
//     // Hmmm. I wanna do portrait for phones and landscape for tablets, or allow the user to choose via device orientation.
//     // For now just roll with this.
//     config.screen_orientation = .PORTRAIT;
//     // config.screen_orientation = .LANDSCAPE;
//     if !generate_android_project(*config) {
//         compiler_report(tprint("Failed to create android project \"%\"", config.project_path));
//         return;
//     }
//     log("Succesfully created or updated android project \"%\".", config.project_path);
//     log("Generate Android project time: %", GetUnixTimestamp()-start_time);

//     // NOTE(Charles): Android Icons!
//     //
//     // The apps icon is set with the icon attribute of application tag in manifest. This has to be set to a reference
//     // to a "drawable resource". This most direct way to do this is to put a png in the drawable directory, eg
//     // AndroidProject/app/src/main/res/drawable/my_icon.png. The attribute can then be set to
//     // android:icon="@drawable/my_icon".
//     //
//     // However, on my phone at least, this looks total ass. The image gets massively shrunk down and the rest of the
//     // icon is a white circle. Apparently this is because now android expects you to define "adaptive icons". Instead
//     // you now have to provide both a foreground and background image, and define your "image" in xml. So we now have
//     // two images drawable/icon_foreground.png, drawable/icon_background.png and the magic link file mipmap/icon.xml.
//     //
//     // This now looks ok on my phone, but I fully expect on other devices it might look weird and we will have to
//     // figure some more android configuration junk out. If we manage to figure out enough of the junk then I could
//     // maybe write some helper that generates all the required crap given some simpler inputs, for now it's all manually
//     // defined.
//     //
//     // Adaptive icons docs: https://developer.android.com/develop/ui/views/launch/icon_design_adaptive
//     // "Alternative resources" docs: https://developer.android.com/guide/topics/resources/providing-resources#AlternativeResources
// }
// }

target_windows  : bool;
target_linux    : bool;
target_macos    : bool;
target_android  : bool;
target_optimize : bool;
target_x64      : bool;

cpu_target: CPU_Tag;
os_target: Operating_System_Tag;

android_make_apk := false;
android_install  := false;

GetUnixTimestamp :: () -> float64 {
    #if OS == .WINDOWS {
        #import "Windows";
        FILETIME_TO_UNIX :: 116444736000000000;
        ft: FILETIME;
        GetSystemTimePreciseAsFileTime(*ft);
        t: s64 = ((cast(s64)ft.dwHighDateTime) << 32) | (cast(s64)ft.dwLowDateTime);
        return (cast(float64)(t - FILETIME_TO_UNIX)) / (10.0 * 1000.0 * 1000.0);
    }
    #if OS == .LINUX || OS == .MACOS {
        #import "POSIX";
        TIMESPEC_TO_SEC_DIV :: 1000000000.0;
        at := current_time_consensus();
        ts := to_timespec(at);
        return cast(float64)(ts.tv_sec) + cast(float64)(ts.tv_nsec) / TIMESPEC_TO_SEC_DIV;
    }
}
