#include "XcbInterface.h"

#include "DesktopManager.h"

#include <X11/Xlib.h>
#include <X11/Xlib-xcb.h> /* for XGetXCBConnection, link with libX11-xcb */
#include <xcb/xcb.h>

#include "PaintEvent.h"
#include "Screen.h"
#include "XcbScreen.h"
#include "UiEvent.h"
#include "VisualLayer.h"
#include "XcbKeyboard.h"
#include "XcbWindow.h"
#include "XcbLayerInterface.h"
#include "XcbEventInterface.h"
#include "XcbWindowInterface.h"
#include "GlxInterface.h"
#include "FileLogger.h"


XcbInterface::XcbInterface()
    : mUseOpenGl(true),
      mConnection(nullptr),
      mX11Display(),
      mGlxInterface(),
      mXcbEventInterface(XcbEventInterface::Create()),
      mXcbWindowInterface(XcbWindowInterface::Create())
{

}

XcbInterface::~XcbInterface()
{

}

void XcbInterface::SetUseOpenGl(bool use)
{
    mUseOpenGl = use;
}

void XcbInterface::Initialize(DesktopManager* desktopManager)
{
    Connect();
    UpdateScreen(desktopManager);
    CreateGraphicsContext(desktopManager);
    if (mUseOpenGl)
    {
        InitializeOpenGl();
    }
}

void XcbInterface::Connect()
{
    if (mUseOpenGl)
    {
        mX11Display = XOpenDisplay(0);
        if (!mX11Display)
        {
            MLOG_ERROR("Can't open X11 display");
            return;
        }
        mConnection = XGetXCBConnection(mX11Display);
        if (!mConnection)
        {
            XCloseDisplay(mX11Display);
            MLOG_ERROR("Can't get xcb connection from display");
            return;
        }
        XSetEventQueueOwner(mX11Display, XCBOwnsEventQueue);
    }
    else
    {
        mConnection = xcb_connect(nullptr, nullptr);
    }
}

void XcbInterface::UpdateScreen(DesktopManager* desktopManager)
{
    if (!mConnection)
    {
        return;
    }
    auto screen_iter = xcb_setup_roots_iterator(xcb_get_setup(mConnection));
    if (mUseOpenGl)
    {
        if (!mX11Display)
        {
            return;
        }
        const auto default_screen = DefaultScreen(mX11Display);
        for(int screen_num = default_screen; screen_iter.rem && screen_num > 0;
            --screen_num, xcb_screen_next(&screen_iter));
    }
    auto xcb_screen = XcbScreen::Create(screen_iter.data);
    auto screen = mt::Screen::Create();
    screen->SetPlatformScreen(std::move(xcb_screen));
    desktopManager->AddScreen(std::move(screen));
}

void XcbInterface::InitializeOpenGl()
{
    mGlxInterface = GlxInterface::Create();
    const auto default_screen = DefaultScreen(mX11Display);
    mGlxInterface->SetupContext(mX11Display, default_screen);
}

void XcbInterface::CreateOpenGlDrawable(mt::Window* window)
{
    if (!mUseOpenGl or !window or !window->GetPlatformWindow())
    {
        return;
    }
    auto xcb_window = dynamic_cast<XcbWindow*>(window->GetPlatformWindow());
    if (!xcb_window)
    {
        return;
    }
    if (!mGlxInterface->SetupWindow(mX11Display, xcb_window->GetHandle()))
    {
        xcb_destroy_window(mConnection, xcb_window->GetHandle());
    }
}

void XcbInterface::CreateGraphicsContext(DesktopManager* desktopManager)
{
    if (!mConnection || desktopManager->GetDefaultScreen())
    {
        return;
    }
    auto xcb_screen = dynamic_cast<XcbScreen*>(desktopManager->GetDefaultScreen()->GetPlatformScreen());
    if (!xcb_screen)
    {
        return;
    }

    auto gc = xcb_generate_id(mConnection);
    xcb_drawable_t window = xcb_screen->GetNativeScreen()->root;
    uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES;
    uint32_t values[2] = {XcbLayerInterface::GetColor(240, 240, 240), 0};
    xcb_create_gc(mConnection, gc, window, mask, values);
    xcb_screen->SetGraphicsContext(gc);
}

void XcbInterface::MapWindow(mt::Window* window)
{
    mXcbWindowInterface->Map(window, mConnection);
}

void XcbInterface::ShowWindow(mt::Window* window)
{
    mXcbWindowInterface->Show(window, mConnection);
}

uint32_t XcbInterface::GetEventMask()
{
    return XCB_EVENT_MASK_KEY_RELEASE |
            XCB_EVENT_MASK_BUTTON_PRESS |
            XCB_EVENT_MASK_BUTTON_RELEASE |
            XCB_EVENT_MASK_EXPOSURE;
}

void XcbInterface::AddWindow(mt::Window* window, DesktopManager* desktopManager)
{
    auto screen = desktopManager->GetDefaultScreen();
    mXcbWindowInterface->Add(window, mConnection, screen, GetEventMask());
}

void XcbInterface::onPaint(DesktopManager* desktopManager)
{
    desktopManager->OnUiEvent(PaintEvent::Create());
    auto mainWindow = desktopManager->GetWindowManager()->GetMainWindow();
    auto defaultScreen = desktopManager->GetDefaultScreen();
    auto xcb_screen = dynamic_cast<XcbScreen*>(defaultScreen->GetPlatformScreen());
    mXcbWindowInterface->Paint(mainWindow, mConnection,
            xcb_screen->GetNativeScreen(), xcb_screen->GetGraphicsContext());
}

void XcbInterface::OnExposeEvent(xcb_expose_event_t* event, DesktopManager* desktopManager)
{
    // Update the window
    if (mUseOpenGl)
    {
        mGlxInterface->Draw();
        mGlxInterface->SwapBuffers(mX11Display);
    }
    else
    {
        //auto window = mWindows[event->window];
        //window->SetSize(event->width, event->height);
        onPaint(desktopManager);
        xcb_flush(mConnection);
    }
}

void XcbInterface::Loop(DesktopManager* desktopManager)
{
    if (!mConnection)
    {
        return;
    }
    xcb_generic_event_t *event;
    while ((event = xcb_wait_for_event(mConnection)))
    {
        switch (event->response_type & ~0x80)
        {
            case XCB_EXPOSE:{
                auto expose_event = reinterpret_cast<xcb_expose_event_t*>(event);
                OnExposeEvent(expose_event, desktopManager);
                break;
            }
            case XCB_KEY_PRESS: {
                auto kp = reinterpret_cast<xcb_key_press_event_t*>(event);
                auto ui_event = mXcbEventInterface->ConvertKeyPress(kp, desktopManager->GetKeyboard());
                desktopManager->OnUiEvent(std::move(ui_event));
                break;
            }
            case XCB_KEY_RELEASE: {
                auto kr = reinterpret_cast<xcb_key_release_event_t*>(event);
                auto ui_event = mXcbEventInterface->ConvertKeyRelease(kr, desktopManager->GetKeyboard());
                desktopManager->OnUiEvent(std::move(ui_event));
                break;
            }
            case XCB_BUTTON_PRESS: {
                auto press = reinterpret_cast<xcb_button_press_event_t*>(event);
                auto ui_event = mXcbEventInterface->ConvertButtonPress(press);
                desktopManager->OnUiEvent(std::move(ui_event));
                break;
            }
            case XCB_BUTTON_RELEASE: {
                auto release = reinterpret_cast<xcb_button_release_event_t*>(event);
                auto ui_event = mXcbEventInterface->ConvertButtonRelease(release);
                desktopManager->OnUiEvent(std::move(ui_event));
                break;
            }
            default:
                /* Unknown event type, ignore it */
                break;
        }
        onEventsDispatched(desktopManager);
        free(event);
    }
    OnLoopCompleted(desktopManager);
}

void XcbInterface::OnLoopCompleted(DesktopManager* desktopManagers)
{
    if (mUseOpenGl)
    {
        mGlxInterface->DestroyWindow(mX11Display);
        mGlxInterface->DestroyContext(mX11Display);
    }
}

void XcbInterface::onEventsDispatched(DesktopManager* desktopManager)
{
    if (desktopManager->IsModified())
    {
        desktopManager->SetIsModified(false);
        auto mainWindow = desktopManager->GetWindowManager()->GetMainWindow();
        mXcbWindowInterface->Clear(mainWindow, mConnection);
    }
}

void XcbInterface::ShutDown()
{
    if (!mConnection)
    {
        return;
    }
    MLOG_INFO("starting shutdown");
    xcb_disconnect(mConnection);
    MLOG_INFO("Ending shutdown");
}
