#include <gtkmm/window.h>
#include <gdkmm/frameclock.h>
#include <glibmm/main.h>
#include <gdk/wayland/gdkwayland.h>

#include <gtk-utils.hpp>
#include <wf-shell-app.hpp>
#include <gtk4-layer-shell.h>
#include <wf-autohide-window.hpp>

#include "dock.hpp"
#include "../util/gtk-utils.hpp"
#include "gtkmm/flowbox.h"
#include "network/manager.hpp"
#include "network/network-widget.hpp"
#include "network/network.hpp"
#include "wf-popover.hpp"
#include <css-config.hpp>


class WfDock::impl
{
    WayfireOutput *output;
    std::unique_ptr<WayfireAutohidingWindow> window;
    wl_surface *_wl_surface;
    Gtk::FlowBox box;
    std::unique_ptr<WayfireMenuWidget> network_image;
    std::unique_ptr<NetworkControlWidget> network_control;
    std::shared_ptr<NetworkManager> network_manager;

    WfOption<std::string> css_path{"dock/css_path"};
    WfOption<int> entries_per_line{"dock/max_per_line"};
    WfOption<std::string> position{"dock/position"};

    // needed as a workaround to shrink down when removing items
    WfOption<int> height{"dock/minimal_height"};
    WfOption<int> width{"dock/minimal_width"};

  public:
    impl(WayfireOutput *output)
    {
        this->output = output;
        window = std::unique_ptr<WayfireAutohidingWindow>(
            new WayfireAutohidingWindow(output, "dock"));
        window->set_auto_exclusive_zone(false);
        gtk_layer_set_layer(window->gobj(), GTK_LAYER_SHELL_LAYER_TOP);

        box.add_css_class("box");

        window->add_css_class("wf-dock");
        window->set_child(box);

        if (css_path.value() != "")
        {
            auto css = load_css_from_path(css_path);
            if (css)
            {
                auto display = Gdk::Display::get_default();
                Gtk::StyleContext::add_provider_for_display(display, css, GTK_STYLE_PROVIDER_PRIORITY_USER);
            }
        }

        window->present();
        _wl_surface = gdk_wayland_surface_get_wl_surface(
            window->get_surface()->gobj());

        box.add_tick_callback([=] (Glib::RefPtr<Gdk::FrameClock> fc)
        {
            set_clickable_region();
            return true;
        });

        auto update_entries_per_line = [=] ()
        {
            if (entries_per_line == 0)
            {
                box.set_min_children_per_line(-1);
                box.set_max_children_per_line(-1);
                return;
            }

            box.set_min_children_per_line(entries_per_line);
            box.set_max_children_per_line(entries_per_line);
        };
        entries_per_line.set_callback(update_entries_per_line);
        update_entries_per_line();

        auto update_position = [=] ()
        {
            if (position.value() == "bottom")
            {
                // this is not great, but we lack better options without doing a
                // layout with boxes in boxes (ugly) or some sort of custom layout manager
                box.set_orientation(Gtk::Orientation::HORIZONTAL);
                box.set_direction(Gtk::TextDirection::LTR);
            } else if (position.value() == "left")
            {
                box.set_orientation(Gtk::Orientation::VERTICAL);
                box.set_direction(Gtk::TextDirection::LTR);
            } else if (position.value() == "right")
            {
                box.set_orientation(Gtk::Orientation::VERTICAL);
                box.set_direction(Gtk::TextDirection::RTL);
            } else // top
            {
                box.set_orientation(Gtk::Orientation::HORIZONTAL);
                box.set_direction(Gtk::TextDirection::LTR);
            }
        };
        position.set_callback(update_position);
        update_position();
    }

    void add_child(Gtk::Widget& widget)
    {
        box.append(widget);
    }

    void rem_child(Gtk::Widget& widget)
    {
        box.remove(widget);
        window->set_default_size(width, height);
    }

    wl_surface *get_wl_surface()
    {
        return this->_wl_surface;
    }

    /* Sets the central section as clickable and transparent edges as click-through
     * Gets called regularly to ensure css size changes all register */
    void set_clickable_region()
    {
        auto surface = window->get_surface();
        auto widget_bounds = box.compute_bounds(*window);

        auto rect = Cairo::RectangleInt{
            (int)widget_bounds->get_x(),
            (int)widget_bounds->get_y(),
            (int)widget_bounds->get_width(),
            (int)widget_bounds->get_height()
        };

        auto region = Cairo::Region::create(rect);

        surface->set_input_region(region);
    }
};

WfDock::WfDock(WayfireOutput *output) :
    pimpl(new impl(output))
{}
WfDock::~WfDock() = default;

void WfDock::add_child(Gtk::Widget& w)
{
    return pimpl->add_child(w);
}

void WfDock::rem_child(Gtk::Widget& w)
{
    return pimpl->rem_child(w);
}

wl_surface*WfDock::get_wl_surface()
{
    return pimpl->get_wl_surface();
}
