#import "Basic";
#import "Windows";
#import "Windows_Utf8";
#import "Thread";
#import "String";

#load "win32.jai";
#load "dialog.jai";
#load "updater.jai";

// Custom constants
TIMER_PROCESS_CHECK :: 1;

IDI_APPLICATION   :: cast(*u16) 32512;
IDI_SHIELD        :: cast(*u16) 32518;

App_State :: struct {
    hwnd: HWND;
    singbox_running: bool;
    singbox_process_handle: HANDLE;
    singbox_thread_handle: HANDLE;
    singbox_job_handle: HANDLE;

    updater_thread: Thread;
    updater_data: Updater_Thread_Data;
    use_system_proxy: bool = true;
}

// Custom log print sending logs to OutputDebugStringW
log_print :: (format_string: string, args: .. Any) {
    formatted := tprint(format_string, .. args);
    OutputDebugStringW(utf8_to_wide(formatted));
}

file_exists :: (path: string) -> bool {
    INVALID_FILE_ATTRIBUTES :: 0xFFFFFFFF;
    wide_path := utf8_to_wide(path);
    attribs := GetFileAttributesW(wide_path);
    return attribs != INVALID_FILE_ATTRIBUTES && !(attribs & FILE_ATTRIBUTE_DIRECTORY);
}

// Hidden Process Spawning with Job Object configuration and stdout/stderr redirection
create_process_hidden :: (cmd: string, log_file: string = "") -> HANDLE, PROCESS_INFORMATION, bool {
    startup_info: STARTUPINFOW;
    startup_info.cb = size_of(STARTUPINFOW);
    startup_info.dwFlags = STARTF_USESHOWWINDOW;
    startup_info.wShowWindow = SW_HIDE;

    file_handle: HANDLE = INVALID_HANDLE_VALUE;

    if log_file {
        sa: SECURITY_ATTRIBUTES;
        sa.nLength = size_of(SECURITY_ATTRIBUTES);
        sa.bInheritHandle = 1; // TRUE
        sa.lpSecurityDescriptor = null;

        wide_log := utf8_to_wide(log_file);

        file_handle = CreateFileW(
            wide_log,
            GENERIC_WRITE,
            FILE_SHARE_READ,
            *sa,
            CREATE_ALWAYS,
            FILE_ATTRIBUTE_NORMAL,
            null
        );

        if file_handle != INVALID_HANDLE_VALUE {
            startup_info.dwFlags |= STARTF_USESTDHANDLES;
            startup_info.hStdOutput = file_handle;
            startup_info.hStdError = file_handle;
        }
    }

    process_info: PROCESS_INFORMATION;

    CREATE_NO_WINDOW :: 0x08000000;

    job_handle := CreateJobObjectA(null, null);
    if job_handle {
        job_info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION;
        job_info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
        SetInformationJobObject(job_handle, .ExtendedLimitInformation, *job_info, size_of(type_of(job_info)));
    }

    cmd_wide := utf8_to_wide(cmd);

    inherit_handles := ifx file_handle != INVALID_HANDLE_VALUE then cast(BOOL) 1 else cast(BOOL) 0;

    success := CreateProcessW(
        null,
        cmd_wide,
        null,
        null,
        inherit_handles,
        CREATE_NO_WINDOW,
        null,
        null,
        *startup_info,
        *process_info
    );

    if success && job_handle {
        AssignProcessToJobObject(job_handle, process_info.hProcess);
    }

    if file_handle != INVALID_HANDLE_VALUE {
        CloseHandle(file_handle);
    }

    return job_handle, process_info, cast(bool) success;
}

set_tray_tip :: (nid: *NOTIFYICONDATAW, text: string) {
    wide_text := utf8_to_wide(text);
    i := 0;
    while wide_text[i] != 0 && i < 127 {
        nid.szTip[i] = wide_text[i];
        i += 1;
    }
    nid.szTip[i] = 0;
}

update_tray :: (app: *App_State) {
    nid: NOTIFYICONDATAW;
    nid.cbSize = size_of(NOTIFYICONDATAW);
    nid.hWnd = app.hwnd;
    nid.uID = 1;
    nid.uFlags = NIF_ICON | NIF_TIP;

    if app.singbox_running {
        nid.hIcon = LoadIconW(null, IDI_SHIELD);
        set_tray_tip(*nid, "Sing-box: Running");
    } else {
        nid.hIcon = LoadIconW(null, IDI_APPLICATION);
        set_tray_tip(*nid, "Sing-box: Stopped");
    }

    Shell_NotifyIconW(NIM_MODIFY, *nid);
}

start_singbox :: (app: *App_State) -> bool {
    if app.singbox_running return true;

    if !file_exists("config.json") {
        if file_exists("url.txt") {
            changed, success, err_msg := perform_update();
            if !success {
                msg := tprint("Could not download configuration.\nError: %", err_msg);
                MessageBoxW(app.hwnd, utf8_to_wide(msg), utf8_to_wide("Sing-box Tray"), MB_ICONERROR);
                return false;
            }
        } else {
            MessageBoxW(app.hwnd, utf8_to_wide("No configuration found. Please configure the Config URL first."), utf8_to_wide("Sing-box Tray"), MB_ICONWARNING);
            return false;
        }
    }

    if file_exists("config.json") {
        config_data, read_ok := read_entire_file("config.json");
        if read_ok {
            modified := modify_config_inbounds(config_data);
            if modified != config_data {
                write_entire_file("config.json", modified);
                log_print("Existing config.json sanitized to use mixed inbound.\n");
            }
            free(config_data);
            free(modified);
        }
    }

    exe_path := "sing-box.exe";
    // Check if sing-box.exe is not in current folder, check in the sing-box subfolder
    if !file_exists("sing-box.exe") {
        if file_exists("sing-box/sing-box.exe") {
            exe_path = "sing-box\\sing-box.exe";
        }
    }

    cmd := tprint("% run -c config.json", exe_path);
    job, pi, ok := create_process_hidden(cmd, "sing-box.log");
    if !ok {
        msg := tprint("Failed to start %. Please ensure sing-box.exe is in the same folder or in the 'sing-box' subfolder.", exe_path);
        MessageBoxW(app.hwnd, utf8_to_wide(msg), utf8_to_wide("Sing-box Tray"), MB_ICONERROR);
        return false;
    }

    app.singbox_process_handle = pi.hProcess;
    app.singbox_thread_handle = pi.hThread;
    app.singbox_job_handle = job;
    app.singbox_running = true;

    if app.use_system_proxy {
        set_windows_system_proxy(true, "127.0.0.1:20122");
        log_print("System proxy enabled.\n");
    }

    update_tray(app);
    log_print("Sing-box started successfully.\n");
    return true;
}

stop_singbox :: (app: *App_State) {
    if !app.singbox_running return;

    TerminateProcess(app.singbox_process_handle, 0);
    CloseHandle(app.singbox_process_handle);
    CloseHandle(app.singbox_thread_handle);
    CloseHandle(app.singbox_job_handle);

    app.singbox_process_handle = null;
    app.singbox_thread_handle = null;
    app.singbox_job_handle = null;
    app.singbox_running = false;

    set_windows_system_proxy(false, "");
    log_print("System proxy disabled.\n");

    update_tray(app);
    log_print("Sing-box stopped.\n");
}

check_process_status :: (app: *App_State) {
    if !app.singbox_running return;

    exit_code: u32;
    success := GetExitCodeProcess(app.singbox_process_handle, *exit_code);
    STILL_ACTIVE :: 259;
    if success && exit_code != STILL_ACTIVE {
        log_print("Sing-box process exited unexpectedly with code %.\n", exit_code);

        CloseHandle(app.singbox_process_handle);
        CloseHandle(app.singbox_thread_handle);
        CloseHandle(app.singbox_job_handle);

        app.singbox_process_handle = null;
        app.singbox_thread_handle = null;
        app.singbox_job_handle = null;
        app.singbox_running = false;

        set_windows_system_proxy(false, "");
        log_print("System proxy disabled due to unexpected exit.\n");

        update_tray(app);
    }
}

trigger_immediate_update :: (app: *App_State) {
    changed, success, err_msg := perform_update();
    if success {
        if changed {
            log_print("Config updated, restarting Sing-box...\n");
            if app.singbox_running {
                stop_singbox(app);
                start_singbox(app);
            } else {
                start_singbox(app);
            }
            MessageBoxW(app.hwnd, utf8_to_wide("Configuration updated and Sing-box restarted successfully."), utf8_to_wide("Sing-box Tray"), MB_ICONINFORMATION);
        } else {
            if !app.singbox_running {
                start_singbox(app);
            }
            MessageBoxW(app.hwnd, utf8_to_wide("Configuration is already up-to-date."), utf8_to_wide("Sing-box Tray"), MB_ICONINFORMATION);
        }
    } else {
        msg := tprint("Failed to download configuration.\nError: %", err_msg);
        MessageBoxW(app.hwnd, utf8_to_wide(msg), utf8_to_wide("Sing-box Tray"), MB_ICONERROR);
    }
}

show_context_menu :: (app: *App_State) {
    hMenu := CreatePopupMenu();
    if !hMenu return;
    defer DestroyMenu(hMenu);

    status_str := ifx app.singbox_running then "Sing-box: Running" else "Sing-box: Stopped";
    AppendMenuW(hMenu, MF_STRING | MF_GRAYED | MF_DISABLED, CMD_STATUS, utf8_to_wide(status_str));

    AppendMenuW(hMenu, MF_SEPARATOR, 0, null);

    toggle_str := ifx app.singbox_running then "Stop Sing-box" else "Start Sing-box";
    AppendMenuW(hMenu, MF_STRING, CMD_START_STOP, utf8_to_wide(toggle_str));

    AppendMenuW(hMenu, MF_STRING, CMD_SET_URL, utf8_to_wide("Configure URL..."));
    AppendMenuW(hMenu, MF_STRING, CMD_UPDATE_NOW, utf8_to_wide("Update Config Now"));

    AppendMenuW(hMenu, MF_SEPARATOR, 0, null);

    proxy_flags := MF_STRING;
    sys_proxy_flags := MF_STRING;
    if app.use_system_proxy {
        sys_proxy_flags |= MF_CHECKED;
    } else {
        proxy_flags |= MF_CHECKED;
    }
    AppendMenuW(hMenu, proxy_flags, CMD_MODE_PROXY, utf8_to_wide("Proxy Mode (Local only)"));
    AppendMenuW(hMenu, sys_proxy_flags, CMD_MODE_SYS_PROXY, utf8_to_wide("System Proxy Mode"));

    AppendMenuW(hMenu, MF_SEPARATOR, 0, null);

    AppendMenuW(hMenu, MF_STRING, CMD_EXIT, utf8_to_wide("Exit"));

    cursor_pos: POINT;
    GetCursorPos(*cursor_pos);

    SetForegroundWindow(app.hwnd);

    track_flags := TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD;
    cmd := TrackPopupMenu(hMenu, track_flags, cursor_pos.x, cursor_pos.y, 0, app.hwnd, null);

    if cmd == {
        case CMD_START_STOP; {
            if app.singbox_running {
                stop_singbox(app);
            } else {
                start_singbox(app);
            }
        }
        case CMD_SET_URL; {
            changed := show_config_url_dialog(app.hwnd);
            if changed {
                log_print("URL configured, triggering download...\n");
                trigger_immediate_update(app);
            }
        }
        case CMD_UPDATE_NOW; {
            trigger_immediate_update(app);
        }
        case CMD_MODE_PROXY; {
            if app.use_system_proxy {
                app.use_system_proxy = false;
                write_entire_file("mode.txt", "proxy");
                log_print("Switched to Proxy Mode.\n");
                if app.singbox_running {
                    set_windows_system_proxy(false, "");
                    log_print("System proxy disabled.\n");
                }
            }
        }
        case CMD_MODE_SYS_PROXY; {
            if !app.use_system_proxy {
                app.use_system_proxy = true;
                write_entire_file("mode.txt", "system_proxy");
                log_print("Switched to System Proxy Mode.\n");
                if app.singbox_running {
                    set_windows_system_proxy(true, "127.0.0.1:20122");
                    log_print("System proxy enabled.\n");
                }
            }
        }
        case CMD_EXIT; {
            DestroyWindow(app.hwnd);
        }
    }

    PostMessageW(app.hwnd, WM_NULL, 0, 0);
}

app_wnd_proc :: (hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT #c_call {
    ctx: #Context;
    push_context ctx {
        app := cast(*App_State) GetWindowLongPtrW(hwnd, GWLP_USERDATA);

        if msg == {
            case WM_CREATE; {
                create_struct := cast(*CREATESTRUCTW) lparam;
                app = cast(*App_State) create_struct.lpCreateParams;
                SetWindowLongPtrW(hwnd, GWLP_USERDATA, cast(LONG_PTR) app);
                return 0;
            }
            case WM_TRAY_CALLBACK; {
                if lparam == WM_RBUTTONUP || lparam == WM_LBUTTONUP {
                    show_context_menu(app);
                }
                return 0;
            }
            case WM_RESTART_SINGBOX; {
                log_print("WM_RESTART_SINGBOX received, restarting sing-box...\n");
                if app.singbox_running {
                    stop_singbox(app);
                    start_singbox(app);
                }
                return 0;
            }
            case WM_TIMER; {
                if wparam == TIMER_PROCESS_CHECK {
                    check_process_status(app);
                }
                return 0;
            }
            case WM_DESTROY; {
                PostQuitMessage(0);
                return 0;
            }
        }
        return DefWindowProcW(hwnd, msg, wparam, lparam);
    }
}

main :: () {
    hInstance := GetModuleHandleW(null);
    class_name := utf8_to_wide("SingboxTrayControllerClass");

    wclass: WNDCLASSEXW;
    wclass.cbSize = size_of(WNDCLASSEXW);
    wclass.lpfnWndProc = app_wnd_proc;
    wclass.hInstance = hInstance;
    wclass.hCursor = LoadCursorW(null, IDC_ARROW);
    wclass.lpszClassName = class_name;

    RegisterClassExW(*wclass);
    defer UnregisterClassW(class_name, hInstance);

    app_state: App_State;

    hwnd := CreateWindowExW(
        0,
        class_name,
        utf8_to_wide("Singbox Tray Controller"),
        0,
        0, 0, 0, 0,
        null,
        null,
        hInstance,
        *app_state
    );

    if !hwnd {
        log_print("Failed to create hidden window!\n");
        return;
    }

    app_state.hwnd = hwnd;

    // Load persisted mode
    mode_data, mode_ok := read_entire_file("mode.txt");
    if mode_ok {
        mode_str := trim(mode_data);
        if mode_str == "proxy" {
            app_state.use_system_proxy = false;
        }
        free(mode_data);
    }

    nid: NOTIFYICONDATAW;
    nid.cbSize = size_of(NOTIFYICONDATAW);
    nid.hWnd = hwnd;
    nid.uID = 1;
    nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
    nid.uCallbackMessage = WM_TRAY_CALLBACK;
    nid.hIcon = LoadIconW(null, IDI_APPLICATION);
    set_tray_tip(*nid, "Sing-box: Stopped");

    if !Shell_NotifyIconW(NIM_ADD, *nid) {
        log_print("Failed to register tray icon!\n");
        return;
    }
    defer Shell_NotifyIconW(NIM_DELETE, *nid);

    if file_exists("config.json") {
        start_singbox(*app_state);
    }

    SetTimer(hwnd, TIMER_PROCESS_CHECK, 1000, null);
    defer KillTimer(hwnd, TIMER_PROCESS_CHECK);

    app_state.updater_data.hwnd = hwnd;
    app_state.updater_data.update_interval_seconds = 3600;
    app_state.updater_data.stop_event = CreateEventW(null, TRUE, FALSE, null);

    thread_init(*app_state.updater_thread, updater_thread_proc);
    app_state.updater_thread.data = *app_state.updater_data;
    thread_start(*app_state.updater_thread);

    msg: MSG;
    while GetMessageW(*msg, null, 0, 0) {
        TranslateMessage(*msg);
        DispatchMessageW(*msg);
    }

    log_print("Exiting application...\n");

    if app_state.updater_data.stop_event {
        SetEvent(app_state.updater_data.stop_event);
        thread_is_done(*app_state.updater_thread, -1);
        thread_deinit(*app_state.updater_thread);
        CloseHandle(app_state.updater_data.stop_event);
    }

    stop_singbox(*app_state);
}
