#include "grabWindow.h"

#ifndef HINST_THISCOMPONENT
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
#define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase)
#endif

void grabWindow::ResetVariables() {
	msg = {};
    IsOpened = false;

    // Pointers
	hWnd        = NULL;
	rt          = NULL;
    Factory     = NULL;
}

LRESULT grabWindow::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    LRESULT result = 0;

    if (message == WM_CREATE) {
        LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
        grabWindow* pWindow = (grabWindow*)pcs->lpCreateParams;

        ::SetWindowLongPtrW(
            hWnd,
            GWLP_USERDATA,
            reinterpret_cast<LONG_PTR>(pWindow)
        );

        result = 1;
    }
    else
    {
        grabWindow* pWindow = reinterpret_cast<grabWindow*>(static_cast<LONG_PTR>(
            ::GetWindowLongPtrW(
                hWnd,
                GWLP_USERDATA
            )));

        bool wasHandled = false;

        if (pWindow)
        {
            switch (message)
            {
            case WM_SIZE:
            {
                if (pWindow->rt) pWindow->rt->Resize(D2D1::SizeU(LOWORD(lParam), HIWORD(lParam)));
            }
            result = 0;
            wasHandled = true;
            break;

            case WM_PAINT:
            {
                ValidateRect(hWnd, NULL);
            }
            result = 0;
            wasHandled = true;
            break;

            case WM_DISPLAYCHANGE:
            {
                InvalidateRect(hWnd, NULL, false);
            }
            result = 0;
            wasHandled = true;
            break;

            case WM_DESTROY:
            {
                PostQuitMessage(0);
                pWindow->IsOpened = false;
            }
            result = 1;
            wasHandled = true;
            break;
            }
        }

        if (!wasHandled)
        {
            result = DefWindowProc(hWnd, message, wParam, lParam);
        }
    }

    return result;
}

grabWindow::grabWindow() {
	ResetVariables();
}

grabWindow::grabWindow(grabFactory* factory, std::wstring title, int width, int height) {
    ResetVariables();
    std::wstring class_name = (title + L"_class");

    // Register the window class.
    WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = grabWindow::WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = sizeof(LONG_PTR);
    wcex.hInstance = HINST_THISCOMPONENT;
    wcex.hbrBackground = NULL;
    wcex.lpszMenuName = NULL;
    wcex.hCursor = LoadCursor(NULL, IDI_APPLICATION);
    wcex.lpszClassName = class_name.c_str();

    RegisterClassEx(&wcex);

    FLOAT dpiX, dpiY;

    Factory = factory;
    Factory->Direct2DFactory->GetDesktopDpi(&dpiX, &dpiY);


    // Create the window.
    hWnd = CreateWindow(
        //WS_EX_OVERLAPPEDWINDOW,
        class_name.c_str(),
        title.c_str(),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        static_cast<UINT>(ceil((float)width * dpiX / 96.f)),
        static_cast<UINT>(ceil((float)height * dpiY / 96.f)),
        NULL,
        NULL,
        HINST_THISCOMPONENT,
        this
    );

    HRESULT hr = hWnd ? S_OK : E_FAIL;
    if (SUCCEEDED(hr))
    {
        ShowWindow(hWnd, SW_SHOWNORMAL);
        UpdateWindow(hWnd);

        RECT rc;
        GetClientRect(hWnd, &rc);

        D2D1_SIZE_U size = D2D1::SizeU(
            rc.right - rc.left,
            rc.bottom - rc.top
        );

        // Create a Direct2D render target.
        hr = Factory->Direct2DFactory->CreateHwndRenderTarget(
            D2D1::RenderTargetProperties(),
            D2D1::HwndRenderTargetProperties(hWnd, size),
            &rt
        );

        IsOpened = true;
    }
}

void grabWindow::GetWindowMessage(grabWindowMessage* Message) {
    if (!IsOpened) {
        Message->CommonMessage.Type = grabMESSAGE_DESTROY;
        return;
    }
        
    bool bRet = GetMessage(&msg, nullptr, 0, 0);

    if (bRet == -1) {
        Message->CommonMessage.Type = grabMESSAGE_DESTROY;
        return;
    }

    TranslateMessage(&msg);

    Message->CommonMessage.Type = grabMESSAGE_NOT_DEFINED;

    switch (msg.message)
    {
    case WM_PAINT:
        Message->CommonMessage.Type = grabMESSAGE_PAINT;
        break;

    case WM_KEYDOWN:
        Message->CommonMessage.Type = grabMESSAGE_KEY_DOWN;
        Message->KeyDownMessage.Key = msg.wParam;
        Message->KeyDownMessage.PrevState = msg.lParam & 0x40000000;
        Message->KeyDownMessage.RepeatCount = msg.lParam & 0xFFFF;
        break;

    case WM_KEYUP:
        Message->CommonMessage.Type = grabMESSAGE_KEY_UP;
        Message->KeyUpMessage.Key = msg.wParam;
        break;

    case WM_TIMER:
        Message->CommonMessage.Type = grabMESSAGE_TIMER;
        Message->TimerMessage.ID = msg.wParam;
        break;

    case WM_SIZE:
        Message->CommonMessage.Type = grabMESSAGE_RESIZE;
        Message->ResizeMessage.Size = { LOWORD(msg.lParam), HIWORD(msg.lParam) };
        break;
    case WM_LBUTTONUP:
        Message->CommonMessage.Type = grabMESSAGE_MOUSE_BUTTON_UP;
        Message->MouseButtonUpMessage.Button = msg.wParam;

    default:
    {
        Message->CommonMessage.Milliseconds = msg.time;
        POINT MousePos = msg.pt;
        ScreenToClient(hWnd, &MousePos);
        Message->CommonMessage.MousePos = { (unsigned int)MousePos.x, (unsigned int)MousePos.y };
    }
        break;
    }

    if (Message->CommonMessage.Type == grabMESSAGE_NOT_DEFINED) {
        Message->NotDefinedMessage.Data = msg;
    }

    DispatchMessage(&msg);
}

void grabWindow::DrawSprite(grabSprite* spr) {
    rt->SetTransform(
        D2D1::Matrix3x2F::Scale(*((D2D1_SIZE_F*)(&spr->Scale)))
        * D2D1::Matrix3x2F::Translation(*((D2D1_SIZE_F*)(&spr->Position)))
    );

    D2D1_SIZE_F size = spr->Bitmap->GetSize();
    D2D1_RECT_F rectangle = D2D1::RectF(0, 0, size.width, size.height);
    rt->DrawBitmap(spr->Bitmap, rectangle, 1.f, D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, rectangle);
}

void grabWindow::Cleanup() {
    // TODO: что с этим делать?
}
