#include <signal.h>

#include "common.h"
#include "devices/_devices.h"
#include "drivers/mx.h"
#include "hooks/_hooks.h"

WCHAR exeName[MAX_PATH + 1];
DWORD imageOffset;

#define WIN32_EXE_BASE 0x00400000

DWORD GetImageBase(void) {
    WCHAR sModulePath[MAX_PATH];
    GetModuleFileNameW(NULL, sModulePath, MAX_PATH);

    HANDLE hObject =
        _CreateFileW(sModulePath, FILE_READ_DATA, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    if (hObject == INVALID_HANDLE_VALUE) {
        log_error(plfBoot, "Failed to open %ls %03x", sModulePath, GetLastError());
        return 0;
    }

    IMAGE_DOS_HEADER dosHeader = { 0 };
    DWORD nRead;
    _SetFilePointer(hObject, 0, NULL, FILE_BEGIN);
    _ReadFile(hObject, &dosHeader, sizeof dosHeader, &nRead, NULL);
    if (nRead != sizeof dosHeader) {
        log_error(plfBoot, "Failed to read DOS header %03x", GetLastError());
        return 0;
    }
    if (dosHeader.e_magic != IMAGE_DOS_SIGNATURE) {
        log_error(plfBoot, "Invalid DOS magic number: %04x", dosHeader.e_magic);
        return 0;
    }

    IMAGE_NT_HEADERS32 ntHeaders32 = { 0 };
    _SetFilePointer(hObject, dosHeader.e_lfanew, NULL, FILE_BEGIN);
    _ReadFile(hObject, &ntHeaders32, sizeof ntHeaders32, &nRead, NULL);
    if (nRead != sizeof ntHeaders32) {
        log_error(plfBoot, "Failed to read NT header %03x", GetLastError());
        return 0;
    }
    if (ntHeaders32.Signature != IMAGE_NT_SIGNATURE) {
        log_error(plfBoot, "Invalid NT signature: %08x", ntHeaders32.Signature);
        return 0;
    }

    _CloseHandle(hObject);
    return ntHeaders32.OptionalHeader.ImageBase;
}

void apply_patches(HMODULE hModule) {
    void* baseAddress = (void*)hModule;

    DWORD imageBase = GetImageBase();
    if (imageBase == 0) {
        log_error(plfBoot, "Failed to locate image base. Patches will not be applied.");
        return;
    }
    imageOffset = (DWORD)hModule - imageBase;

    char exeNameC[MAX_PATH + 1];
    WideCharToMultiByte(CP_ACP, 0, exeName, -1, exeNameC, sizeof exeNameC, NULL, NULL);

    if (!load_patches(MiceConfig.mice.patches_file, exeNameC)) {
        log_error(plfBoot, "Failed to load patches file %s", MiceConfig.mice.patches_file);
        return;
    }

    patch_t* patch = patch_list->next;
    if (patch == NULL) {
        log_warning(plfBoot, "No patches to apply. Did you forgot an amiDebug patch file?");
    }
    while (patch) {
        DWORD oldProt;
        VirtualProtect((void*)((DWORD)patch->address + imageOffset), patch->count,
                       PAGE_EXECUTE_READWRITE, &oldProt);

        if (memcmp(patch->match, (void*)((DWORD)patch->address + imageOffset), patch->count) != 0) {
            log_error(plfBoot, "Patch %s failed! from-value missmatch", patch->name);
            VirtualProtect((void*)((DWORD)patch->address + imageOffset), patch->count, oldProt,
                           &oldProt);
            patch = patch->next;
            continue;
        }

        memcpy((void*)((DWORD)patch->address + imageOffset), patch->replace, patch->count);
        log_misc(plfBoot, "Patched %d bytes at %08x (%s)", patch->count, patch->address,
                 patch->name);

        patch = patch->next;
    }
}

void prebind_hooks() {
    hook_all();
    install_devices();
    // TODO: Figure out why we're needing to call this manually (medium priority)
    if (wcscmp(exeName, L"ALLNetProc.exe") == 0) {
        log_warning(plfBoot, "Making explicit call to OPENSSL_add_all_algorithms_noconf");

        // OPENSSL_add_all_algorithms_noconf
        ((void (*)(void))(0x00459770))();
    }
}

void init_injection(HMODULE hModule) {
    // Make sure our CRC32 tables are ready for anything that might want to use them
    amiCrc32RInit();

    load_mice_config();

    // We're in a new context now, so need to reconfigure
    setup_logging();
    log_info(plfBoot, "Handover complete. Now executing within %ls", exeName);

    init_com_devices();

    if (MiceConfig.mice.apply_patches) apply_patches(hModule);

    // Columba: Driver-level memory access, used to read the DMI tables
    if (MiceConfig.drivers.columba) setup_columba();
    // MX SRAM: SRAM-based nv memory
    if (MiceConfig.drivers.mxsram) setup_mxsram();
    // MX SuperIO: Communicate with the HW monitor chip
    if (MiceConfig.drivers.mxsuperio) setup_mxsuperio();
    // MX JVS: Interacting with JVS-based devices
    if (MiceConfig.drivers.mxjvs) setup_mxjvs();
    // MX HW Reset: Forcibly reboot the machine
    if (MiceConfig.drivers.mxhwreset) setup_mxhwreset();
    // MX SMBus: Communicate over the LPC bus. This contains the EEPROM, and PCA9535
    if (MiceConfig.drivers.mxsmbus) setup_mxsmbus();
    // MX Parallel: The parallel port (i.e. keychip)
    if (MiceConfig.drivers.mxparallel) setup_mxparallel();

    if (MiceConfig.drivers.platform) {
        if (!add_fake_device(&PLATFORM_GUID, L"\\\\.\\platform")) {
            log_error(plfPlatform, "failed to install platform device");
        }
    }

    // Must be the last thing called!
    prebind_hooks();
    setup_hooks();
}

void tea_hook_test(char* fmt, ...) {
    va_list argp;
    va_start(argp, fmt);
    vlog_game(plfTea, fmt, argp);
    va_end(argp);
}

int mxkMain();
int miceDummyMaster(unsigned short textPort, unsigned short binaryPort, bool global);
int miceDummyInstaller(unsigned short textPort, unsigned short binaryPort, bool global);
DWORD WINAPI MxkThreadProc(LPVOID lpParameter) {
    mxkMain();
    return 0;
};
DWORD WINAPI MdMasterThreadProc(LPVOID lpParameter) {
    miceDummyMaster(40100, 40101, false);
    return 0;
};
DWORD WINAPI MdInstallerThreadProc(LPVOID lpParameter) {
    miceDummyInstaller(40102, 40103, false);
    return 0;
};
void spawn_pcp_processes(void) {
    // CreateThread(NULL, 0, MxkThreadProc, NULL, 0, NULL);
    // CreateThread(NULL, 0, MdMasterThreadProc, NULL, 0, NULL);
    // CreateThread(NULL, 0, MdInstallerThreadProc, NULL, 0, NULL);
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    if (ul_reason_for_call != DLL_PROCESS_ATTACH) return TRUE;

    GetModuleFileNameW(NULL, exeName, MAX_PATH);
    wcscpy_s(exeName, MAX_PATH + 1, PathFindFileNameW(exeName));

    HMODULE exeModule = GetModuleHandleA(NULL);
    init_injection(exeModule);

    if (wcscmp(exeName, L"InitialD8_GLW_RE_SBZZ_dumped_.exe") == 0) {
        CreateHook32((void*)(0x00407850), &tea_hook_test);
        // *((DWORD*)(0x00407850)) = (DWORD)(&logcb);
    }

    spawn_pcp_processes();

    // if (wcscmp(exeName, L"RingGame.exe") == 0) {
    //     log_warning(plfBoot, "Bodge hook goo!");
    //     CreateHook32((void*)(0x005f2580), &tea_hook_test);
    // }

    return TRUE;
}
