#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "Change.h"

#define CHANGE_LOAD_EXPORT(api, module, member, type)                                      \
    do {                                                                                    \
        FARPROC change_proc = GetProcAddress((module), #member);                           \
        if (change_proc == NULL) {                                                          \
            DWORD change_error = GetLastError();                                            \
            FreeLibrary((module));                                                          \
            memset((api), 0, sizeof(*(api)));                                               \
            (api)->last_system_error = (uint32_t)change_error;                              \
            return CHANGE_LOADER_EXPORT_NOT_FOUND;                                          \
        }                                                                                   \
        (api)->member = (type)change_proc;                                                   \
    } while (0)

int Change_ValidateAbi(void)
{
    if (sizeof(void *) != 4u || sizeof(CHANGE_LONG) != 4u || sizeof(CHANGE_INT64) != 8u) {
        return CHANGE_LOADER_ABI_MISMATCH;
    }
    if (sizeof(CustomerInfo) != 68u || sizeof(DOWNINFO) != 320u ||
        sizeof(DOWNDEPINFO) != 268u) {
        return CHANGE_LOADER_ABI_MISMATCH;
    }
    if (sizeof(UPLOADINFO) != CHANGE_EXPECTED_UPLOADINFO_SIZE ||
        offsetof(UPLOADINFO, psamID) != CHANGE_EXPECTED_PSAMID_OFFSET) {
        return CHANGE_LOADER_ABI_MISMATCH;
    }
    return CHANGE_LOADER_OK;
}

int Change_Load(ChangeApi *api, const char *dll_path)
{
    HMODULE module;
    int abi_status;

    if (api == NULL) {
        return CHANGE_LOADER_INVALID_ARGUMENT;
    }

    memset(api, 0, sizeof(*api));
    abi_status = Change_ValidateAbi();
    if (abi_status != CHANGE_LOADER_OK) {
        return abi_status;
    }

    if (dll_path == NULL || dll_path[0] == '\0') {
        dll_path = "Change.dll";
    }

    module = LoadLibraryA(dll_path);
    if (module == NULL) {
        api->last_system_error = (uint32_t)GetLastError();
        return CHANGE_LOADER_DLL_NOT_FOUND;
    }

    api->module = (void *)module;
    CHANGE_LOAD_EXPORT(api, module, CapDBConnect, CHANGE_PFN_CapDBConnect);
    CHANGE_LOAD_EXPORT(api, module, CapDBDiscon, CHANGE_PFN_CapDBDiscon);
    CHANGE_LOAD_EXPORT(api, module, CapDownBlacklist, CHANGE_PFN_CapDownBlacklist);
    CHANGE_LOAD_EXPORT(api, module, CapDownBlacklistCount, CHANGE_PFN_CapDownBlacklistCount);
    CHANGE_LOAD_EXPORT(api, module, CapDownCustomerInfo, CHANGE_PFN_CapDownCustomerInfo);
    CHANGE_LOAD_EXPORT(api, module, CapGetCustomerByNum, CHANGE_PFN_CapGetCustomerByNum);
    CHANGE_LOAD_EXPORT(api, module, CapGetCustomerCount, CHANGE_PFN_CapGetCustomerCount);
    CHANGE_LOAD_EXPORT(api, module, CapGetCustomerList, CHANGE_PFN_CapGetCustomerList);
    CHANGE_LOAD_EXPORT(api, module, CapGetDepCount, CHANGE_PFN_CapGetDepCount);
    CHANGE_LOAD_EXPORT(api, module, CapGetDepList, CHANGE_PFN_CapGetDepList);
    CHANGE_LOAD_EXPORT(api, module, CapGetNBCardInfo, CHANGE_PFN_CapGetNBCardInfo);
    CHANGE_LOAD_EXPORT(api, module, CapNBQueryCard, CHANGE_PFN_CapNBQueryCard);
    CHANGE_LOAD_EXPORT(api, module, CapSetNBCardInfo, CHANGE_PFN_CapSetNBCardInfo);
    CHANGE_LOAD_EXPORT(api, module, CapUpload, CHANGE_PFN_CapUpload);
    CHANGE_LOAD_EXPORT(api, module, CloseCom, CHANGE_PFN_CloseCom);
    CHANGE_LOAD_EXPORT(api, module, OpenCom, CHANGE_PFN_OpenCom);

    api->last_system_error = 0u;
    return CHANGE_LOADER_OK;
}

void Change_Unload(ChangeApi *api)
{
    if (api == NULL) {
        return;
    }
    if (api->module != NULL) {
        FreeLibrary((HMODULE)api->module);
    }
    memset(api, 0, sizeof(*api));
}

int Change_IsLoaded(const ChangeApi *api)
{
    return api != NULL && api->module != NULL;
}

const char *Change_LoaderStatusText(int status)
{
    switch (status) {
    case CHANGE_LOADER_OK:
        return "success";
    case CHANGE_LOADER_INVALID_ARGUMENT:
        return "invalid loader argument";
    case CHANGE_LOADER_ABI_MISMATCH:
        return "ABI mismatch: build Win32/x86 and check structure packing";
    case CHANGE_LOADER_DLL_NOT_FOUND:
        return "Change.dll could not be loaded";
    case CHANGE_LOADER_EXPORT_NOT_FOUND:
        return "a required Change.dll export is missing";
    default:
        return "unknown loader status";
    }
}

size_t Change_FormatSystemError(uint32_t error_code, char *buffer, size_t buffer_size)
{
    DWORD length;

    if (buffer == NULL || buffer_size == 0u) {
        return 0u;
    }
    buffer[0] = '\0';
    if (error_code == 0u) {
        return 0u;
    }

    length = FormatMessageA(
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        (DWORD)error_code,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        buffer,
        (DWORD)buffer_size,
        NULL);

    while (length > 0u && (buffer[length - 1u] == '\r' || buffer[length - 1u] == '\n')) {
        buffer[--length] = '\0';
    }
    return (size_t)length;
}
