/*
 * Carla Bridge UI
 * Copyright (C) 2011-2017 Filipe Coelho <falktx@falktx.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
 */

#include "CarlaBridgeFormat.hpp"
#include "CarlaBridgeToolkit.hpp"

#include <gtk/gtk.h>

#ifdef HAVE_X11
# include <gdk/gdkx.h>
#endif

CARLA_BRIDGE_UI_START_NAMESPACE

// -------------------------------------------------------------------------

static int    gargc = 0;
static char** gargv = nullptr;

static const bool gHideShowTesting = std::getenv("CARLA_UI_TESTING") != nullptr;

// -------------------------------------------------------------------------

class CarlaBridgeToolkitGtk : public CarlaBridgeToolkit
{
public:
    CarlaBridgeToolkitGtk(CarlaBridgeFormat* const format)
        : CarlaBridgeToolkit(format),
          fNeedsShow(false),
          fWindow(nullptr),
          fLastX(0),
          fLastY(0),
          fLastWidth(0),
          fLastHeight(0)
    {
        carla_debug("CarlaBridgeToolkitGtk::CarlaBridgeToolkitGtk(%p)", format);
    }

    ~CarlaBridgeToolkitGtk() override
    {
        CARLA_SAFE_ASSERT(fWindow == nullptr);
        carla_debug("CarlaBridgeToolkitGtk::~CarlaBridgeToolkitGtk()");
    }

    bool init(const int /*argc*/, const char** /*argv[]*/) override
    {
        CARLA_SAFE_ASSERT_RETURN(fWindow == nullptr, false);
        carla_debug("CarlaBridgeToolkitGtk::init()");

        gtk_init(&gargc, &gargv);

        fWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
        CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr, false);

        gtk_window_resize(GTK_WINDOW(fWindow), 30, 30);
        gtk_widget_hide(fWindow);

        return true;
    }

    void exec(const bool showUI) override
    {
        CARLA_SAFE_ASSERT_RETURN(fPlugin != nullptr,);
        CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
        carla_debug("CarlaBridgeToolkitGtk::exec(%s)", bool2str(showUI));

        const CarlaBridgeFormat::Options& options(fPlugin->getOptions());

        GtkWindow* const gtkWindow(GTK_WINDOW(fWindow));
        CARLA_SAFE_ASSERT_RETURN(gtkWindow != nullptr,);

        GtkWidget* const widget((GtkWidget*)fPlugin->getWidget());
        gtk_container_add(GTK_CONTAINER(fWindow), widget);

        gtk_window_set_resizable(gtkWindow, options.isResizable);
        gtk_window_set_title(gtkWindow, options.windowTitle.buffer());

        if (showUI || fNeedsShow)
        {
            show();
            fNeedsShow = false;
        }

        g_timeout_add(30, gtk_ui_timeout, this);
        g_signal_connect(fWindow, "destroy", G_CALLBACK(gtk_ui_destroy), this);
        g_signal_connect(fWindow, "realize", G_CALLBACK(gtk_ui_realize), this);

        // First idle
        handleTimeout();

        // Main loop
        gtk_main();
    }

    void quit() override
    {
        carla_debug("CarlaBridgeToolkitGtk::quit()");

        if (fWindow != nullptr)
        {
            gtk_widget_destroy(fWindow);
            fWindow = nullptr;

            gtk_main_quit_if_needed();
        }
    }

    void show() override
    {
        carla_debug("CarlaBridgeToolkitGtk::show()");

        fNeedsShow = true;

        if (fWindow != nullptr)
            gtk_widget_show_all(fWindow);
    }

    void focus() override
    {
        carla_debug("CarlaBridgeToolkitGtk::focus()");
    }

    void hide() override
    {
        carla_debug("CarlaBridgeToolkitGtk::hide()");

        fNeedsShow = false;

        if (fWindow != nullptr)
            gtk_widget_hide(fWindow);
    }

    void setChildWindow(void* const) override {}

    void setSize(const uint width, const uint height) override
    {
        CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
        carla_debug("CarlaBridgeToolkitGtk::resize(%i, %i)", width, height);

        gtk_window_resize(GTK_WINDOW(fWindow), static_cast<gint>(width), static_cast<gint>(height));
    }

    void setTitle(const char* const title) override
    {
        CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
        carla_debug("CarlaBridgeToolkitGtk::setTitle(\"%s\")", title);

        gtk_window_set_title(GTK_WINDOW(fWindow), title);
    }

    // ---------------------------------------------------------------------

protected:
    bool fNeedsShow;
    GtkWidget* fWindow;

    gint fLastX;
    gint fLastY;
    gint fLastWidth;
    gint fLastHeight;

    void handleDestroy()
    {
        carla_debug("CarlaBridgeToolkitGtk::handleDestroy()");

        fWindow = nullptr;
    }

    void handleRealize()
    {
        carla_debug("CarlaBridgeToolkitGtk::handleRealize()");

        const CarlaBridgeFormat::Options& options(fPlugin->getOptions());

        if (options.transientWindowId != 0)
            setTransient(options.transientWindowId);
    }

    gboolean handleTimeout()
    {
        if (fWindow != nullptr)
        {
            gtk_window_get_position(GTK_WINDOW(fWindow), &fLastX, &fLastY);
            gtk_window_get_size(GTK_WINDOW(fWindow), &fLastWidth, &fLastHeight);
        }

        if (fPlugin->isPipeRunning())
            fPlugin->idlePipe();

        fPlugin->idleUI();

        if (gHideShowTesting)
        {
            static int counter = 0;
            ++counter;

            if (counter == 100)
            {
                hide();
            }
            else if (counter == 200)
            {
                show();
                counter = 0;
            }
        }

        return true;
    }

    void setTransient(const uintptr_t winId)
    {
        CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
        carla_debug("CarlaBridgeToolkitGtk::setTransient(0x" P_UINTPTR ")", winId);

#ifdef HAVE_X11
        GdkWindow* const gdkWindow(gtk_widget_get_window(fWindow));
        CARLA_SAFE_ASSERT_RETURN(gdkWindow != nullptr,);

# ifdef BRIDGE_GTK3
        GdkDisplay* const gdkDisplay(gdk_window_get_display(gdkWindow));
        CARLA_SAFE_ASSERT_RETURN(gdkDisplay != nullptr,);

        ::Display* const display(gdk_x11_display_get_xdisplay(gdkDisplay));
        CARLA_SAFE_ASSERT_RETURN(display != nullptr,);

        const ::XID xid(gdk_x11_window_get_xid(gdkWindow));
        CARLA_SAFE_ASSERT_RETURN(xid != 0,);
# else
        ::Display* const display(gdk_x11_drawable_get_xdisplay(gdkWindow));
        CARLA_SAFE_ASSERT_RETURN(display != nullptr,);

        const ::XID xid(gdk_x11_drawable_get_xid(gdkWindow));
        CARLA_SAFE_ASSERT_RETURN(xid != 0,);
# endif

        XSetTransientForHint(display, xid, static_cast< ::Window>(winId));
#endif
    }

    // ---------------------------------------------------------------------

private:
    static void gtk_main_quit_if_needed()
    {
        if (gtk_main_level() != 0)
            gtk_main_quit();
    }

    static void gtk_ui_destroy(GtkWidget*, gpointer data)
    {
        CARLA_SAFE_ASSERT_RETURN(data != nullptr,);

        ((CarlaBridgeToolkitGtk*)data)->handleDestroy();
        gtk_main_quit_if_needed();
    }

    static void gtk_ui_realize(GtkWidget*, gpointer data)
    {
        CARLA_SAFE_ASSERT_RETURN(data != nullptr,);

        ((CarlaBridgeToolkitGtk*)data)->handleRealize();
    }

    static gboolean gtk_ui_timeout(gpointer data)
    {
        CARLA_SAFE_ASSERT_RETURN(data != nullptr, false);

        return ((CarlaBridgeToolkitGtk*)data)->handleTimeout();
    }

    CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaBridgeToolkitGtk)
};

// -------------------------------------------------------------------------

CarlaBridgeToolkit* CarlaBridgeToolkit::createNew(CarlaBridgeFormat* const format)
{
    return new CarlaBridgeToolkitGtk(format);
}

// -------------------------------------------------------------------------

CARLA_BRIDGE_UI_END_NAMESPACE
