#include "XcbWindow.h"

#include "Window.h"
#include "Widget.h"
#include "XcbScreen.h"
#include "XcbImage.h"
#include "Screen.h"
#include "ImagePrimitives.h"
#include "XcbGlWindowInterface.h"

#include "DrawingContext.h"
#include "FontsManager.h"

#include <xcb/xcb.h>
#include <xcb/xcb_atom.h>

XcbWindow::XcbWindow(mt::Window* window, int hwnd, xcb_connection_t* connection, XcbGlInterface* xcbGlInterface)
    : IPlatformWindow(window),
      mHandle(hwnd),
      mConnection(connection)
{
    if (xcbGlInterface)
    {
        mGlInterface = std::make_unique<XcbGlWindowInterface>(xcbGlInterface);
    }
}

XcbWindow::~XcbWindow()
{
    xcb_destroy_window(mConnection, mHandle);
}

void XcbWindow::add(mt::Window* window, xcb_connection_t* connection, mt::Screen* screen, uint32_t eventMask, FontsManager*, XcbGlInterface* xcbGlInterface)
{
    if (!screen)
    {
        return;
    }
    auto xcb_screen = dynamic_cast<XcbScreen*>(screen->GetPlatformScreen());

    const auto hwnd = xcb_generate_id(connection);
    const uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
    const uint32_t values[2] = {xcb_screen->getNativeScreen()->white_pixel, eventMask};

    xcb_window_t parent_hwnd = xcb_screen->getNativeScreen()->root;
    if (auto parent = window->getParent())
    {
        parent_hwnd = dynamic_cast<XcbWindow*>(parent->getPlatformWindow())->getHandle();
    }

    xcb_create_window (connection,                    /* connection          */
                       XCB_COPY_FROM_PARENT,          /* depth               */
                       hwnd,                        /* window Id           */
                       parent_hwnd,                  /* parent window       */
                       0, 0,                          /* x, y                */
                       window->getWidth(), window->getHeight(), /* width, height */
                       10,                            /* border_width        */
                       XCB_WINDOW_CLASS_INPUT_OUTPUT, /* class               */
                       xcb_screen->getNativeScreen()->root_visual,           /* visual              */
                       mask, values );                /* masks */

    if (!window->getTitle().empty())
    {
        auto title = window->getTitle();
        xcb_change_property (connection, XCB_PROP_MODE_REPLACE, hwnd, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, title.size(), title.c_str());
        //xcb_change_property (connection, XCB_PROP_MODE_REPLACE, hwnd, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, title.size(), title.c_str());
    }

    auto xcb_window = std::make_unique<XcbWindow>(window, hwnd, connection, xcbGlInterface);

    //const auto drawing_mode = xcbGlInterface ? DrawingMode::GRAPH : DrawingMode::RASTER;
    window->setPlatformWindow(std::move(xcb_window));
}

int XcbWindow::getHandle() const
{
    return mHandle;
}

unsigned XcbWindow::getGraphicsContext() const
{
    return mGraphicsContext;
}

void XcbWindow::show()
{
    map();
}

void XcbWindow::beforePaint(mt::Screen* screen)
{
    if (mGlInterface)
    {
        mGlInterface->initialize(mHandle);
    }
    else
    {
        auto xcb_screen = dynamic_cast<XcbScreen*>(screen->GetPlatformScreen());
        if (!mBackingImage)
        {
            mBackingImage = std::make_unique<XcbImage>(mConnection, mWindow, xcb_screen->getNativeScreen());
        }
    }
}

void XcbWindow::afterPaint(mt::Screen* screen)
{
    if (mGlInterface)
    {
        mGlInterface->afterPaint();
    }
    else
    {
        auto xcb_screen = dynamic_cast<XcbScreen*>(screen->GetPlatformScreen());
        mBackingImage->update(mWindow, xcb_screen->getNativeScreen(), mConnection,  xcb_screen->getGraphicsContext());

        xcb_copy_area(mConnection, mBackingImage->getPixMap(), mHandle, xcb_screen->getGraphicsContext(), 0, 0 ,0 , 0, mWindow->getWidth(), mWindow->getHeight());
    }
}

void XcbWindow::clear()
{
    xcb_clear_area(mConnection, 1, mHandle, 0, 0, mWindow->getWidth(), mWindow->getHeight());
    xcb_flush(mConnection);
}

void XcbWindow::map()
{
    xcb_map_window(mConnection, mHandle);
    xcb_flush(mConnection);
}

void XcbWindow::onResize(unsigned width, unsigned height)
{
    if (mGlInterface)
    {
        mGlInterface->resizeViewPort(width, height);
    }
}
