#include "Window.h"

#include "AlphaField.h"
#include "Border.h"
#include "Globals.h"
#include "Image.h"
#include "KeyboardEvent.h"
#include "MouseEvent.h"
#include "Screen.h"
#include "Scroll.h"
#include "Text.h"
#include "TextField.h"
#include "ToolTip.h"
#ifdef WIN32
#    include "Mouse.h"
#endif
#include <iostream>

#include "Logging.h"
#include "Timer.h"

using namespace Framework;
#ifdef WIN32
// Create window class
WNDCLASS Framework::F_Normal(HINSTANCE hInst) // Creates a normal window class
{
    if (!hInst) hInst = _hinst;
    WNDCLASS ret;
    ret.cbClsExtra = 0;
    ret.cbWndExtra = 0;
    ret.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    ret.hCursor = LoadCursor(NULL, IDC_ARROW);
    ret.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    ret.hInstance = hInst;
    ret.lpszMenuName = "";
    ret.lpfnWndProc = WindowProc;
    ret.style = CS_HREDRAW | CS_VREDRAW;
    return ret;
}

WNDCLASSEX Framework::F_NormalEx(HINSTANCE hInst)
{
    if (!hInst) hInst = _hinst;
    WNDCLASSEX ret;
    ret.cbSize = sizeof(WNDCLASSEX);
    ret.cbClsExtra = 0;
    ret.cbWndExtra = 0;
    ret.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    ret.hCursor = LoadCursor(NULL, IDC_ARROW);
    ret.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    ret.hInstance = hInst;
    ret.lpszMenuName = "";
    ret.lpfnWndProc = WindowProc;
    ret.style = CS_HREDRAW | CS_VREDRAW;
    ret.hIconSm = 0;
    return ret;
}

// WinAPI
LRESULT CALLBACK Framework::WindowProc(
    HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
    setShowCursor(cursorVisible);
    if (msgExit) return (DefWindowProc(hwnd, message, wparam, lparam));
    switch (message)
    {
    case WM_SETCURSOR: // Mouse cursor
        mousePointer.update();
        return 0;
    case WM_SIZE: // Window
        if (wparam == SIZE_RESTORED) nativeWindows.sendRestoreMessage(hwnd);
        break;
    case WM_CLOSE: // Close
        if (nativeWindows.sendPreCloseMessage(hwnd)) return 0;
        break;
    case WM_DESTROY:
        if (nativeWindows.sendPostCloseMessage(hwnd)) return 0;
        break;           // Mouse
    case WM_LBUTTONDOWN: // Left click
        {
            MouseState[M_Left] = 1;
            MouseEvent me = {ME_PLeft,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam),
                0,
                1,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam)};
            nativeWindows.sendMouseMessage(hwnd, me);
            break;
        }
    case WM_RBUTTONDOWN: // Right click
        {
            MouseState[M_Right] = 1;
            MouseEvent me = {ME_PRight,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam),
                0,
                1,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam)};
            nativeWindows.sendMouseMessage(hwnd, me);
            break;
        }
    case WM_MBUTTONDOWN: // Middle click
        {
            MouseState[M_Middle] = 1;
            MouseEvent me = {ME_PMiddle,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam),
                0,
                1,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam)};
            nativeWindows.sendMouseMessage(hwnd, me);
            break;
        }
    case WM_LBUTTONUP: // Left release
        {
            MouseState[M_Left] = 0;
            MouseEvent me = {ME_RLeft,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam),
                0,
                1,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam)};
            nativeWindows.sendMouseMessage(hwnd, me);
            break;
        }
    case WM_RBUTTONUP: // Right release
        {
            MouseState[M_Right] = 0;
            MouseEvent me = {ME_RRight,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam),
                0,
                1,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam)};
            nativeWindows.sendMouseMessage(hwnd, me);
            break;
        }
    case WM_MBUTTONUP: // Middle release
        {
            MouseState[M_Middle] = 0;
            MouseEvent me = {ME_RMiddle,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam),
                0,
                1,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam)};
            nativeWindows.sendMouseMessage(hwnd, me);
            break;
        }
    case WM_LBUTTONDBLCLK: // Left double click
        {
            MouseEvent me = {ME_DCLeft,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam),
                0,
                1,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam)};
            nativeWindows.sendMouseMessage(hwnd, me);
            break;
        }
    case WM_RBUTTONDBLCLK: // Right double click
        {
            MouseEvent me = {ME_DCRight,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam),
                0,
                1,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam)};
            nativeWindows.sendMouseMessage(hwnd, me);
            break;
        }
    case WM_MBUTTONDBLCLK: // Middle double click
        {
            MouseEvent me = {ME_DCMiddle,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam),
                0,
                1,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam)};
            nativeWindows.sendMouseMessage(hwnd, me);
            break;
        }
    case WM_MOUSEHOVER: // Mouse enters window
        {
            MouseTrack = 1;
            MouseEvent me = {ME_Enter,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam),
                0,
                1,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam)};
            nativeWindows.sendMouseMessage(hwnd, me);
            break;
        }
    case WM_MOUSELEAVE: // Mouse leaves window
        {
            MouseTrack = 1;
            MouseEvent me = {ME_Leaves,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam),
                0,
                1,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam)};
            nativeWindows.sendMouseMessage(hwnd, me);
            break;
        }
    case WM_MOUSEMOVE: // Mouse is moving
        {
            if (MouseTrack)
            {
                TRACKMOUSEEVENT lptme;
                lptme.cbSize = sizeof(TRACKMOUSEEVENT);
                lptme.dwFlags = TME_HOVER | TME_LEAVE;
                lptme.dwHoverTime = 0;
                lptme.hwndTrack = hwnd;
                TrackMouseEvent(&lptme);
                MouseTrack = 0;
            }
            MouseEvent me = {ME_Move,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam),
                0,
                1,
                (int)LOWORD(lparam),
                (int)HIWORD(lparam)};
            nativeWindows.sendMouseMessage(hwnd, me);
            break;
        }
    case WM_MOUSEWHEEL: // Mouse scroll
        {
            Point pos = getMousePos();
            RECT r;
            GetWindowRect(hwnd, &r);
            pos.x -= r.left;
            pos.y -= r.top;
            MouseEvent me = {0, pos.x, pos.y, 0, 1, pos.x, pos.y};
            if ((int)(short)HIWORD(wparam) < 0)
                me.id = !getKeyState(T_Shift) ? ME_DScroll : ME_RScroll;
            else
                me.id = !getKeyState(T_Shift) ? ME_UScroll : ME_LScroll;
            nativeWindows.sendMouseMessage(hwnd, me);
            break;
        }
        // Keyboard
    case WM_KEYDOWN:
        {
            KeyboardEvent te = {
                TE_Press, {0, 0, 0},
                 0, 0
            };
            CalculateEnteredString((int)wparam, 0, te);
            nativeWindows.sendKeyboardMessage(hwnd, te);
            return 0;
        } // Key is pressed
    case WM_KEYUP:
        {
            KeyboardEvent te = {
                TE_Release, {0, 0, 0},
                 0, 0
            };
            CalculateEnteredString((int)wparam, HIWORD(lparam) & 0xFF, te);
            nativeWindows.sendKeyboardMessage(hwnd, te);
            return 0;
        } // Key is released
    }
    return (DefWindowProc(hwnd, message, wparam, lparam));
}

void Framework::StartMessageLoop()
{
    MSG msg;
    Timer tm;
    while (GetMessage(&msg, NULL, 0, 0) > 0 && !msgExit)
    {
        if (!msgExit)
        {
            tm.measureStart();
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            tm.measureEnd();
            if (tm.getSekunden() > 0.1)
            {
                Logging::warning()
                    << "Message loop is taking too long: "
                    << std::to_string(tm.getSekunden()) << " seconds";
            }
        }
    }
    msgExit = 0;
}

void Framework::StopMessageLoop(HWND hwnd)
{
    msgExit = 1;
    PostMessage(hwnd, WM_MOUSELEAVE, 0, 0);
}

void Framework::CalculateEnteredString(
    int virtualKeyCode, int scanCode, KeyboardEvent& te)
{
    memset(te.key, 0, sizeof(te.key));
    te.virtualKey = virtualKeyCode;
#    ifdef WIN32
    unsigned char keyState[256];
    memset(keyState, 0, sizeof(keyState));
    if (GetKeyboardState(keyState))
    {
        unsigned short str[2] = {0, 0};
        int count = ToAscii(virtualKeyCode, scanCode, keyState, str, 0);
        if (count == 1 && (str[0] & 0xFF) == str[0])
            te.key[0] = (unsigned char)str[0];
        else if (count == 2 && (str[0] & 0xFF) == str[0]
                 && (str[1] & 0xFF) == str[1])
            te.key[0] = (unsigned char)str[0],
            te.key[1] = (unsigned char)str[1];
    }
#    endif // WIN32
}

// Contents of the NativeWindow class from Window.h
// Constructor
NativeWindow::NativeWindow()
    : ReferenceCounter(),
      hWnd(0),
      style(0),
      makParam(0),
      sakParam(0),
      takParam(0),
      mouseAction(0),
      preCloseAction(0),
      postCloseAction(0),
      keyboardAction(0),
      screen(0),
      mx(-1),
      my(-1),
      movable(0),
      border(0),
      bitmap(0),
      hdc(0)
{
    nativeWindows.addWindow(this);
}

NativeWindow::NativeWindow(HWND hwnd)
    : NativeWindow()
{
    hWnd = hwnd;
}

// Destructor
NativeWindow::~NativeWindow()
{
    if (nativeWindows.removeWindow(this))
    {
        nativeWindows.del();
    }
    if (hWnd) destroy();
    if (screen) screen->release();
}

// non-constant
void NativeWindow::create(int style, WNDCLASS wc) // Creates a window in Windows
{
    if (!GetClassInfo(wc.hInstance, wc.lpszClassName, &wc))
    {
#    pragma warning(suppress : 6102)
        if (!RegisterClass(&wc)) // Register window class
        {
            MessageBox(hWnd,
                "Fehler beim Registrieren der WindowClass!",
                "Error",
                MB_ICONERROR); // Error message on failure
            return;
        }
    }

    hWnd = CreateWindow(wc.lpszClassName,
        wc.lpszClassName,
        style,
        0,
        0,
        0,
        0,
        0,
        0,
        wc.hInstance,
        0); // Create window

    if (hWnd == 0)
    {
        MessageBox(hWnd,
            "Fehler beim create des Windows!",
            "Error",
            MB_ICONERROR); // Error message on failure
        return;
    }
    this->style = style;
}

void NativeWindow::createEx(
    int exStyle, int style, WNDCLASSEX wc) // Create the window
{
    if (!GetClassInfoEx(wc.hInstance, wc.lpszClassName, &wc))
    {
#    pragma warning(suppress : 6102)
        if (!RegisterClassEx(&wc)) // Register window class
        {
            MessageBox(hWnd,
                "Fehler beim Registrieren der WindowClass!",
                "Error",
                MB_ICONERROR); // Error message on failure
            return;
        }
    }

    hWnd = CreateWindowEx(exStyle,
        wc.lpszClassName,
        wc.lpszClassName,
        style,
        0,
        0,
        0,
        0,
        0,
        0,
        wc.hInstance,
        0); // Create window

    if (hWnd == 0)
    {
        MessageBox(hWnd,
            "Fehler beim create des Windows!",
            "Error",
            MB_ICONERROR); // Error message on failure
        return;
    }
    this->style = style;
}

void NativeWindow::setDisplayMode(int mod) // Display window
{
    if (border)
    {
        if (mod == 2)
            ShowWindow(border, 0);
        else
            ShowWindow(border, mod);
    }
    ShowWindow(hWnd, mod);
}

bool NativeWindow::setFocus() // Sets the focus on the window
{
    DWORD dwCurrentThread = GetCurrentThreadId();
    DWORD dwFGThread = GetWindowThreadProcessId(GetForegroundWindow(), NULL);
    AttachThreadInput(dwCurrentThread, dwFGThread, TRUE);
    SetFocus(hWnd);
    AttachThreadInput(dwCurrentThread, dwFGThread, FALSE);
    SetWindowPos(hWnd,
        HWND_TOPMOST,
        0,
        0,
        0,
        0,
        SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
    SetWindowPos(hWnd,
        HWND_NOTOPMOST,
        0,
        0,
        0,
        0,
        SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
    return GetFocus() == hWnd;
}

void NativeWindow::setPosition(const Point& p) // Window position
{
    setPosition(p.x, p.y);
}

void NativeWindow::setPosition(int x, int y)
{
    RECT r;
    GetWindowRect(hWnd, &r); // Find previous position
    RECT res;
    res.left = x, res.top = y, res.right = r.right - r.left,
    res.bottom = r.bottom - r.top;
    AdjustWindowRect(&res, style, 0);
    SetWindowPos(hWnd,
        0,
        res.left,
        res.top,
        res.right,
        res.bottom,
        0); // Change position
}

void NativeWindow::setSize(Point& g) // Window size
{
    RECT r;
    GetWindowRect(hWnd, &r); // Find previous size
    RECT res;
    res.left = r.left, res.top = r.top, res.right = g.x, res.bottom = g.y;
    AdjustWindowRect(&res, style, 0);
    SetWindowPos(
        hWnd, 0, res.left, res.top, res.right, res.bottom, 0); // Change size
}

void NativeWindow::setSize(int Width, int height)
{
    RECT r;
    GetWindowRect(hWnd, &r); // Find previous size
    RECT res;
    res.left = r.left, res.top = r.top, res.right = Width, res.bottom = height;
    AdjustWindowRect(&res, style, 0);
    SetWindowPos(
        hWnd, 0, res.left, res.top, res.right, res.bottom, 0); // Change size
}

void NativeWindow::setBounds(
    const Point& p, const Point& g) // sets size and position
{
    SetWindowPos(hWnd, 0, p.x, p.y, g.x, g.y, 0); // Change size and position
}

void NativeWindow::setScreen(Screen* screen)
{
    if (this->screen) this->screen->release();
    this->screen = screen;
}

void NativeWindow::destroy()
{
    DestroyWindow(hWnd);
    hWnd = 0;
    if (border) DestroyWindow(border);
    border = 0;
    if (bitmap) DeleteObject(bitmap);
    bitmap = 0;
    if (hdc) DeleteDC(hdc);
    hdc = 0;
}

void NativeWindow::doMouseAction(MouseEvent& me)
{
    if (!mouseAction || !mouseAction(makParam, this, me)) return;
    if (screen && me.id != ME_Enter && me.id != ME_Leaves)
    {
        screen->doMouseEvent(me);
        if (!me.processed && movable)
        {
            if (mx != -1 && my != -1) // move
            {
                RECT r;
                if (border)
                {
                    GetWindowRect(border, &r);
                    r.right -= r.left, r.bottom -= r.top;
                    r.left += me.mx - mx;
                    r.top += me.my - my;
                    SetWindowPos(border,
                        0,
                        r.left,
                        r.top,
                        r.right,
                        r.bottom,
                        0); // Set position
                }
                GetWindowRect(hWnd, &r);              // Find previous position
                r.right -= r.left, r.bottom -= r.top; // Update position
                r.left += me.mx - mx;
                r.top += me.my - my;
                SetWindowPos(hWnd,
                    0,
                    r.left,
                    r.top,
                    r.right,
                    r.bottom,
                    0); // Set position
            }
            else if (me.id == ME_PLeft) // start moving
                mx = me.mx, my = me.my;
            if (me.id == ME_RLeft) // stop moving
                mx = -1, my = -1;
            me.processed = 1;
        }
    }
}

void NativeWindow::doPreCloseAction()
{
    if (!preCloseAction) return;
    preCloseAction(sakParam, this);
}

void NativeWindow::doPostCloseAction()
{
    if (!postCloseAction) return;
    postCloseAction(sakParam, this);
}

void NativeWindow::doKeyboardAction(KeyboardEvent& te)
{
    if (!keyboardAction || !keyboardAction(takParam, this, te)) return;
    if (screen) screen->doKeyboardEvent(te);
}

void NativeWindow::doRestoreMessage() // makes the border visible
{
    if (border) ShowWindow(border, 1);
    ShowWindow(hWnd, 1);
}

void NativeWindow::setMouseEventParameter(
    void* p) // sets the mouse event parameter
{
    makParam = p;
}

void NativeWindow::setCloseEventParameter(
    void* p) // sets the close event parameter
{
    sakParam = p;
}

void NativeWindow::setKeyboardEventParameter(
    void* p) // sets the keyboard event parameter
{
    takParam = p;
}

void NativeWindow::setMouseAction(MouseAction mouseAk)
{
    mouseAction = mouseAk;
}

void NativeWindow::setPreCloseAction(
    std::function<void(void*, void*)> vSchliessAk)
{
    preCloseAction = vSchliessAk;
}

void NativeWindow::setPostCloseAction(
    std::function<void(void*, void*)> nSchliessAk)
{
    postCloseAction = nSchliessAk;
}

void NativeWindow::setKeyboardAction(KeyboardAction tastaturAk)
{
    keyboardAction = tastaturAk;
}

void NativeWindow::setWindowHandle(HWND hWnd) // sets the operation window
{
    this->hWnd = hWnd;
}

void NativeWindow::setMovable(
    bool movable) // determines whether the window can be
                  // moved by dragging with the mouse
{
    this->movable = movable;
}

void NativeWindow::loadBorderWindow(Image* zImage,
    HINSTANCE hinst) // sets a transparent border around the window
{
    if (!zImage) return;
    // Create window
    WNDCLASSEX wcl = {0};
    wcl.cbSize = sizeof(wcl);
    wcl.style = CS_HREDRAW | CS_VREDRAW;
    wcl.lpfnWndProc = WindowProc;
    wcl.cbClsExtra = 0;
    wcl.cbWndExtra = 0;
    wcl.hInstance = hinst;
    wcl.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wcl.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcl.hbrBackground = NULL;
    wcl.lpszMenuName = NULL;
    wcl.lpszClassName = TEXT("LayeredWindowClass");
    wcl.hIconSm = NULL;

    // Create bitmap
    hdc = CreateCompatibleDC(NULL);
    if (!hdc) return;
    BITMAPINFO info;
    info.bmiHeader.biSize = sizeof(info.bmiHeader);
    info.bmiHeader.biBitCount = 32;
    info.bmiHeader.biWidth = zImage->getWidth();
    info.bmiHeader.biHeight = -zImage->getHeight();
    info.bmiHeader.biCompression = BI_RGB;
    info.bmiHeader.biPlanes = 1;
    unsigned char* pPixels = 0;
    bitmap
        = CreateDIBSection(hdc, &info, DIB_RGB_COLORS, (void**)&pPixels, 0, 0);
    if (!bitmap) DeleteDC(hdc);
    GdiFlush();
    // Fill bitmap
    int pitch = ((zImage->getWidth() * 32 + 31) & ~31) >> 3;
    unsigned char* pRow = 0;
    int* buffer = zImage->getBuffer();
    for (int i = 0; i < zImage->getHeight(); ++i)
    {
        pRow = &pPixels[i * pitch];
        for (int i2 = 0; i2 < zImage->getWidth(); ++i2)
        {
            pRow[i2 * 4]
                = (unsigned char)((buffer[i2 + i * zImage->getWidth()] >> 16)
                                  & 0xFF);
            pRow[i2 * 4 + 1]
                = (unsigned char)((buffer[i2 + i * zImage->getWidth()] >> 8)
                                  & 0xFF);
            pRow[i2 * 4 + 2]
                = (unsigned char)((buffer[i2 + i * zImage->getWidth()]) & 0xFF);
            pRow[i2 * 4 + 3]
                = (unsigned char)((buffer[i2 + i * zImage->getWidth()] >> 24)
                                  & 0xFF);
        }
    }
    // Calculate alpha
    unsigned char* pPixel = 0;
    if (zImage->getWidth() * 4 == pitch)
    {
        int i = 0;
        int totalBytes = zImage->getWidth() * zImage->getHeight() * 4;

        for (i = 0; i < totalBytes; i += 4)
        {
            pPixel = &pPixels[i];
            pPixel[0] *= (unsigned char)((float)pPixel[3] / 255.0f);
            pPixel[1] *= (unsigned char)((float)pPixel[3] / 255.0f);
            pPixel[2] *= (unsigned char)((float)pPixel[3] / 255.0f);
        }
    }
    else
    {
        int x = 0;
        int y = 0;

        for (y = 0; y < zImage->getHeight(); ++y)
        {
            for (x = 0; x < zImage->getWidth(); ++x)
            {
                pPixel = &pPixels[(y * pitch) + (x * 4)];
                pPixel[0] *= (unsigned char)((float)pPixel[3] / 255.0f);
                pPixel[1] *= (unsigned char)((float)pPixel[3] / 255.0f);
                pPixel[2] *= (unsigned char)((float)pPixel[3] / 255.0f);
            }
        }
    }
    // Continue creating window
    if (RegisterClassEx(&wcl))
    {
        border = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_LAYERED,
            wcl.lpszClassName,
            TEXT("Transparentes Window"),
            WS_POPUP,
            0,
            0,
            zImage->getWidth(),
            zImage->getHeight(),
            0,
            0,
            wcl.hInstance,
            0);
        if (border)
        {
            // draw
            HDC h = 0;
            if ((h = GetDC(border)) && bitmap)
            {
                HGDIOBJ hPrevObj = NULL;
                POINT ptDest = {0, 0};
                POINT ptSrc = {0, 0};
                SIZE client = {zImage->getWidth(), zImage->getHeight()};
                BLENDFUNCTION blendFunc = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};

                hPrevObj = SelectObject(hdc, bitmap);
                ClientToScreen(border, &ptDest);

                UpdateLayeredWindow(border,
                    h,
                    &ptDest,
                    &client,
                    hdc,
                    &ptSrc,
                    0,
                    &blendFunc,
                    ULW_ALPHA);

                SelectObject(hdc, hPrevObj);
                ReleaseDC(border, h);
            }
            UpdateWindow(border);
            Point pos = getPosition() + (getSize() - zImage->getSize()) / 2;
            SetWindowPos(border,
                0,
                pos.x,
                pos.y,
                zImage->getWidth(),
                zImage->getHeight(),
                0);
        }
    }
}

// constant
Point NativeWindow::getPosition() const // returns the position
{
    RECT r;
    GetWindowRect(hWnd, &r); // Find position
    return {r.left, r.top};
}

Point NativeWindow::getSize() const // returns the size
{
    RECT r;
    GetWindowRect(hWnd, &r); // Find size
    return {r.right - r.left, r.bottom - r.top};
}

Point NativeWindow::getBodySize() const // returns the window body size
{
    RECT r;
    GetClientRect(hWnd, &r); // Find size
    return {r.right - r.left, r.bottom - r.top};
}

int NativeWindow::getBodyWidth() const // returns the window body width
{
    RECT r;
    GetClientRect(hWnd, &r); // Find size
    return r.right;
}

int NativeWindow::getBodyHeight() const // returns the window body height
{
    RECT r;
    GetClientRect(hWnd, &r); // Find size
    return r.bottom;
}

HWND NativeWindow::getWindowHandle() const // returns a handle to the window
{
    return hWnd;
}

bool NativeWindow::hasMouseAction() const
{
    return mouseAction != 0;
}

bool NativeWindow::hasPreCloseAction() const
{
    return preCloseAction != 0;
}

bool NativeWindow::hasPostCloseAction() const
{
    return postCloseAction != 0;
}

bool NativeWindow::hasKeyboardAction() const
{
    return keyboardAction != 0;
}

Screen* NativeWindow::getScreen() const
{
    if (!screen) return 0;
    return dynamic_cast<Screen*>(screen->getThis());
}

Screen* NativeWindow::zScreen() const
{
    return screen;
}

bool NativeWindow::isMovable()
    const // checks if the window can be moved by dragging with the mouse
{
    return movable;
}

// Contents of the NativeWindowArray class from Window.h
// Constructor
NativeWindowArray::NativeWindowArray()
    : next(0),
      This(0)
{}

// Destructor
NativeWindowArray::~NativeWindowArray()
{
    if (next) delete next;
}

// add und remove
bool NativeWindowArray::addWindow(NativeWindow* fenster)
{
    if (fenster == This) return 0;
    if (!This)
    {
        This = fenster;
        return 1;
    }
    if (!next) next = new NativeWindowArray();
    return next->addWindow(fenster);
}

bool NativeWindowArray::removeWindow(NativeWindow* fenster)
{
    if (fenster == This) return 1;
    if (!next) return 0;
    if (next->removeWindow(fenster))
    {
        NativeWindowArray* tmp = next->getNext();
        next->setNext0();
        delete next;
        next = tmp;
    }
    return 0;
}

NativeWindowArray* NativeWindowArray::getNext()
{
    return next;
}

void NativeWindowArray::setNext0()
{
    next = 0;
}

void NativeWindowArray::del()
{
    if (next)
    {
        This = next->getThis();
        NativeWindowArray* tmp = next->getNext();
        next->setNext0();
        delete next;
        next = tmp;
    }
    else
        This = 0;
}

// Messages
bool NativeWindowArray::sendPreCloseMessage(HWND hwnd)
{
    if (!This) return 0;
    bool ret = 0;
    if (This->getWindowHandle() == hwnd && This->hasPreCloseAction())
    {
        This->doPreCloseAction();
        ret = 1;
    }
    if (!next) return ret;
    return ret || next->sendPreCloseMessage(hwnd);
}

bool NativeWindowArray::sendPostCloseMessage(HWND hwnd)
{
    if (!This) return 0;
    bool ret = 0;
    if (This->getWindowHandle() == hwnd && This->hasPostCloseAction())
    {
        This->doPostCloseAction();
        ret = 1;
    }
    if (!next) return ret;
    return ret || next->sendPostCloseMessage(hwnd);
}

bool NativeWindowArray::sendMouseMessage(HWND hwnd, MouseEvent& me)
{
    if (!This) return 0;
    bool ret = 0;
    if (This->getWindowHandle() == hwnd && This->hasMouseAction())
    {
        This->doMouseAction(me);
        ret = 1;
    }
    if (!next) return ret;
    return ret || next->sendMouseMessage(hwnd, me);
}

bool NativeWindowArray::sendKeyboardMessage(HWND hwnd, KeyboardEvent& te)
{
    if (!This) return 0;
    bool ret = 0;
    if (This->getWindowHandle() == hwnd && This->hasKeyboardAction())
    {
        This->doKeyboardAction(te);
        ret = 1;
    }
    if (!next) return ret;
    return ret || next->sendKeyboardMessage(hwnd, te);
}

bool NativeWindowArray::sendRestoreMessage(HWND hwnd)
{
    if (!This) return 0;
    bool ret = 0;
    if (This->getWindowHandle() == hwnd && This->hasMouseAction())
    {
        This->doRestoreMessage();
        ret = 1;
    }
    if (!next) return ret;
    return ret || next->sendRestoreMessage(hwnd);
}

NativeWindow* NativeWindowArray::getThis()
{
    return This;
}

// WMessageBox
void Framework::WMessageBox(HWND hWnd, Text* title, Text* meldung, UINT style)
{
    MessageBox(
        hWnd, meldung->getText(), title->getText(), style); // Message Box
    title->release();
    meldung->release();
}
#endif

// Contents of the Window class from Window.h
// Constructor
Window::Window()
    : Drawable(),
      closingMe(0),
      closingMeParam(0),
      border(0),
      title(0),
      members(new RCArray<Drawable>()),
      bgBodyColor(0xFF000000),
      bgBodyPicture(0),
      bodyBuffer(0),
      bgClosingColor(0xFF000000),
      bgClosingImage(0),
      closeBuffer(0),
      closeClickBuffer(0),
      vScroll(0),
      hScroll(0),
      kMin(0, 0),
      kMax(0, 0),
      closeClick(0),
      click(0),
      moving(0),
      mx(0),
      my(0)
{
    style = Style::MEIgnoreProcessed | Style::MEIgnoreVisible
          | Style::MEIgnoreParentInside | Style::MEIgnoreInside;
    this->setMouseEvent(_ret1ME);
    this->setKeyboardEvent(_ret1TE);
    min = Point(0, 0), max = Point(0, 0);
}

// Destructor
Window::~Window()
{
    if (border) border->release();
    if (title) title->release();
    members->release();
    if (bodyBuffer) bodyBuffer->release();
    if (bgBodyPicture) bgBodyPicture->release();
    if (bgClosingImage) bgClosingImage->release();
    if (closeBuffer) closeBuffer->release();
    if (closeClickBuffer) closeClickBuffer->release();
    if (vScroll) vScroll->release();
    if (hScroll) hScroll->release();
}

void Window::doMouseEvent(MouseEvent& me, bool userRet)
{
    if (me.id == ME_Leaves)
    {
        if (closeClick != 0) rend = 1;
        closeClick = 0, click = 0;
    }
    if (hasStyleNot(Style::Visible) || !me.insideParent || me.processed
        || me.mx < 0 || me.my < 0 || me.mx >= gr.x || me.my >= gr.y || !userRet)
    {
        bool insideParent = me.insideParent;
        bool processed = me.processed;
        me.processed |= hasStyleNot(Style::Visible);
        me.insideParent = 0;
        int rbr = 0;
        if (hasStyle(Style::Border) && border) rbr = border->getRWidth();
        int th = 0;
        if (hasStyle(Style::Title) && title) th = title->getHeight();
        me.mx -= rbr;
        me.my -= rbr + th;
        if (hasStyle(Style::VScroll) && vScroll) me.my += vScroll->getScroll();
        if (hasStyle(Style::HScroll) && hScroll) me.mx += hScroll->getScroll();
        if (me.id != ME_Enter && me.id != ME_Leaves)
        {
            for (int i = members->getEntryCount() - 1; i >= 0; i--)
                members->z(i)->doPublicMouseEvent(me);
        }
        me.mx += rbr;
        me.my += rbr + th;
        if (hasStyle(Style::VScroll) && vScroll) me.my -= vScroll->getScroll();
        if (hasStyle(Style::HScroll) && hScroll) me.mx -= hScroll->getScroll();
        me.insideParent = insideParent;
        if (hasStyleNot(Style::Visible)) me.processed = processed;
        if (!moving) return;
    }
    bool mvtmp = me.processed;
    if (hasStyleNot(Style::Allowed)) me.processed = 1;
    int rbr = 0;
    if (hasStyle(Style::Border) && border) rbr = border->getRWidth();
    int th = 0;
    if (hasStyle(Style::Title) && title) th = title->getHeight();
    bool hSc = hasStyle(Style::HScroll) && hScroll;
    bool vSc = hasStyle(Style::VScroll) && vScroll;
    if (me.id == ME_Move && moving && getMouseState(M_Left))
    {
        if (hasStyle(Style::Movable) || hasStyle(Style::HeightChangeable)
            || hasStyle(Style::WidthChangeable)
            || hasStyle(Style::TitleHeightChangeable))
        {
            bool ret1 = 0;
            bool mset = 0;
            int schi = 0;
            int scbr = 0;
            if (hSc)
            {
                schi = 15;
            }
            if (vSc)
            {
                scbr += 15;
            }
            bool minXb, maxXb, minYb, maxYb;
            bool kMinXb, kMaxXb, kMinYb, kMaxYb;
            minXb = hasStyle(Style::MinWidth);
            maxXb = hasStyle(Style::MaxWidth);
            minYb = hasStyle(Style::MinHeight);
            maxYb = hasStyle(Style::MaxHeight);
            kMinXb = hasStyle(Style::BodyMinWidth);
            kMaxXb = hasStyle(Style::BodyMaxWidth);
            kMinYb = hasStyle(Style::BodyMinHeight);
            kMaxYb = hasStyle(Style::BodyMaxHeight);
            int fMinWidth = rbr * 2 + scbr;
            if (minXb) fMinWidth = fMinWidth < min.x ? min.x : fMinWidth;
            if (kMinXb)
                fMinWidth = fMinWidth < (rbr * 2 + kMin.x + scbr)
                              ? (rbr * 2 + kMin.x + scbr)
                              : fMinWidth;
            int fMinHeight = rbr * 2 + th + schi;
            if (minYb) fMinHeight = fMinHeight < min.y ? min.y : fMinHeight;
            if (kMinYb)
                fMinHeight = fMinHeight < (rbr * 2 + kMin.y + th + schi)
                               ? (rbr * 2 + kMin.y + th + schi)
                               : fMinHeight;
            int fMaxWidth = 0;
            if (maxXb) fMaxWidth = max.x;
            if (kMaxXb)
                fMaxWidth = fMaxWidth < (rbr * 2 + kMax.x + scbr)
                              ? (rbr * 2 + kMax.x + scbr)
                              : fMaxWidth;
            int fMaxHeight = 0;
            if (maxYb) fMaxHeight = max.y;
            if (kMaxYb)
                fMaxHeight = fMaxHeight < (rbr * 2 + kMax.y + th + schi)
                               ? (rbr * 2 + kMax.y + th + schi)
                               : fMaxHeight;
            minXb |= kMinXb, maxXb |= kMaxXb, minYb |= kMinYb, maxYb |= kMaxYb;
            if (hasStyle(Style::HeightChangeable))
            {
                if ((moving | 0x1) == moving) // top
                {
                    pos.y -= my - me.my;
                    gr.y += my - me.my;
                    if (gr.y < fMinHeight)
                    {
                        pos.y -= fMinHeight - gr.y;
                        gr.y = fMinHeight;
                    }
                    else if (maxYb && gr.y > fMaxHeight)
                    {
                        pos.y -= gr.y - fMaxHeight;
                        gr.y = fMaxHeight;
                    }
                    else if (vSc)
                        vScroll->getScrollData()->anzeige = gr.y;
                    rend = 1;
                    ret1 = 1;
                }
                else if ((moving | 0x2) == moving) // bottom
                {
                    gr.y += me.my - my;
                    if (gr.y < fMinHeight)
                        gr.y = fMinHeight;
                    else if (maxYb && gr.y > fMaxHeight)
                        gr.y = fMaxHeight;
                    else
                    {
                        mset = 1;
                        if (vSc) vScroll->getScrollData()->anzeige = gr.y;
                    }
                    rend = 1;
                    ret1 = 1;
                }
            }
            if (hasStyle(Style::WidthChangeable))
            {
                if ((moving | 0x4) == moving) // left
                {
                    pos.x -= mx - me.mx;
                    gr.x += mx - me.mx;
                    if (gr.x < fMinWidth)
                    {
                        pos.x -= fMinWidth - gr.x;
                        gr.x = fMinWidth;
                    }
                    else if (maxXb && gr.x > fMaxWidth)
                    {
                        pos.x -= gr.x - fMaxWidth;
                        gr.x = fMaxWidth;
                    }
                    else if (hSc)
                        hScroll->getScrollData()->anzeige = gr.x;
                    rend = 1;
                    ret1 = 1;
                }
                else if ((moving | 0x8) == moving) // right
                {
                    gr.x += me.mx - mx;
                    if (gr.x < rbr * 2 + fMinWidth)
                        gr.x = rbr * 2 + fMinWidth;
                    else if (maxXb && gr.x > fMaxWidth)
                        gr.x = fMaxWidth;
                    else
                    {
                        mset = 1;
                        if (hSc) hScroll->getScrollData()->anzeige = gr.x;
                    }
                    rend = 1;
                    ret1 = 1;
                }
            }
            if (hasStyle(Style::TitleHeightChangeable) && title
                && (moving | 0x10) == moving) // title height
            {
                int maxTh = gr.y - rbr * 2 - schi;
                if (kMaxYb)
                    maxTh = maxTh < (gr.x - rbr * 2 - kMin.y)
                              ? maxTh
                              : (gr.x - rbr * 2 - kMin.y);
                if (hasStyle(Style::Closable))
                    maxTh = (gr.x - th - 5 - rbr * 2 - me.my + my) < 0 ? th
                                                                       : maxTh;
                title->setSize(
                    title->getWidth(), title->getHeight() + me.my - my);
                if (title->getHeight() > maxTh)
                    title->setSize(title->getWidth(), maxTh);
                else if (title->getHeight() < 5)
                    title->setSize(title->getWidth(), 5);
                else
                    mset = 1;
                rend = 1;
                ret1 = 1;
            }
            if (ret1)
            {
                if (mset) mx = me.mx, my = me.my;
                me.processed = 1;
            }
            if (hasStyle(Style::Movable) && (moving | 0x20) == moving) // move
            {
                pos.x += me.mx - mx;
                pos.y += me.my - my;
                rend = 1;
                ret1 = 1;
            }
            if (ret1) me.processed = 1;
        }
    }
    if (me.id == ME_RLeft)
    {
        if (closeClick) rend = 1;
        closeClick = 0, click = 0;
        moving = 0;
        mx = -1, my = -1;
    }
    if (me.id == ME_Leaves)
    {
        if (closeClick != 0) rend = 1;
        closeClick = 0, click = 0;
    }
    if (hasStyleNot(Style::Visible) || !me.insideParent || me.processed
        || me.mx < 0 || me.my < 0 || me.mx >= gr.x || me.my >= gr.y || !userRet)
        return;
    if (!me.processed)
    {
        if (hasStyle(Style::Closable) && me.my <= th + rbr
            && me.mx >= gr.x + rbr - th && me.my >= rbr && me.mx <= gr.x - rbr)
        {
            if (!closingMe || closingMe(closingMeParam, this, me))
            {
                if (me.id == ME_PLeft)
                {
                    closeClick = 1;
                    rend = 1;
                }
                if (!closeClick && MouseState[M_Left])
                {
                    closeClick = 1;
                    rend = 1;
                }
                me.processed = 1;
            }
        }
        else if (closeClick)
        {
            closeClick = 0;
            rend = 1;
        }
        if (me.id == ME_PLeft && !closeClick)
        {
            click = 1;
            mx = me.mx, my = me.my;
            if (me.mx >= 0 && me.mx < gr.x && me.my >= 0 && me.my < rbr + 5
                && !hasStyle(Style::TopPositionFixed))
                moving |= 0x1;
            if (me.mx >= 0 && me.mx < gr.x && me.my >= gr.y - (rbr + 5)
                && me.my < gr.y && !hasStyle(Style::BottomPositionFixed))
                moving |= 0x2;
            if (me.mx >= 0 && me.mx < rbr + 5 && me.my >= 0 && me.my < gr.y
                && !hasStyle(Style::LeftPositionFixed))
                moving |= 0x4;
            if (me.mx >= gr.x - rbr - 5 && me.mx < gr.x && me.my >= 0
                && me.my < gr.y && !hasStyle(Style::RightPositionFixed))
                moving |= 0x8;
            if (title && me.mx >= 0 && me.mx < gr.x
                && me.my >= title->getHeight()
                && me.my < title->getHeight() + 2 * (rbr + 5))
                moving |= 0x10;
            if (title && me.mx >= 0 && me.mx < gr.x && me.my >= rbr + 5
                && me.my < title->getHeight())
                moving |= 0x20;
        }
    }
    if (vSc)
    {
        vScroll->doMouseMessage(
            gr.x - rbr - 15, rbr + th, 15, gr.y - rbr * 2 - th, me);
        if (hSc)
            hScroll->doMouseMessage(
                rbr, gr.y - rbr - 15, gr.x - rbr * 2 - 15, 15, me);
    }
    else if (hSc)
        hScroll->doMouseMessage(rbr, gr.y - rbr - 15, gr.x - rbr * 2, 15, me);
    me.mx -= rbr;
    me.my -= rbr + th;
    if (hasStyle(Style::VScroll) && vScroll) me.my += vScroll->getScroll();
    if (hasStyle(Style::HScroll) && hScroll) me.mx += hScroll->getScroll();
    if (me.id != ME_Enter && me.id != ME_Leaves)
    {
        for (int i = members->getEntryCount() - 1; i >= 0; i--)
            members->z(i)->doPublicMouseEvent(me);
    }
    me.mx += rbr;
    me.my += rbr + th;
    if (hasStyle(Style::VScroll) && vScroll) me.my -= vScroll->getScroll();
    if (hasStyle(Style::HScroll) && hScroll) me.mx -= hScroll->getScroll();
    if (hasStyleNot(Style::METransparent)) me.processed = 1;
    if (hasStyleNot(Style::Allowed)) me.processed = mvtmp;
}

// non-const
// -- Border --
void Window::setBorderZ(Border* ram) // sets the border
{
    if (border) border->release();
    border = ram;
    rend = 1;
}

void Window::setBorderColor(int f) // sets the border color
{
    if (!border) border = new LBorder();
    border->setColor(f);
    rend = 1;
}

void Window::setBorderWidth(int br) // sets the border width
{
    if (!border) border = new LBorder();
    border->setBorderWidth(br);
    rend = 1;
}

// -- Title --
void Window::setTitel(Text* txt) // sets the title
{
    if (!title) title = new TextField();
    title->setText(txt);
    rend = 1;
}

void Window::setTitelZ(Text* txt)
{
    if (!title) title = new TextField();
    title->setTextZ(txt);
    rend = 1;
}

void Window::setTitel(const char* txt)
{
    if (!title) title = new TextField();
    title->setText(txt);
    rend = 1;
}

void Window::setTTextFieldZ(TextField* tf) // sets the title text field
{
    if (title) title->release();
    title = tf;
    rend = 1;
}

// -- Font --
void Window::setTFontZ(Font* font) // sets the title font
{
    if (!title) title = new TextField();
    title->setFontZ(font);
    rend = 1;
}

void Window::setTFontColor(int f) // sets the title font color
{
    if (!title) title = new TextField();
    title->setFontColor(f);
    rend = 1;
}

void Window::setTFontSize(int gr) // sets the title font size
{
    if (!title) title = new TextField();
    title->setFontSize((unsigned char)gr);
    rend = 1;
}

// -- Title Background --
void Window::setTBgColor(int f) // sets title background color
{
    if (!title) title = new TextField();
    title->setBackgroundColor(f);
    rend = 1;
}

// -- Title AlphaField --
void Window::setTAlphaFieldZ(AlphaField* af) // sets the title AlphaField
{
    if (!title) title = new TextField();
    title->setAlphaFieldZ(af);
    rend = 1;
}

void Window::setTAfColor(int f) // sets the title AlphaField color
{
    if (!title) title = new TextField();
    title->setAlphaFieldColor(f);
    rend = 1;
}

void Window::setTAfStrength(int st) // sets the strength of the title AlphaField
{
    if (!title) title = new TextField();
    title->setAlphaFieldStrength(st);
    rend = 1;
}

// -- Title Background Image --
void Window::setTBgImage(Image* b) // sets the title background image
{
    if (!title) title = new TextField();
    title->setBackgroundImage(b);
    rend = 1;
}

void Window::setTBgImageZ(Image* b)
{
    if (!title) title = new TextField();
    title->setBackgroundImageZ(b);
    rend = 1;
}

// -- Title Border --
void Window::setTBorderZ(Border* ram) // sets the title border
{
    if (!title) title = new TextField();
    title->setBorderZ(ram);
    rend = 1;
}

void Window::setTBorderColor(int f) // sets the title border color
{
    if (!title) title = new TextField();
    title->setBorderColor(f);
    rend = 1;
}

void Window::setTBorderWidth(int br) // sets the title border width
{
    if (!title) title = new TextField();
    title->setBorderWidth(br);
    rend = 1;
}

// -- Body Background --
void Window::setBodyBgColor(int f) // sets the body background color
{
    bgBodyColor = f;
    rend = 1;
}

// -- Body Background Image --
void Window::setBodyBgImage(Image* b) // sets the body background image
{
    if (!bgBodyPicture) bgBodyPicture = new Image();
    bgBodyPicture->newImage(b->getWidth(), b->getHeight(), 0);
    int* buff1 = bgBodyPicture->getBuffer();
    int* buff2 = b->getBuffer();
    int gr = bgBodyPicture->getWidth() * bgBodyPicture->getHeight();
    for (int i = 0; i < gr; ++i)
        buff1[i] = buff2[i];
    b->release();
    rend = 1;
}

void Window::setBodyBgImageZ(Image* b)
{
    if (bgBodyPicture) bgBodyPicture->release();
    bgBodyPicture = b;
    rend = 1;
}

// -- Body AlphaField --
void Window::setBodyAlphaFieldZ(AlphaField* af) // sets the body AlphaField
{
    if (bodyBuffer) bodyBuffer->release();
    bodyBuffer = af;
    rend = 1;
}

void Window::setBodyAfColor(int f) // sets body AlphaField color
{
    if (!bodyBuffer) bodyBuffer = new AlphaField();
    bodyBuffer->setColor(f);
    rend = 1;
}

void Window::setBodyAfStrength(
    int st) // sets the strength of the body AlphaField
{
    if (!bodyBuffer) bodyBuffer = new AlphaField();
    bodyBuffer->setStrength(st);
    rend = 1;
}

// -- Close --
void Window::setClosingMeParam(void* param)
{
    closingMeParam = param;
}

void Window::setClosingMe(MouseAction closingMe) // sets the close mouse event
{
    this->closingMe = closingMe;
}

// -- Close Background --
void Window::setCloseBgColor(int f) // sets the close background color
{
    bgClosingColor = f;
    rend = 1;
}

// -- Close Background Image --
void Window::setCloseBgImage(Image* b) // sets the close background image
{
    if (!bgClosingImage) bgClosingImage = new Image();
    bgClosingImage->newImage(b->getWidth(), b->getHeight(), 0);
    int* buff1 = bgClosingImage->getBuffer();
    int* buff2 = b->getBuffer();
    int gr = bgClosingImage->getWidth() * bgClosingImage->getHeight();
    for (int i = 0; i < gr; ++i)
        buff1[i] = buff2[i];
    b->release();
    rend = 1;
}

void Window::setCloseBgImageZ(Image* b)
{
    if (bgClosingImage) bgClosingImage->release();
    bgClosingImage = b;
    rend = 1;
}

// -- Close AlphaField --
void Window::setCloseAlphaFieldZ(AlphaField* af) // sets the close AlphaField
{
    if (closeBuffer) closeBuffer->release();
    closeBuffer = af;
    rend = 1;
}

void Window::setCloseAfColor(int f) // sets the color of the close AlphaField
{
    if (!closeBuffer) closeBuffer = new AlphaField();
    closeBuffer->setColor(f);
    rend = 1;
}

void Window::setCloseAfStrength(
    int st) // sets the strength of the close AlphaField
{
    if (!closeBuffer) closeBuffer = new AlphaField();
    closeBuffer->setStrength(st);
    rend = 1;
}

// -- Close Click AlphaField --
void Window::setCloseClickAlphaFieldZ(
    AlphaField* af) // sets the close click AlphaField
{
    if (closeClickBuffer) closeClickBuffer->release();
    closeClickBuffer = af;
    rend = 1;
}

void Window::setCloseClickAfColor(
    int f) // sets the color of the close click AlphaField
{
    if (!closeClickBuffer) closeClickBuffer = new AlphaField();
    closeClickBuffer->setColor(f);
    rend = 1;
}

void Window::setCloseClickAfStrength(
    int st) // sets the strength of the close click AlphaField
{
    if (!closeClickBuffer) closeClickBuffer = new AlphaField();
    closeClickBuffer->setStrength(st);
    rend = 1;
}

// -- min max --
void Window::setMin(int mx, int my) // sets the minimum window size
{
    min.x = mx;
    min.y = my;
}

void Window::setMin(const Point& min)
{
    this->min = min;
}

void Window::setMax(int mx, int my) // sets the maximum window size
{
    max.x = mx;
    max.y = my;
}

void Window::setMax(const Point& max)
{
    this->max = max;
}

void Window::setBodyMin(int mx, int my) // sets the minimum body size
{
    kMin.x = mx;
    kMin.y = my;
}

void Window::setBodyMin(const Point& min)
{
    kMin = min;
}

void Window::setBodyMax(int mx, int my) // sets the maximum body size
{
    kMax.x = mx;
    kMax.y = my;
}

void Window::setBodyMax(const Point& max)
{
    kMax = max;
}

// -- scroll --
void Window::setHScrollBarZ(
    HScrollBar* hScroll) // sets the horizontal scroll bar
{
    if (this->hScroll) this->hScroll->release();
    this->hScroll = hScroll;
    rend = 1;
}

void Window::setVScrollBarZ(VScrollBar* vScroll) // sets the vertical scroll bar
{
    if (this->vScroll) this->vScroll->release();
    this->vScroll = vScroll;
    rend = 1;
}

void Window::setHSBMax(int max) // sets the scroll maximum
{
    if (!hScroll) hScroll = new HScrollBar();
    int rbr = 0;
    if (hasStyle(Style::Border) && border) rbr = border->getRWidth();
    int vsh = 0;
    if (hasStyle(Style::VScroll) && vScroll) vsh = 15;
    hScroll->update(max, gr.x - rbr * 2 - vsh);
    rend = 1;
}

void Window::setVSBMax(int max)
{
    if (!vScroll) vScroll = new VScrollBar();
    int rbr = 0;
    int th = 0;
    if (hasStyle(Style::Border) && border) rbr = border->getRWidth();
    if (hasStyle(Style::Title) && title) th = title->getHeight();
    int hsh = 0;
    if (hasStyle(Style::HScroll) && hScroll) hsh = 15;
    vScroll->update(max, gr.y - rbr * 2 - th - hsh);
    rend = 1;
}

void Window::setHSBScroll(int scroll) // sets the current scroll position
{
    if (!hScroll) hScroll = new HScrollBar();
    hScroll->scroll(scroll);
    rend = 1;
}

void Window::setVSBScroll(int scroll)
{
    if (!vScroll) vScroll = new VScrollBar();
    vScroll->scroll(scroll);
    rend = 1;
}

void Framework::Window::updateHScroll()
{
    if (hScroll)
    {
        hScroll->update(getNeededChildWidth(), getInnerWidth());
    }
}

void Framework::Window::updateVScroll()
{
    if (vScroll)
    {
        vScroll->update(getNeededChildHeight(), getInnerHeight());
    }
}

// -- Members --
void Window::addMember(Drawable* obj) // adds a member
{
    members->add(obj);
    rend = 1;
}

void Framework::Window::setMemberIndex(Drawable* zMember, int index)
{
    if (index < 0 || index >= members->getEntryCount()) return;
    int currentIndex = members->indexOf(zMember);
    members->setPosition(currentIndex, index);
}

void Window::removeMember(Drawable* zObj) // removes a member
{
    for (int i = 0; i < members->getEntryCount(); i++)
    {
        if (members->z(i) == zObj)
        {
            members->remove(i);
            rend = 1;
        }
    }
}

void Window::removeAll()
{
    members->clear();
    rend = 1;
}

// -- Messages --
bool Window::tick(double tickval) // tick
{
    if (hasStyle(Style::Visible))
    {
        for (Drawable* i : *members)
            rend |= i->tick(tickval);
    }
    else
    {
        for (Drawable* i : *members)
            i->tick(tickval);
    }
    if (vScroll && hasStyle(Style::VScroll)) rend |= vScroll->getNeedRender();
    if (hScroll && hasStyle(Style::HScroll)) rend |= hScroll->getNeedRender();
    return Drawable::tick(tickval);
}

void Window::doKeyboardEvent(KeyboardEvent& te)
{
    bool ntakc = !te.processed;
    if (hasStyle(Style::Visible))
    {
        if (te.processed)
        {
            for (int i = members->getEntryCount() - 1; i >= 0; i--)
                members->z(i)->doKeyboardEvent(te);
        }
        else
        {
            if (tak && tak(takParam, this, te))
            {
                for (int i = members->getEntryCount() - 1; i >= 0; i--)
                    members->z(i)->doKeyboardEvent(te);
            }
        }
    }
    if (ntakc && te.processed && nTak) te.processed = nTak(ntakParam, this, te);
}

// -- Render --
void Window::render(Image& zRObj) // draws to zRObj
{
    if (hasStyle(Style::Visible))
    {
        rwLock.lockRead();
        if (!zRObj.setDrawOptions(pos, gr))
        {
            rwLock.unlockRead();
            return;
        }
        Drawable::render(zRObj);
        int rbr = 0;
        if (hasStyle(Style::Border) && border)
        {
            border->setSize(gr);
            border->render(zRObj);
            rbr = border->getRWidth();
        }
        int th = 0;
        if (title)
            title->setStyle(TextField::Style::Visible, hasStyle(Style::Title));
        if (hasStyle(Style::Title) && title)
        {
            title->setStyle(
                TextField::Style::Background, hasStyle(Style::TitleBackground));
            title->setStyle(
                TextField::Style::BAlpha, hasStyle(Style::TitleBAlpha));
            title->setStyle(
                TextField::Style::BImage, hasStyle(Style::TitleBImage));
            title->setStyle(
                TextField::Style::Buffered, hasStyle(Style::TitleBuffered));
            th = title->getHeight();
            if (!zRObj.setDrawOptions(rbr, rbr, gr.x - rbr * 2, th))
            {
                zRObj.releaseDrawOptions();
                rwLock.unlockRead();
                return;
            }
            int sbr = 0;
            if (hasStyle(Style::Closable))
            {
                sbr = th;
                if (hasStyle(Style::ClosingBackground))
                {
                    if (hasStyle(Style::ClosingBAlpha))
                        zRObj.alphaRegion(
                            gr.x - th - rbr * 2, 0, th, th, bgClosingColor);
                    else
                        zRObj.fillRegion(
                            gr.x - th - rbr * 2, 0, th, th, bgClosingColor);
                    if (hasStyle(Style::ClosingBImage) && bgClosingImage)
                    {
                        if (hasStyle(Style::ClosingBAlpha))
                            zRObj.alphaImage(gr.x - th - rbr * 2,
                                0,
                                th,
                                th,
                                *bgClosingImage);
                        else
                            zRObj.drawImage(gr.x - th - rbr * 2,
                                0,
                                th,
                                th,
                                *bgClosingImage);
                    }
                }
                if (!hasStyle(Style::ClosingBImage) || !bgClosingImage)
                {
                    zRObj.drawLine(Point(gr.x - th - rbr * 2, 0),
                        Point(gr.x - rbr * 2, th),
                        0xFFFFFFFF);
                    zRObj.drawLine(Point(gr.x - rbr * 2, 0),
                        Point(gr.x - th - rbr * 2, th),
                        0xFFFFFFFF);
                }
                if (hasStyle(Style::ClosingBuffer) && closeBuffer)
                {
                    closeBuffer->setPosition(gr.x - th - rbr * 2, 0);
                    closeBuffer->setSize(th, th);
                    closeBuffer->render(zRObj);
                }
                if (hasStyle(Style::ClosingClickBuffer) && closeClickBuffer
                    && closeClick)
                {
                    closeClickBuffer->setPosition(gr.x - th - rbr * 2, 0);
                    closeClickBuffer->setSize(th, th);
                    closeClickBuffer->render(zRObj);
                }
            }
            if (!hasStyle(Style::CustomTitle))
                title->setSize(gr.x - rbr * 2 - sbr, th);
            title->render(zRObj);
            zRObj.releaseDrawOptions();
        }
        bool vSc = hasStyle(Style::VScroll) && vScroll;
        bool hSc = hasStyle(Style::HScroll) && hScroll;
        if (vSc)
        {
            vScroll->render(
                gr.x - rbr - 15, rbr + th, 15, gr.y - rbr * 2 - th, zRObj);
            if (hSc)
                hScroll->render(
                    rbr, gr.y - rbr - 15, gr.x - rbr * 2 - 15, 15, zRObj);
        }
        else if (hSc)
            hScroll->render(rbr, gr.y - rbr - 15, gr.x - rbr * 2, 15, zRObj);
        int x = rbr;
        int y = rbr + th;
        int br = gr.x - rbr * 2;
        int hi = gr.y - rbr * 2 - th;
        if (vSc) br -= 15;
        if (hSc) hi -= 15;
        if (!zRObj.setDrawOptions(x, y, br, hi))
        {
            zRObj.releaseDrawOptions();
            rwLock.unlockRead();
            return;
        }
        if (hasStyle(Style::BodyBackground))
        {
            if (hasStyle(Style::BodyBAlpha))
                zRObj.alphaRegion(0, 0, br, hi, bgBodyColor);
            else
                zRObj.fillRegion(0, 0, br, hi, bgBodyColor);
            if (hasStyle(Style::BodyBImage) && bgBodyPicture)
            {
                if (hasStyle(Style::BodyBAlpha))
                    zRObj.alphaImage(0, 0, br, hi, *bgBodyPicture);
                else
                    zRObj.drawImage(0, 0, br, hi, *bgBodyPicture);
            }
        }
        if (hasStyle(Style::BodyBuffered) && bodyBuffer)
        {
            bodyBuffer->setSize(br, hi);
            bodyBuffer->render(zRObj);
        }
        if (!vSc && !hSc)
        {
            for (Drawable* i : *members)
                i->render(zRObj);
        }
        else
        {
            zRObj.addScrollOffset(
                hSc ? hScroll->getScroll() : 0, vSc ? vScroll->getScroll() : 0);
            for (Drawable* i : *members)
                i->render(zRObj);
        }
        zRObj.releaseDrawOptions();
        zRObj.releaseDrawOptions();
        rwLock.unlockRead();
    }
}

// constant
// Returns the width of the interior area of the drawing in pixels
int Window::getInnerWidth() const
{
    return getWidth() - 2 * getBorderWidth();
}

// Returns the height of the interior area of the drawing in pixels
int Window::getInnerHeight() const
{
    int th = 0;
    if (hasStyle(Style::Title) && title) th += title->getHeight();
    return getHeight() - 2 * getBorderWidth() - th;
}

// -- Border --
Border* Window::getBorder() const // returns the border
{
    if (!border) return 0;
    return dynamic_cast<Border*>(border->getThis());
}

Border* Window::zBorder() const
{
    return border;
}

int Window::getBorderColor() const // returns the border color
{
    if (!border) return 0;
    return border->getColor();
}

int Window::getBorderWidth() const // returns the border width
{
    if (!border || hasStyleNot(Style::Border)) return 0;
    return border->getRWidth();
}

// -- Title --
Text* Window::getTitel() const // returns the title
{
    if (!title) return 0;
    return title->getText();
}

Text* Window::zTitel() const
{
    if (!title) return 0;
    return title->zText();
}

TextField* Window::getTTextField() const // returns the title text field
{
    if (!title) return 0;
    return dynamic_cast<TextField*>(title->getThis());
}

TextField* Window::zTTextField() const
{
    return title;
}

// -- Title Font --
Font* Window::getTFont() const // returns the title font
{
    if (!title) return 0;
    return title->getFont();
}

Font* Window::zTFont() const
{
    if (!title) return 0;
    return title->zFont();
}

int Window::getTFontColor() const // returns the title font color
{
    if (!title) return 0;
    return title->getFontColor();
}

int Window::getTFontSize() const // returns the title font size
{
    if (!title) return 0;
    return title->getFontSize();
}

// -- Title Background --
int Window::getTBgColor() const // returns the title background color
{
    if (!title) return 0;
    return title->getBackgroundColor();
}

// -- Title AlphaField --
AlphaField* Window::getTAlphaField() const // returns the title AlphaField
{
    if (!title) return 0;
    return title->getAlphaField();
}

AlphaField* Window::zTAlphaField() const
{
    if (!title) return 0;
    return title->zAlphaField();
}

int Window::getTAfColor() const // returns the color of the title AlphaField
{
    if (!title) return 0;
    return title->getAlphaFieldColor();
}

int Window::getTAfStrength()
    const // returns the strength of the title AlphaField
{
    if (!title) return 0;
    return title->getAlphaFieldStrength();
}

// -- Title Background Image --
Image* Window::getTBgImage() const // returns the title background image
{
    if (!title) return 0;
    return title->getBackgroundImage();
}

Image* Window::zTBgImage() const
{
    if (!title) return 0;
    return title->zBackgroundImage();
}

// -- Title Border --
Border* Window::getTBorder() const // returns the title border
{
    if (!title) return 0;
    return title->getBorder();
}

Border* Window::zTBorder() const
{
    if (!title) return 0;
    return title->zBorder();
}

int Window::getTBorderColor() const // returns the title border color
{
    if (!title) return 0;
    return title->getBorderColor();
}

int Window::getTBorderWidth() const // returns the title border width
{
    if (!title) return 0;
    return title->getBorderWidth();
}

// -- Body Background --
int Window::getBodyBgColor() const // returns the body background color
{
    return bgBodyColor;
}

// -- Body Background Image --
Image* Window::getBodyBgImage() const // returns the body background image
{
    if (!bgBodyPicture) return 0;
    return dynamic_cast<Image*>(bgBodyPicture->getThis());
}

Image* Window::zBodyBgImage() const
{
    return bgBodyPicture;
}

// -- Body AlphaField --
AlphaField* Window::getBodyAlphaField() const // returns the body AlphaField
{
    if (!bodyBuffer) return 0;
    return dynamic_cast<AlphaField*>(bodyBuffer->getThis());
}

AlphaField* Window::zBodyAlphaField() const
{
    return bodyBuffer;
}

int Window::getBodyAfColor() const // returns the body AlphaField color
{
    if (!bodyBuffer) return 0;
    return bodyBuffer->getColor();
}

int Window::getBodyAfStrength()
    const // returns the strength of the body AlphaField
{
    if (!bodyBuffer) return 0;
    return bodyBuffer->getStrength();
}

// -- Close Background --
int Window::getCloseBgColor() const // returns the close background color
{
    return bgClosingColor;
}

// -- Close Background Image --
Image* Window::getCloseBgImage() const // returns the close background image
{
    if (!bgClosingImage) return 0;
    return dynamic_cast<Image*>(bgClosingImage->getThis());
}

Image* Window::zCloseBgImage() const
{
    return bgClosingImage;
}

// -- Close AlphaField --
AlphaField* Window::getCloseAlphaField() const // returns the close AlphaField
{
    if (!closeBuffer) return 0;
    return dynamic_cast<AlphaField*>(closeBuffer->getThis());
}

AlphaField* Window::zCloseAlphaField() const
{
    return closeBuffer;
}

int Window::getCloseAfColor() const // returns the close AlphaField color
{
    if (!closeBuffer) return 0;
    return closeBuffer->getColor();
}

int Window::getCloseAfStrength()
    const // returns the strength of the close AlphaField
{
    if (!closeBuffer) return 0;
    return closeBuffer->getStrength();
}

// -- Close Click AlphaField --
AlphaField*
Window::getCloseClickAlphaField() const // returns the close click AlphaField
{
    if (!closeClickBuffer) return 0;
    return dynamic_cast<AlphaField*>(closeClickBuffer->getThis());
}

AlphaField* Window::zCloseClickAlphaField() const
{
    return closeClickBuffer;
}

int Window::getCloseClickAfColor()
    const // returns the close click AlphaField color
{
    if (!closeClickBuffer) return 0;
    return closeClickBuffer->getColor();
}

int Window::getCloseClickAfStrength()
    const // returns the strength of the close click AlphaField
{
    if (!closeClickBuffer) return 0;
    return closeClickBuffer->getStrength();
}

// -- min max --
const Point& Window::getMin() const // returns the minimum window size
{
    return min;
}

const Point& Window::getMax() const // returns the maximum window size
{
    return max;
}

const Point& Window::getBodyMin() const // returns the minimum body size
{
    return kMin;
}

const Point& Window::getBodyMax() const // returns the maximum body size
{
    return kMax;
}

// -- scroll --
VScrollBar* Window::getVScrollBar() const // returns the vertical scroll bar
{
    if (!vScroll) return 0;
    return dynamic_cast<VScrollBar*>(vScroll->getThis());
}

VScrollBar* Window::zVScrollBar() const
{
    return vScroll;
}

HScrollBar* Window::getHScrollBar() const // returns the horizontal scroll bar
{
    if (!hScroll) return 0;
    return dynamic_cast<HScrollBar*>(hScroll->getThis());
}

HScrollBar* Window::zHScrollBar() const
{
    return hScroll;
}

// -- Members --
const RCArray<Drawable>& Window::getMembers() const // returns the members
{
    return *members;
}

int Framework::Window::getNeededChildWidth() const
{
    int max = 0;
    for (Drawable* z : *members)
    {
        if (z->getX() + z->getWidth() > max) max = z->getX() + z->getWidth();
    }
    return max;
}

int Framework::Window::getNeededChildHeight() const
{
    int max = 0;
    for (Drawable* z : *members)
    {
        if (z->getY() + z->getHeight() > max) max = z->getY() + z->getHeight();
    }
    return max;
}

// -- Copy --
Drawable* Window::duplicate() const // Creates a copy of the window
{
    Window* ret = new Window();
    ret->setPosition(pos);
    ret->setSize(gr);
    ret->setMouseEventParameter(makParam);
    ret->setKeyboardEventParameter(takParam);
    ret->setMouseEvent(mak);
    ret->setKeyboardEvent(tak);
    if (toolTip) ret->setToolTipZ((ToolTip*)toolTip->duplicate());
    ret->setStyle(style);
    ret->setClosingMeParam(closingMeParam);
    ret->setClosingMe(closingMe);
    if (border)
    {
        ret->setBorderWidth(border->getRWidth());
        ret->setBorderColor(border->getColor());
    }
    if (title) ret->setTTextFieldZ((TextField*)title->duplicate());
    ret->setBodyBgColor(bgBodyColor);
    if (bgBodyPicture)
        ret->setBodyBgImage(dynamic_cast<Image*>(bgBodyPicture->getThis()));
    if (bodyBuffer)
    {
        ret->setBodyAfColor(bodyBuffer->getColor());
        ret->setBodyAfStrength(bodyBuffer->getStrength());
    }
    ret->setCloseBgColor(bgClosingColor);
    if (bgClosingImage)
        ret->setCloseBgImage(dynamic_cast<Image*>(bgClosingImage->getThis()));
    if (closeBuffer)
    {
        ret->setCloseAfColor(closeBuffer->getColor());
        ret->setCloseAfStrength(closeBuffer->getStrength());
    }
    if (closeClickBuffer)
    {
        ret->setCloseClickAfColor(closeClickBuffer->getColor());
        ret->setCloseClickAfStrength(closeClickBuffer->getStrength());
    }
    if (vScroll)
    {
        ret->setVSBMax(vScroll->getScrollData()->max);
        ret->setVSBScroll(vScroll->getScroll());
    }
    if (hScroll)
    {
        ret->setHSBMax(hScroll->getScrollData()->max);
        ret->setHSBScroll(hScroll->getScroll());
    }
    ret->setMin(min);
    ret->setMax(max);
    ret->setBodyMin(kMin);
    ret->setBodyMax(kMax);
    return ret;
}