#pragma once

#ifndef PRE_VICASTCAM_C_H
#define PRE_VICASTCAM_C_H

#include <windows.h>
#include <shlwapi.h>
#include <wchar.h>
#include <wctype.h>

#pragma comment(lib, "shlwapi.lib")

#ifdef __cplusplus
extern "C" {
#endif

#ifndef ERROR_ELEVATION_REQUIRED
#define ERROR_ELEVATION_REQUIRED 740L
#endif

#ifndef ERROR_INSTALL_FAILURE
#define ERROR_INSTALL_FAILURE 1603L
#endif

#ifndef LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
#define LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR 0x00000100
#endif

#ifndef LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
#define LOAD_LIBRARY_SEARCH_DEFAULT_DIRS 0x00001000
#endif

#define PRE_VICASTCAM_MAX_PATH_CHARS (MAX_PATH * 4)

typedef int  (__cdecl *PFN_VICASTCAM_C_InitVcam)(void);
typedef int  (__cdecl *PFN_VICASTCAM_C_SetOutputFormat)(int width, int height, int format);
typedef int  (__cdecl *PFN_VICASTCAM_C_SendFrameYUY2)(unsigned char* i_yuy2, int i_width, int i_height, int i_stride);
typedef int  (__cdecl *PFN_VICASTCAM_C_SendFrameNV12)(unsigned char* i_nv12, int i_width, int i_height, int i_stride);
typedef int  (__cdecl *PFN_VICASTCAM_C_getMultiMediaToCamera)(wchar_t* tmpPath);
typedef int  (__cdecl *PFN_VICASTCAM_C_getImageToCamera)(wchar_t* tmpPath);
typedef int  (__cdecl *PFN_VICASTCAM_C_SetFriendlyName)(wchar_t* friendly_name);
typedef int  (__cdecl *PFN_VICASTCAM_C_SetLicenseCode)(char* license_code);
typedef void (__cdecl *PFN_VICASTCAM_C_SetMirrorEnabled)(int enabled);
typedef void (__cdecl *PFN_VICASTCAM_C_SetFlipEnabled)(int enabled);
typedef void (__cdecl *PFN_VICASTCAM_C_SetRotateRightDegree)(int degree);
typedef int  (__cdecl *PFN_VICASTCAM_C_SetVacantImg)(wchar_t* imgPath);
typedef void (__cdecl *PFN_VICASTCAM_C_setVcamOn)(int isOn);

static PFN_VICASTCAM_C_InitVcam              g_vicastcam_InitVcam = NULL;
static PFN_VICASTCAM_C_SetOutputFormat       g_vicastcam_SetOutputFormat = NULL;
static PFN_VICASTCAM_C_SendFrameYUY2         g_vicastcam_SendFrameYUY2 = NULL;
static PFN_VICASTCAM_C_SendFrameNV12         g_vicastcam_SendFrameNV12 = NULL;
static PFN_VICASTCAM_C_getMultiMediaToCamera g_vicastcam_getMultiMediaToCamera = NULL;
static PFN_VICASTCAM_C_getImageToCamera      g_vicastcam_getImageToCamera = NULL;
static PFN_VICASTCAM_C_SetFriendlyName       g_vicastcam_SetFriendlyName = NULL;
static PFN_VICASTCAM_C_SetLicenseCode        g_vicastcam_SetLicenseCode = NULL;
static PFN_VICASTCAM_C_SetMirrorEnabled      g_vicastcam_SetMirrorEnabled = NULL;
static PFN_VICASTCAM_C_SetFlipEnabled        g_vicastcam_SetFlipEnabled = NULL;
static PFN_VICASTCAM_C_SetRotateRightDegree  g_vicastcam_SetRotateRightDegree = NULL;
static PFN_VICASTCAM_C_SetVacantImg          g_vicastcam_SetVacantImg = NULL;
static PFN_VICASTCAM_C_setVcamOn             g_vicastcam_setVcamOn = NULL;

typedef HRESULT (WINAPI *PVCAM_C_DllInstall)(BOOL bInstall, LPCWSTR pszCmdLine);
typedef HRESULT (WINAPI *PVCAM_C_DllRegisterServer)(void);

static const wchar_t* pvcam_c_clsid = L"{0006A2C1-3F92-2D7B-1A4E-77093D628F55}";
static const wchar_t* pvcam_c_category = L"{860BB310-5D01-11D0-BD3B-00A0C911CE86}";
static HMODULE pvcam_c_dll = NULL;
static wchar_t pvcam_c_dll_path[PRE_VICASTCAM_MAX_PATH_CHARS] = { 0 };
static volatile LONG pvcam_c_lock = 0;

static void pvcam_c_lock_enter(void)
{
    while (InterlockedCompareExchange((volatile LONG*)&pvcam_c_lock, 1, 0) != 0) {
        Sleep(0);
    }
}

static void pvcam_c_lock_leave(void)
{
    InterlockedExchange((volatile LONG*)&pvcam_c_lock, 0);
}

static int pvcam_c_return_error(DWORD err)
{
    SetLastError(err);
    return (int)err;
}

static int pvcam_c_return_last_error_or(DWORD fallback)
{
    DWORD err = GetLastError();
    if (err == ERROR_SUCCESS) err = fallback;
    return pvcam_c_return_error(err);
}

static int pvcam_c_trim_path(const wchar_t* input, wchar_t* output, DWORD outputCch)
{
    const wchar_t* begin;
    const wchar_t* end;
    size_t len;

    if (!output || outputCch == 0) return 0;
    output[0] = L'\0';
    if (!input || !*input) return 0;

    begin = input;
    while (*begin && iswspace(*begin)) ++begin;

    end = begin + wcslen(begin);
    while (end > begin && iswspace(*(end - 1))) --end;

    if ((end - begin) >= 2 &&
        ((*begin == L'"' && *(end - 1) == L'"') || (*begin == L'\'' && *(end - 1) == L'\''))) {
        ++begin;
        --end;
        while (*begin && begin < end && iswspace(*begin)) ++begin;
        while (end > begin && iswspace(*(end - 1))) --end;
    }

    len = (size_t)(end - begin);
    if (len == 0 || len >= outputCch) return 0;

    wmemcpy(output, begin, len);
    output[len] = L'\0';
    return 1;
}

static int pvcam_c_resolve_path(const wchar_t* inputPath, wchar_t* outAbsPath, DWORD outCch)
{
    wchar_t path[PRE_VICASTCAM_MAX_PATH_CHARS];
    wchar_t fullPath[PRE_VICASTCAM_MAX_PATH_CHARS];
    DWORD n;

    if (!outAbsPath || outCch == 0) return 0;
    outAbsPath[0] = L'\0';

    if (!pvcam_c_trim_path(inputPath, path, PRE_VICASTCAM_MAX_PATH_CHARS)) return 0;

    if (!PathIsRelativeW(path)) {
        n = GetFullPathNameW(path, PRE_VICASTCAM_MAX_PATH_CHARS, fullPath, NULL);
        if (n == 0 || n >= PRE_VICASTCAM_MAX_PATH_CHARS || n >= outCch) return 0;
        wcscpy_s(outAbsPath, outCch, fullPath);
        return 1;
    }

    {
        wchar_t exePath[PRE_VICASTCAM_MAX_PATH_CHARS];
        wchar_t combined[PRE_VICASTCAM_MAX_PATH_CHARS];

        n = GetModuleFileNameW(NULL, exePath, PRE_VICASTCAM_MAX_PATH_CHARS);
        if (n == 0 || n >= PRE_VICASTCAM_MAX_PATH_CHARS) return 0;
        PathRemoveFileSpecW(exePath);

        if (!PathCombineW(combined, exePath, path)) return 0;

        n = GetFullPathNameW(combined, PRE_VICASTCAM_MAX_PATH_CHARS, fullPath, NULL);
        if (n == 0 || n >= PRE_VICASTCAM_MAX_PATH_CHARS || n >= outCch) return 0;
        wcscpy_s(outAbsPath, outCch, fullPath);
        return 1;
    }
}

static void pvcam_c_normalize_path(wchar_t* path)
{
    size_t i;
    if (!path) return;

    for (i = 0; path[i]; ++i) {
        if (path[i] == L'/') path[i] = L'\\';
        path[i] = (wchar_t)towlower(path[i]);
    }

    while (i > 0 && (path[i - 1] == L'\\' || path[i - 1] == L'/')) {
        path[i - 1] = L'\0';
        --i;
    }
}

static int pvcam_c_same_path(const wchar_t* a, const wchar_t* b)
{
    wchar_t fa[PRE_VICASTCAM_MAX_PATH_CHARS];
    wchar_t fb[PRE_VICASTCAM_MAX_PATH_CHARS];
    DWORD na;
    DWORD nb;

    if (!a || !b) return 0;

    na = GetFullPathNameW(a, PRE_VICASTCAM_MAX_PATH_CHARS, fa, NULL);
    nb = GetFullPathNameW(b, PRE_VICASTCAM_MAX_PATH_CHARS, fb, NULL);

    if (na == 0 || na >= PRE_VICASTCAM_MAX_PATH_CHARS) wcscpy_s(fa, PRE_VICASTCAM_MAX_PATH_CHARS, a);
    if (nb == 0 || nb >= PRE_VICASTCAM_MAX_PATH_CHARS) wcscpy_s(fb, PRE_VICASTCAM_MAX_PATH_CHARS, b);

    pvcam_c_normalize_path(fa);
    pvcam_c_normalize_path(fb);

    return wcscmp(fa, fb) == 0;
}

static REGSAM pvcam_c_registry_view(void)
{
#if defined(_WIN64)
    return KEY_WOW64_64KEY;
#else
    return KEY_WOW64_32KEY;
#endif
}

static int pvcam_c_reg_key_exists(HKEY root, const wchar_t* subKey, REGSAM viewFlag)
{
    HKEY hKey = NULL;
    LONG lr = RegOpenKeyExW(root, subKey, 0, KEY_READ | viewFlag, &hKey);
    if (lr == ERROR_SUCCESS) {
        RegCloseKey(hKey);
        return 1;
    }
    return 0;
}

static int pvcam_c_read_reg_default(HKEY root, const wchar_t* subKey, REGSAM viewFlag, wchar_t* outValue, DWORD outCch)
{
    HKEY hKey = NULL;
    LONG lr;
    DWORD type = 0;
    DWORD cbData = 0;
    wchar_t value[PRE_VICASTCAM_MAX_PATH_CHARS];

    if (!outValue || outCch == 0) return 0;
    outValue[0] = L'\0';

    lr = RegOpenKeyExW(root, subKey, 0, KEY_READ | viewFlag, &hKey);
    if (lr != ERROR_SUCCESS) return 0;

    lr = RegQueryValueExW(hKey, NULL, NULL, &type, NULL, &cbData);
    if (lr != ERROR_SUCCESS || (type != REG_SZ && type != REG_EXPAND_SZ) || cbData < sizeof(wchar_t) || cbData > sizeof(value)) {
        RegCloseKey(hKey);
        return 0;
    }

    ZeroMemory(value, sizeof(value));
    lr = RegQueryValueExW(hKey, NULL, NULL, &type, (LPBYTE)value, &cbData);
    RegCloseKey(hKey);
    if (lr != ERROR_SUCCESS) return 0;

    value[PRE_VICASTCAM_MAX_PATH_CHARS - 1] = L'\0';

    if (type == REG_EXPAND_SZ) {
        wchar_t expanded[PRE_VICASTCAM_MAX_PATH_CHARS];
        DWORD n = ExpandEnvironmentStringsW(value, expanded, PRE_VICASTCAM_MAX_PATH_CHARS);
        if (n > 0 && n < PRE_VICASTCAM_MAX_PATH_CHARS) wcscpy_s(value, PRE_VICASTCAM_MAX_PATH_CHARS, expanded);
    }

    if (wcslen(value) >= outCch) return 0;
    wcscpy_s(outValue, outCch, value);
    return outValue[0] != L'\0';
}

static int pvcam_c_is_camera_registered(const wchar_t* expectedDllPath)
{
    REGSAM viewFlag = pvcam_c_registry_view();
    wchar_t inprocKey[512];
    wchar_t categoryKey[512];
    wchar_t dllPath[PRE_VICASTCAM_MAX_PATH_CHARS];

    swprintf_s(inprocKey, 512, L"CLSID\\%s\\InprocServer32", pvcam_c_clsid);
    swprintf_s(categoryKey, 512, L"CLSID\\%s\\Instance\\%s", pvcam_c_category, pvcam_c_clsid);

    if (!pvcam_c_read_reg_default(HKEY_CLASSES_ROOT, inprocKey, viewFlag, dllPath, PRE_VICASTCAM_MAX_PATH_CHARS)) return 0;
    if (!PathFileExistsW(dllPath)) return 0;
    if (!pvcam_c_reg_key_exists(HKEY_CLASSES_ROOT, categoryKey, viewFlag)) return 0;
    if (expectedDllPath && *expectedDllPath && !pvcam_c_same_path(dllPath, expectedDllPath)) return 0;

    return 1;
}

static DWORD pvcam_c_hresult_to_win32(HRESULT hr, DWORD fallback)
{
    if (SUCCEEDED(hr)) return ERROR_SUCCESS;
    if (hr == E_ACCESSDENIED) return ERROR_ACCESS_DENIED;
    if (hr == E_OUTOFMEMORY) return ERROR_OUTOFMEMORY;
    if (hr == E_INVALIDARG) return ERROR_INVALID_PARAMETER;
    if (HRESULT_FACILITY(hr) == FACILITY_WIN32) {
        DWORD err = HRESULT_CODE(hr);
        if (err != ERROR_SUCCESS) return err;
    }
    return fallback;
}

static int pvcam_c_is_elevated(void)
{
    HANDLE hToken = NULL;
    TOKEN_ELEVATION elevation;
    DWORD cb = 0;
    BOOL ok;

    ZeroMemory(&elevation, sizeof(elevation));
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) return 0;
    ok = GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &cb);
    CloseHandle(hToken);

    return ok && elevation.TokenIsElevated != 0;
}

static DWORD pvcam_c_try_register(HMODULE hDll, const wchar_t* expectedDllPath)
{
    PVCAM_C_DllInstall pDllInstall;
    PVCAM_C_DllRegisterServer pDllRegisterServer;
    DWORD lastErr = ERROR_PROC_NOT_FOUND;

    if (!hDll) return ERROR_INVALID_HANDLE;

    pDllInstall = (PVCAM_C_DllInstall)GetProcAddress(hDll, "DllInstall");
    pDllRegisterServer = (PVCAM_C_DllRegisterServer)GetProcAddress(hDll, "DllRegisterServer");

    if (pDllInstall) {
        HRESULT hr = pDllInstall(TRUE, L"user");
        if (SUCCEEDED(hr) && pvcam_c_is_camera_registered(expectedDllPath)) return ERROR_SUCCESS;
        lastErr = pvcam_c_hresult_to_win32(hr, ERROR_INSTALL_FAILURE);
    }

    if (!pDllRegisterServer) return lastErr != ERROR_SUCCESS ? lastErr : ERROR_PROC_NOT_FOUND;
    if (!pvcam_c_is_elevated()) return ERROR_ELEVATION_REQUIRED;

    {
        HRESULT hr = pDllRegisterServer();
        if (FAILED(hr)) {
            DWORD err = pvcam_c_hresult_to_win32(hr, ERROR_INSTALL_FAILURE);
            if (err == ERROR_ACCESS_DENIED) err = ERROR_ELEVATION_REQUIRED;
            return err;
        }
    }

    return pvcam_c_is_camera_registered(expectedDllPath) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
}

static void pvcam_c_clear_pointers(void)
{
    g_vicastcam_InitVcam = NULL;
    g_vicastcam_SetOutputFormat = NULL;
    g_vicastcam_SendFrameYUY2 = NULL;
    g_vicastcam_SendFrameNV12 = NULL;
    g_vicastcam_getMultiMediaToCamera = NULL;
    g_vicastcam_getImageToCamera = NULL;
    g_vicastcam_SetFriendlyName = NULL;
    g_vicastcam_SetLicenseCode = NULL;
    g_vicastcam_SetMirrorEnabled = NULL;
    g_vicastcam_SetFlipEnabled = NULL;
    g_vicastcam_SetRotateRightDegree = NULL;
    g_vicastcam_SetVacantImg = NULL;
    g_vicastcam_setVcamOn = NULL;
}

static DWORD pvcam_c_bind_exports(HMODULE hDll)
{
    if (!hDll) return ERROR_INVALID_HANDLE;

#define PVCAM_C_BIND(varName, typeName, procName)                      \
    do {                                                               \
        varName = (typeName)GetProcAddress(hDll, procName);             \
        if (!varName) {                                                 \
            pvcam_c_clear_pointers();                                   \
            return ERROR_PROC_NOT_FOUND;                                \
        }                                                               \
    } while (0)

    PVCAM_C_BIND(g_vicastcam_InitVcam,              PFN_VICASTCAM_C_InitVcam,              "InitVcam");
    PVCAM_C_BIND(g_vicastcam_SetOutputFormat,       PFN_VICASTCAM_C_SetOutputFormat,       "SetOutputFormat");
    PVCAM_C_BIND(g_vicastcam_SendFrameYUY2,         PFN_VICASTCAM_C_SendFrameYUY2,         "SendFrameYUY2");
    PVCAM_C_BIND(g_vicastcam_SendFrameNV12,         PFN_VICASTCAM_C_SendFrameNV12,         "SendFrameNV12");
    PVCAM_C_BIND(g_vicastcam_getMultiMediaToCamera, PFN_VICASTCAM_C_getMultiMediaToCamera, "getMultiMediaToCamera");
    PVCAM_C_BIND(g_vicastcam_getImageToCamera,      PFN_VICASTCAM_C_getImageToCamera,      "getImageToCamera");
    PVCAM_C_BIND(g_vicastcam_SetFriendlyName,       PFN_VICASTCAM_C_SetFriendlyName,       "SetFriendlyName");
    PVCAM_C_BIND(g_vicastcam_SetLicenseCode,        PFN_VICASTCAM_C_SetLicenseCode,        "SetLicenseCode");
    PVCAM_C_BIND(g_vicastcam_SetMirrorEnabled,      PFN_VICASTCAM_C_SetMirrorEnabled,      "SetMirrorEnabled");
    PVCAM_C_BIND(g_vicastcam_SetFlipEnabled,        PFN_VICASTCAM_C_SetFlipEnabled,        "SetFlipEnabled");
    PVCAM_C_BIND(g_vicastcam_SetRotateRightDegree,  PFN_VICASTCAM_C_SetRotateRightDegree,  "SetRotateRightDegree");
    PVCAM_C_BIND(g_vicastcam_SetVacantImg,          PFN_VICASTCAM_C_SetVacantImg,          "SetVacantImg");
    PVCAM_C_BIND(g_vicastcam_setVcamOn,             PFN_VICASTCAM_C_setVcamOn,             "setVcamOn");

#undef PVCAM_C_BIND

    return ERROR_SUCCESS;
}

static int loadvicastcamdll(wchar_t* tmpDllPath)
{
    wchar_t input[PRE_VICASTCAM_MAX_PATH_CHARS];
    wchar_t absDllPath[PRE_VICASTCAM_MAX_PATH_CHARS];
    const wchar_t* ext;
    HMODULE hDll;

    SetLastError(ERROR_SUCCESS);

    if (!pvcam_c_trim_path(tmpDllPath, input, PRE_VICASTCAM_MAX_PATH_CHARS)) return pvcam_c_return_error(ERROR_INVALID_PARAMETER);
    if (!pvcam_c_resolve_path(input, absDllPath, PRE_VICASTCAM_MAX_PATH_CHARS)) return pvcam_c_return_last_error_or(ERROR_BAD_PATHNAME);
    if (!PathFileExistsW(absDllPath)) return pvcam_c_return_error(ERROR_FILE_NOT_FOUND);
    if (PathIsDirectoryW(absDllPath)) return pvcam_c_return_error(ERROR_BAD_PATHNAME);

    ext = PathFindExtensionW(absDllPath);
    if (!ext || _wcsicmp(ext, L".dll") != 0) return pvcam_c_return_error(ERROR_BAD_FORMAT);

    pvcam_c_lock_enter();

    hDll = pvcam_c_dll;
    if (hDll) {
        if (!pvcam_c_same_path(pvcam_c_dll_path, absDllPath)) {
            pvcam_c_lock_leave();
            return pvcam_c_return_error(ERROR_ALREADY_EXISTS);
        }
    } else {
        DWORD bindErr;
        hDll = LoadLibraryExW(absDllPath, NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
        if (!hDll) {
            DWORD firstErr = GetLastError();
            hDll = LoadLibraryExW(absDllPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
            if (!hDll) {
                DWORD secondErr = GetLastError();
                pvcam_c_lock_leave();
                return pvcam_c_return_error(secondErr != ERROR_SUCCESS ? secondErr : firstErr);
            }
        }

        bindErr = pvcam_c_bind_exports(hDll);
        if (bindErr != ERROR_SUCCESS) {
            FreeLibrary(hDll);
            pvcam_c_clear_pointers();
            pvcam_c_lock_leave();
            return pvcam_c_return_error(bindErr);
        }

        pvcam_c_dll = hDll;
        wcscpy_s(pvcam_c_dll_path, PRE_VICASTCAM_MAX_PATH_CHARS, absDllPath);
    }

    if (!pvcam_c_is_camera_registered(absDllPath)) {
        DWORD regErr = pvcam_c_try_register(hDll, absDllPath);
        if (regErr != ERROR_SUCCESS) {
            pvcam_c_lock_leave();
            return pvcam_c_return_error(regErr);
        }
        if (!pvcam_c_is_camera_registered(absDllPath)) {
            pvcam_c_lock_leave();
            return pvcam_c_return_error(ERROR_INSTALL_FAILURE);
        }
    }

    pvcam_c_lock_leave();
    return pvcam_c_return_error(ERROR_SUCCESS);
}

#ifdef __cplusplus
}
#endif

#endif
