#include "xbox_ref_log.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>

#ifdef _WIN32
#include <windows.h>
#endif

#ifndef MAX_PATH
#define MAX_PATH 260
#endif

static FILE *g_xbox_ref_log = NULL;
static char g_xbox_ref_log_path[MAX_PATH * 2] = {0};
static char g_xbox_ref_temp_path[MAX_PATH * 2] = {0};
static int g_xbox_ref_using_temp = 0;

static void xbox_ref_build_log_path(const char *target_filename, char drive_letter, char *out, size_t out_size, int *is_temp)
{
    if (!out || out_size == 0)
        return;

    out[0] = '\0';
    if (is_temp)
        *is_temp = 0;

    if (target_filename && target_filename[0]) {
        snprintf(out, out_size, "%s.log", target_filename);
        return;
    }

    if (drive_letter && isalpha((unsigned char)drive_letter))
        snprintf(out, out_size, "friidump_%c.log", (char)tolower((unsigned char)drive_letter));
    else
        snprintf(out, out_size, "friidump.log");

    if (is_temp)
        *is_temp = 1;
}

static int xbox_ref_open_path(const char *path, int append)
{
    if (!path || !path[0])
        return 0;

    g_xbox_ref_log = fopen(path, append ? "ab" : "wb");
    if (!g_xbox_ref_log)
        return 0;

    strncpy(g_xbox_ref_log_path, path, sizeof(g_xbox_ref_log_path) - 1);
    g_xbox_ref_log_path[sizeof(g_xbox_ref_log_path) - 1] = '\0';
    return 1;
}

int xbox_ref_log_open_for_target(const char *target_filename, char drive_letter)
{
    char path[MAX_PATH * 2];
    int is_temp = 0;

    xbox_ref_build_log_path(target_filename, drive_letter, path, sizeof(path), &is_temp);

    if (g_xbox_ref_log && strcmp(g_xbox_ref_log_path, path) == 0)
        return 1;

    xbox_ref_log_close();

    if (!xbox_ref_open_path(path, 0)) {
        fprintf(stderr, "[FRIIDUMP][WARN] Could not create log file '%s'. Continuing without file logging.\n", path[0] ? path : "(none)");
        return 0;
    }

    g_xbox_ref_using_temp = is_temp;
    if (is_temp) {
        strncpy(g_xbox_ref_temp_path, path, sizeof(g_xbox_ref_temp_path) - 1);
        g_xbox_ref_temp_path[sizeof(g_xbox_ref_temp_path) - 1] = '\0';
    } else {
        g_xbox_ref_temp_path[0] = '\0';
    }

    xbox_ref_printf("[FRIIDUMP] Log file: %s\n", g_xbox_ref_log_path);
    return 1;
}

int xbox_ref_log_retarget(const char *target_filename)
{
    char final_path[MAX_PATH * 2];
    int dummy = 0;

    if (!target_filename || !target_filename[0])
        return 0;

    xbox_ref_build_log_path(target_filename, 0, final_path, sizeof(final_path), &dummy);
    if (!final_path[0])
        return 0;

    if (strcmp(g_xbox_ref_log_path, final_path) == 0)
        return 1;

    if (!g_xbox_ref_log) {
        return xbox_ref_open_path(final_path, 0);
    }

    xbox_ref_printf("[FRIIDUMP] Retargeting log file to: %s\n", final_path);
    fclose(g_xbox_ref_log);
    g_xbox_ref_log = NULL;

    if (g_xbox_ref_using_temp && g_xbox_ref_temp_path[0]) {
        remove(final_path);
        if (rename(g_xbox_ref_temp_path, final_path) == 0) {
            g_xbox_ref_using_temp = 0;
            g_xbox_ref_temp_path[0] = '\0';
            return xbox_ref_open_path(final_path, 1);
        }
    }

    g_xbox_ref_using_temp = 0;
    g_xbox_ref_temp_path[0] = '\0';
    return xbox_ref_open_path(final_path, 1);
}

void xbox_ref_log_close(void)
{
    if (g_xbox_ref_log) {
        fflush(g_xbox_ref_log);
        fclose(g_xbox_ref_log);
    }
    g_xbox_ref_log = NULL;
    g_xbox_ref_log_path[0] = '\0';
    g_xbox_ref_temp_path[0] = '\0';
    g_xbox_ref_using_temp = 0;
}

int xbox_ref_log_is_open(void)
{
    return g_xbox_ref_log != NULL;
}

const char *xbox_ref_log_path(void)
{
    return g_xbox_ref_log_path[0] ? g_xbox_ref_log_path : NULL;
}

int xbox_ref_printf(const char *fmt, ...)
{
    va_list ap;
    va_list ap2;
    int out;

    va_start(ap, fmt);
    va_copy(ap2, ap);
    out = vprintf(fmt, ap);
    fflush(stdout);
    if (g_xbox_ref_log) {
        vfprintf(g_xbox_ref_log, fmt, ap2);
        fflush(g_xbox_ref_log);
    }
    va_end(ap2);
    va_end(ap);
    return out;
}

int xbox_ref_log_fprintf(FILE *stream, const char *fmt, ...)
{
    va_list ap;
    va_list ap2;
    int out;

    if (!stream)
        stream = stdout;

    va_start(ap, fmt);
    va_copy(ap2, ap);
    out = vfprintf(stream, fmt, ap);
    fflush(stream);
    if (g_xbox_ref_log) {
        vfprintf(g_xbox_ref_log, fmt, ap2);
        fflush(g_xbox_ref_log);
    }
    va_end(ap2);
    va_end(ap);
    return out;
}

int xbox_ref_console_printf(const char *fmt, ...)
{
    va_list ap;
    int out;

    va_start(ap, fmt);
    out = vprintf(fmt, ap);
    fflush(stdout);
    va_end(ap);
    return out;
}

static void xbox_ref_hexdump_to_file(FILE *stream, const char *prefix, const uint8_t *data, size_t length, size_t limit, int truncated)
{
    size_t i;
    size_t shown = (limit && limit < length) ? limit : length;

    if (!stream || !prefix)
        return;

    if (truncated)
        fprintf(stream, "\n--- %s (%lu bytes; console showing first %lu bytes, full data in log) ---\n",
                prefix, (unsigned long)length, (unsigned long)shown);
    else
        fprintf(stream, "\n--- %s (%lu bytes) ---\n", prefix, (unsigned long)length);

    for (i = 0; i < shown; i++) {
        fprintf(stream, "%02X ", data ? data[i] : 0);
        if ((i + 1) % 16 == 0)
            fprintf(stream, "\n");
    }
    if (shown % 16 != 0)
        fprintf(stream, "\n");
    if (truncated)
        fprintf(stream, "... [truncated on console; full %lu bytes written to log]\n", (unsigned long)length);
    fprintf(stream, "---------------------------\n");
    fflush(stream);
}

void xbox_ref_log_hexdump(const char *prefix, const uint8_t *data, size_t length, size_t console_limit)
{
    int console_truncated = (console_limit > 0 && console_limit < length);

    xbox_ref_hexdump_to_file(stdout, prefix, data, length, console_limit, console_truncated);

    if (g_xbox_ref_log) {
        /* The log is intentionally never truncated. */
        xbox_ref_hexdump_to_file(g_xbox_ref_log, prefix, data, length, 0, 0);
    }
}
