#include "xbox_ref_bridge.h"

#include <string.h>

void xbox_ref_dump_result_init(xbox_ref_dump_result *result)
{
    if (result)
        memset(result, 0, sizeof(*result));
}

#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <winioctl.h>
#include <stdio.h>
#include "xbox_ref/utils.h"
#include "xbox_ref/unlock.h"
#include "xbox_ref/xbox_ref_log.h"

#define printf xbox_ref_printf

#define XBOX_REF_LOCKED_VIDEO_VIEW_MAX_SECTORS 200000U

static BOOL xbox_ref_capacity_is_game_view(uint32_t sectors)
{
    return sectors > XBOX_REF_LOCKED_VIDEO_VIEW_MAX_SECTORS;
}

static uint32_t xbox_ref_refresh_ready_capacity(HANDLE hDevice)
{
    RefreshVolume(hDevice);
    Sleep(2000);
    EnsureDriveReady(hDevice, 30000);
    return GetTotalSectors(hDevice);
}

static char xbox_ref_drive_letter_from_device(const char *device)
{
    if (!device || !device[0]) return 0;
    if (device[0] && device[1] == ':') return device[0];
    if (device[0] == '\\' && device[1] == '\\' && device[2] == '.' && device[3] == '\\' && device[4] && device[5] == ':') return device[4];
    return device[0];
}

static int xbox_ref_gdr8050l_dump_core(HANDLE hDevice, const char *device, const char *filename, char mode, BOOL ownsHandle, xbox_ref_dump_result *result)
{
    char driveLetter;
    XboxGameInfo game;
    BOOL isDualLayer = FALSE;
    bool isDualLayerBool = false;
    char mediaId[33] = {0};
    char finalFilename[MAX_PATH] = {0};
    BOOL filenameOverride;
    uint32_t totalSectors = 0;
    DWORD dwBytesReturned = 0;
    BOOL volumeLocked = FALSE;
    BOOL ok = FALSE;

    xbox_ref_dump_result_init(result);
    if (result) {
        result->attempted = 1;
        result->mode = mode;
    }

    filenameOverride = (filename && filename[0]);

    if (mode != '1' && mode != '2') {
        xbox_ref_log_open_for_target(filename, 0);
        printf("[XBOX-REF] Fatal: unsupported Xbox reference dump mode '%c'.\n", mode);
        if (ownsHandle)
            xbox_ref_log_close();
        return 1;
    }

    driveLetter = xbox_ref_drive_letter_from_device(device);
    if (!driveLetter) {
        xbox_ref_log_open_for_target(filename, 0);
        printf("[XBOX-REF] Fatal: could not parse drive letter from device '%s'.\n", device ? device : "(null)");
        if (ownsHandle)
            xbox_ref_log_close();
        return 1;
    }

    xbox_ref_log_open_for_target(filename, driveLetter);
    printf("[XBOX-REF] Using copied original GDR-8050L dumper flow for drive %c:.\n", driveLetter);
    printf("[XBOX-REF] Handle mode: %s.\n", ownsHandle ? "opened by xbox_ref wrapper" : "using FriiDump's existing drive HANDLE");
    printf("[XBOX-REF] Preserving reference timing sleeps: tray 3000ms, ready-settle 1500ms/10000ms fallback, RefreshVolume 1000ms + post-refresh 2000ms.\n");

    if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL) {
        printf("[XBOX-REF] Fatal: invalid drive handle for %c:.\n", driveLetter);
        goto cleanup;
    }

    {
        uint32_t entrySectors;
        uint32_t verifiedSectors;
        BOOL preparedAfterHandshake = FALSE;

        printf("Opening drive %c: for state-aware Xbox handshake...\n", driveLetter);
        if (!EnsureDriveReady(hDevice, 30000)) {
            printf("[XBOX-REF] Fatal: drive did not become ready before Xbox state preparation.\n");
            goto cleanup;
        }

        entrySectors = GetTotalSectors(hDevice);
        printf("[XBOX-REF] Entry READ CAPACITY: %u sectors.\n", entrySectors);

        if (xbox_ref_capacity_is_game_view(entrySectors)) {
            printf("[XBOX-REF] Entry state already exposes the Xbox game view; skipping the redundant initial handshake and tray cycle.\n");
        } else {
            printf("[XBOX-REF] Entry state appears locked/video; attempting the full handshake directly without a media transition.\n");
            UnlockDrive(hDevice);
            verifiedSectors = xbox_ref_refresh_ready_capacity(hDevice);
            preparedAfterHandshake = TRUE;
            printf("[XBOX-REF] Direct-handshake READ CAPACITY: %u sectors.\n", verifiedSectors);

            if (!xbox_ref_capacity_is_game_view(verifiedSectors)) {
                printf("[XBOX-REF][WARN] Direct handshake did not expose the Xbox game view; performing one tray-cycle recovery and retry.\n");
                AutomateTrayCycle(hDevice);
                printf("[XBOX-REF] Re-applying the full handshake after recovery media change.\n");
                UnlockDrive(hDevice);
                verifiedSectors = xbox_ref_refresh_ready_capacity(hDevice);
                printf("[XBOX-REF] Recovery-handshake READ CAPACITY: %u sectors.\n", verifiedSectors);

                if (!xbox_ref_capacity_is_game_view(verifiedSectors)) {
                    printf("[XBOX-REF] Fatal: Xbox game view was not established after direct and recovery handshakes.\n");
                    goto cleanup;
                }
            }
        }

        if (!preparedAfterHandshake)
            xbox_ref_refresh_ready_capacity(hDevice);
    }

    printf("Setting drive to maximum performance mode...\n");
    SetDriveSpeedMax(hDevice);

    if (!DeviceIoControl(hDevice, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &dwBytesReturned, NULL)) {
        printf("[!] Warning: Could not lock volume. Close any open folders pointing to the drive.\n");
        printf("[XBOX-REF] The reference dumper treats this as fatal before XBE/title probing.\n");
        goto cleanup;
    }
    volumeLocked = TRUE;

    printf("Searching for Xbox Game Signature (Raw SCSI Scan)...\n");
    game = GetXboxGameInfo(hDevice);
    printf("Game Title Raw: %s\n", game.TitleName);
    ReadXboxGameDir(hDevice);

    totalSectors = GetXboxPhysicalSectors(hDevice);
    isDualLayerBool = false;
    GetDiscMetadata(hDevice, &totalSectors, &isDualLayerBool, &game);
    isDualLayer = isDualLayerBool ? TRUE : FALSE;
    GetMediaID(hDevice, mediaId);

    if (result) {
        if (game.Success && game.TitleName[0]) {
            strncpy(result->title, game.TitleName, sizeof(result->title) - 1);
            result->title[sizeof(result->title) - 1] = '\0';
        }
        if (mediaId[0]) {
            strncpy(result->media_id, mediaId, sizeof(result->media_id) - 1);
            result->media_id[sizeof(result->media_id) - 1] = '\0';
        }
    }

    if (game.Success) {
        char titleCopy[sizeof(game.TitleName)];
        memset(titleCopy, 0, sizeof(titleCopy));
        strncpy(titleCopy, game.TitleName, sizeof(titleCopy) - 1);
        SanitizeFilename(titleCopy);
        if (filenameOverride) {
            strncpy(finalFilename, filename, sizeof(finalFilename) - 1);
            finalFilename[sizeof(finalFilename) - 1] = '\0';
            printf("[XBOX-REF] Output filename override: %s\n", finalFilename);
        } else {
            snprintf(finalFilename, sizeof(finalFilename), "%s[%s].%s",
                     titleCopy[0] ? titleCopy : "XboxDisc",
                     mediaId[0] ? mediaId : "UNKNOWN_ID",
                     (mode == '1') ? "iso" : "xiso");
            printf("[XBOX-REF] XBE/DMI-derived output filename: %s\n", finalFilename);
            xbox_ref_log_retarget(finalFilename);
        }
        DisplayXboxGameInfo(game);
        printf("\n--- DISC INFORMATION ---\n");
        printf("Physical Sectors: %u\n", totalSectors);
        printf("Layers:           %d\n", isDualLayer ? 2 : 1);
        printf("Media ID:         %s\n", mediaId);
        printf("Target File:      %s\n", finalFilename);
        printf("-------------------------------\n\n");
    } else {
        if (filenameOverride) {
            strncpy(finalFilename, filename, sizeof(finalFilename) - 1);
            finalFilename[sizeof(finalFilename) - 1] = '\0';
            printf("Error: Could not retrieve Xbox game information; using override filename %s.\n", finalFilename);
        } else {
            printf("Error: Could not retrieve Xbox game information and no output filename override was supplied.\n");
            printf("       Rerun with -i <file> or -X <file> if metadata probing cannot complete.\n");
            goto cleanup;
        }
    }

    if (result && finalFilename[0]) {
        strncpy(result->output_path, finalFilename, sizeof(result->output_path) - 1);
        result->output_path[sizeof(result->output_path) - 1] = '\0';
    }

    if (totalSectors != (uint32_t)(REDUMP_SECTORS)) {
        printf("[WARNING] Unlocked drive-reported sectors: %u does not match expected Xbox game-view sectors: %u\n", totalSectors, (uint32_t)(REDUMP_SECTORS));
        printf("-Homebrew games may have different sizes.\n\n");
    }

    if (totalSectors == 0) {
        printf("Error: Could not determine total sectors for dumping.\n");
        goto cleanup;
    }

    printf("Starting dump with copied DumpXboxGameDisc() mode %c...\n", mode);
    ok = DumpXboxGameDisc(hDevice, finalFilename, mode, totalSectors, isDualLayerBool, FALSE, result);

cleanup:
    if (volumeLocked)
        DeviceIoControl(hDevice, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &dwBytesReturned, NULL);
    if (ownsHandle && hDevice != INVALID_HANDLE_VALUE && hDevice != NULL) {
        printf("Issuing STOP UNIT / spin-down after dump attempt... ");
        if (StopDriveUnit(hDevice)) printf("OK\n"); else printf("FAILED\n");
        CloseDrive(hDevice);
    } else {
        printf("[XBOX-REF] Returning to FriiDump; final STOP UNIT cleanup will use the same handle.\n");
    }
    if (ownsHandle) {
        printf("[XBOX-REF] Log file complete: %s\n", xbox_ref_log_path() ? xbox_ref_log_path() : "(unavailable)");
        xbox_ref_log_close();
    } else if (ok) {
        printf("[XBOX-REF] Returning finalized output evidence to FriiDump's shared Redump verifier.\n");
    } else {
        printf("[XBOX-REF] No completed output evidence is available for shared Redump verification.\n");
    }
    return ok ? 0 : 1;
}

int xbox_ref_gdr8050l_dump_with_handle(void *native_handle, const char *device, const char *filename, char mode, xbox_ref_dump_result *result)
{
    return xbox_ref_gdr8050l_dump_core((HANDLE)native_handle, device, filename, mode, FALSE, result);
}

int xbox_ref_gdr8050l_dump(const char *device, const char *filename, char mode, xbox_ref_dump_result *result)
{
    char driveLetter;
    HANDLE hDevice;

    driveLetter = xbox_ref_drive_letter_from_device(device);
    if (!driveLetter) {
        xbox_ref_log_open_for_target(filename, 0);
        printf("[XBOX-REF] Fatal: could not parse drive letter from device '%s'.\n", device ? device : "(null)");
        xbox_ref_log_close();
        return 1;
    }

    hDevice = OpenDrive(driveLetter);
    if (hDevice == INVALID_HANDLE_VALUE) {
        xbox_ref_log_open_for_target(filename, driveLetter);
        printf("[XBOX-REF] Fatal: Cannot open drive %c:. Run as Administrator.\n", driveLetter);
        xbox_ref_log_close();
        return 1;
    }

    return xbox_ref_gdr8050l_dump_core(hDevice, device, filename, mode, TRUE, result);
}
#else
int xbox_ref_gdr8050l_dump_with_handle(void *native_handle, const char *device, const char *filename, char mode, xbox_ref_dump_result *result)
{
    (void)native_handle; (void)device; (void)filename; (void)mode; xbox_ref_dump_result_init(result); return 1;
}

int xbox_ref_gdr8050l_dump(const char *device, const char *filename, char mode, xbox_ref_dump_result *result)
{
    (void)device; (void)filename; (void)mode; xbox_ref_dump_result_init(result); return 1;
}
#endif
