#include "Clipboard.hpp"
#include "ClipboardOperations.hpp"

std::unique_ptr<Clipboard> Clipboard::sInstance = nullptr;

void Clipboard::Initialize(const wchar_t* windowClassName)
{
    if (!sInstance)
        sInstance.reset(new Clipboard(windowClassName));
}

void Clipboard::SetCallback(OnClipboardContentChangedCallback callback)
{
    if (!sInstance) 
        return;

    AcquireSRWLockExclusive(&sInstance->mCallbackLock);
    sInstance->mCallback = std::move(callback);
    ReleaseSRWLockExclusive(&sInstance->mCallbackLock);
}

void Clipboard::SetClipboardText(const std::wstring& newText)
{
    if (!sInstance) 
        return;

    sInstance->SetClipboardTextImpl(newText);
}

std::wstring Clipboard::GetClipboardText()
{
    if (!sInstance)
        return {};

    return sInstance->GetClipboardTextImpl();
}

Clipboard::Clipboard(const wchar_t* windowClassName) : mWindowsClassName(windowClassName)
{
    InitializeSRWLock(&mCallbackLock);

    mhReadyEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);

    mWindowClassManager = std::make_unique<WindowClassManager>(windowClassName);

    mhThread = CreateThread(nullptr, 0, ThreadProcStatic, this, 0, nullptr);

    WaitForSingleObject(mhReadyEvent, INFINITE);
    CloseHandle(mhReadyEvent);
    mhReadyEvent = nullptr;
}

Clipboard::~Clipboard()
{
    if (mhWindow)
        PostMessageW(mhWindow, WM_QUIT, 0, 0);

    if (mhThread)
    {
        WaitForSingleObject(mhThread, INFINITE);
        CloseHandle(mhThread);
    }
}

DWORD WINAPI Clipboard::ThreadProcStatic(LPVOID lpParam)
{
    auto* clipboard = static_cast<Clipboard*>(lpParam);
    clipboard->ThreadMain();
    return 0;
}

void Clipboard::ThreadMain()
{
    if (!mWindowClassManager->Register(WndProc))
    {
        SetEvent(mhReadyEvent);
        return;
    }

    mhWindow = CreateWindowExW(0, mWindowsClassName.c_str(), nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, GetModuleHandleW(nullptr), nullptr);
    if (!mhWindow)
    {
        mWindowClassManager->Unregister();
        SetEvent(mhReadyEvent);
        return;
    }

    SetWindowLongPtrW(mhWindow, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));

    if (!AddClipboardFormatListener(mhWindow))
    {
        DestroyWindow(mhWindow);
        mWindowClassManager->Unregister();
        SetEvent(mhReadyEvent);
        return;
    }

    SetEvent(mhReadyEvent);

    MSG msg;
    while (GetMessageW(&msg, nullptr, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    RemoveClipboardFormatListener(mhWindow);
    DestroyWindow(mhWindow);
    mWindowClassManager->Unregister();
}

LRESULT CALLBACK Clipboard::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (msg != WM_CLIPBOARDUPDATE)
        return DefWindowProcW(hwnd, msg, wParam, lParam);

    auto* self = reinterpret_cast<Clipboard*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
    if (self && self->mCallback)
    {
        std::wstring text = self->GetClipboardTextImpl();
        if (!text.empty())
        {
            AcquireSRWLockShared(&self->mCallbackLock);
            auto callback = self->mCallback;
            ReleaseSRWLockShared(&self->mCallbackLock);

            callback(text);
        }
    }

    return 0;
}

void Clipboard::SetClipboardTextImpl(const std::wstring& newText)
{
    if (!mhWindow || !OpenClipboard(mhWindow))
        return;

    EmptyClipboard();

    HGLOBAL hMem = ClipboardOperations::CreateTextData(newText);
    if (hMem)
        SetClipboardData(CF_UNICODETEXT, hMem);

    CloseClipboard();
}

std::wstring Clipboard::GetClipboardTextImpl()
{
    if (!mhWindow || !OpenClipboard(mhWindow))
        return {};

    std::wstring text;
    HANDLE hData = GetClipboardData(CF_UNICODETEXT);
    if (hData)
        text = ClipboardOperations::ExtractTextData(hData);

    CloseClipboard();
    return text;
}