/// Guards


#ifdef GLBACKEND_WXWIDGETS


/// Includes


#include <glbackend_wxwidgets.hpp>

#include <array>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

#include <glbase.hpp>
#include <glbackend.hpp>

// #include <wx/wx.h>
#include <wx/app.h>
#include <wx/defs.h>
#include <wx/display.h>
#include <wx/event.h>
#include <wx/frame.h>
#include <wx/glcanvas.h>
#include <wx/init.h>
#include <wx/platinfo.h>
#include <wx/string.h>
#include <wx/tbarbase.h>
#include <wx/toplevel.h>
#include <wx/utils.h>
#include <wx/version.h>
#include <wx/versioninfo.h>
#include <wx/vidmode.h>
#include <wx/window.h>
#include <wx/windowid.h>

// NOLINTNEXTLINE
#define STR_EXCEPTION GLBase::Exception
#include <str.hpp>



namespace
{
class Canvas : public wxGLCanvas
{

public:

    Canvas(
        wxWindow      * parent,
        wxWindowID      id,
        int     const * attribs,
        wxPoint const & pos,
        wxSize  const & size
    )
    :
        wxGLCanvas(parent, id, attribs, pos, size)
    {}

protected:

    // TODO(rcrnstn): Implement wxWidgets event handling.
    // void paint_(wxPaintEvent& event);
    // void size_(wxSizeEvent& event);
    // void char_(wxKeyEvent& event);
    // void mouse_event_(wxMouseEvent& event);
    // void exit_(wxCommandEvent& event);
    wxDECLARE_EVENT_TABLE(); // NOLINT

};
}


// TODO(rcrnstn): Implement wxWidgets event handling.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
wxBEGIN_EVENT_TABLE(Canvas, wxGLCanvas) // NOLINT
//     EVT_PAINT(Canvas::paint_)
wxEND_EVENT_TABLE() // NOLINT
#pragma GCC diagnostic pop


GLBackendWxWidgets::GLBackendWxWidgets(
    std::string const & title,
    std::array<int, 2>  size,
    std::array<int, 2>  version,
    int                 samples,
    bool                fullscreen,
    bool                transparent
)
:
    GLBackend(),
    app_       {nullptr},
    frame_     {nullptr},
    canvas_    {nullptr},
    context_   {nullptr},
    event_loop_{},
    time_      {0.0F},
    locked_    {false}
{
    // Backend init
    if (!wxApp::GetInstance())
    {
        wxDISABLE_DEBUG_SUPPORT();

        wxApp::SetInitializerFunction([]()
        {
            // cppcheck-suppress cstyleCast
            return (wxAppConsole *)new wxApp;
        });

        auto argc = int{0};
        auto argv = std::array<char *, 1>{{nullptr}};
        if (!wxEntryStart(argc, &argv[0]))
            STR_THROW("Failed to initialize wxWidgets.");

        app_ = wxApp::GetInstance();
        app_->OnInit();
    }

    // Window options
    auto frame_style = long{0}; // NOLINT
    frame_style |= wxDEFAULT_FRAME_STYLE; // NOLINT
    frame_style &= ~(wxRESIZE_BORDER | wxMAXIMIZE_BOX); // NOLINT
    frame_style |= wxWANTS_CHARS; // NOLINT
    if (transparent)
    {
        frame_style &= ~wxDEFAULT_FRAME_STYLE; // NOLINT
        frame_style |= wxSTAY_ON_TOP; // NOLINT
    }
    if (size[0] == 0 || size[1] == 0)
    {
        auto const mode = wxDisplay(0).GetCurrentMode();
        size[0] = mode.GetWidth();
        size[1] = mode.GetHeight();
    }
    size_ = size;

    // Context options
    auto canvas_attribs = std::vector<int>{};
    //NOLINTNEXTLINE
    #define GLBACKEND_WXWIDGETS_CANVAS_ATTRIBS_INSERT_(...) \
        canvas_attribs.insert(canvas_attribs.end(), __VA_ARGS__);
    GLBACKEND_WXWIDGETS_CANVAS_ATTRIBS_INSERT_({
        WX_GL_RGBA,
        WX_GL_DOUBLEBUFFER,
        WX_GL_MIN_RED, 8,
        WX_GL_MIN_GREEN, 8,
        WX_GL_MIN_BLUE, 8,
        WX_GL_MIN_ALPHA, 8,
        WX_GL_DEPTH_SIZE, 24,
        WX_GL_STENCIL_SIZE, 8,
    })
    if (samples)
        GLBACKEND_WXWIDGETS_CANVAS_ATTRIBS_INSERT_({
            WX_GL_SAMPLE_BUFFERS, 1,
            WX_GL_SAMPLES, samples,
        })
    if (version[0])
        GLBACKEND_WXWIDGETS_CANVAS_ATTRIBS_INSERT_({
            WX_GL_MAJOR_VERSION, version[0],
        })
    if (version[1])
        GLBACKEND_WXWIDGETS_CANVAS_ATTRIBS_INSERT_({
            WX_GL_MINOR_VERSION, version[1],
        })
    if ((version[0] == 3 && version[1] >= 2) || version[0] >= 4) // NOLINT
        GLBACKEND_WXWIDGETS_CANVAS_ATTRIBS_INSERT_({
            WX_GL_CORE_PROFILE,
        })
    GLBACKEND_WXWIDGETS_CANVAS_ATTRIBS_INSERT_({
        0
    });

    try
    {
        // Frame
        frame_ = new wxFrame(); // NOLINT
        if (transparent)
            frame_->SetBackgroundStyle(wxBG_STYLE_TRANSPARENT);
        frame_->Create(
            nullptr,
            wxID_ANY,
            title,
            wxDefaultPosition,
            wxDefaultSize,
            frame_style
        );
        frame_->SetClientSize(size[0], size[1]);
        if (fullscreen)
            frame_->ShowFullScreen(true);
        else
            frame_->Show(true);

        // Canvas
        canvas_ = new Canvas{ // NOLINT
            frame_,
            wxID_ANY,
            &canvas_attribs[0],
            wxDefaultPosition,
            {size[0], size[1]},
        };

        // Context
        context_ = new wxGLContext(canvas_); // NOLINT
        events(); // NOLINT
        context_->SetCurrent(*canvas_);
        init_();

        // Lock
        if (fullscreen)
            lock(true);
    }
    catch (...)
    {
        destroy_();
        throw;
    }
}


GLBackendWxWidgets::~GLBackendWxWidgets()
{
    destroy_();
}


std::string GLBackendWxWidgets::debug_info() const
{
    auto ostream = std::ostringstream{};

    auto version_compiled = wxGetLibraryVersionInfo().GetVersionString();
    auto version_linked   = wxString(wxVERSION_STRING);
    auto os               = wxGetOsDescription();
    auto port             = wxPlatformInfo::Get().GetPortIdName();
    debug_info_(ostream, "wxWidgets", {
        {"VERSION_COMPILED", version_compiled.ToStdString()},
        {"VERSION_LINKED",   version_linked  .ToStdString()},
        {"OS",               os              .ToStdString()},
        {"PORT",             port            .ToStdString()},
    });

    ostream << GLBackend::debug_info();

    return ostream.str();
}


// TODO(rcrnstn): Implement wxWidgets event handling.
// void GLBackendWxWidgets::paint_(wxPaintEvent& WXUNUSED(event))
// {
//     wxPaintDC paint_dc(this);
//     if (render_)
//         render_();
// }


void GLBackendWxWidgets::destroy_()
{
    delete context_;
    if (canvas_)
    {
        lock(false);
        canvas_->Destroy();
    }
    if (frame_)
        frame_->Destroy();
    events(); // NOLINT
    if (app_)
    {
        app_->OnExit();
        wxEntryCleanup();
    }
}


/// Guards


#endif