#ifndef GPANTHERA_LIBRARY_H
#define GPANTHERA_LIBRARY_H

#include <gtkmm.h>
#include <glibmm.h>
#include <glibmm/ustring.h>
#include <utility>
#include <vector>
#include <memory>

namespace gPanthera {
    void init();
    // Utility function to create a signal that fires when a context menu is requested
    sigc::signal<void(double, double)> add_context_menu(Gtk::Widget &widget);
    std::vector<Gtk::Widget*> collect_children(Gtk::Widget &widget);
    Gtk::Image *copy_image(Gtk::Image *image);

    class DockStack;
    class DockWindow;
    class LayoutManager;

    class DockablePane : public Gtk::Box {
    protected:
        Gtk::Widget *child;
        Gtk::Label label;
        Glib::ustring name;
        Gtk::Image *icon;
        DockStack *stack = nullptr;
        DockWindow *window = nullptr;
        std::unique_ptr<Gtk::HeaderBar> header;
        Glib::RefPtr<Gio::SimpleActionGroup> action_group;
    public:
        DockWindow *get_window() const;
        std::shared_ptr<LayoutManager> layout;
        Glib::RefPtr<Gio::Menu> header_menu = Gio::Menu::create();
        DockStack *last_stack = nullptr;
        DockablePane(std::shared_ptr<LayoutManager> layout, Gtk::Widget &child, const Glib::ustring &name, const Glib::ustring &label, Gtk::Image *icon, DockStack *stack = nullptr, Gtk::Widget *custom_header = nullptr);
        Gtk::Stack *get_stack() const;
        Glib::ustring get_identifier() const;
        Gtk::Image *get_icon() const;
        Gtk::Label *get_label();
        void redock(DockStack *stack);
        void pop_out();
        Gtk::Widget *get_child() const;
        sigc::signal<void()> signal_pane_shown;
        sigc::signal<void()> signal_pane_hidden;
    };

    class DockWindow : public Gtk::Window {
    private:
        DockablePane *pane;
    public:
        explicit DockWindow(DockablePane *pane);
    };

    class LayoutManager : public Glib::ObjectBase {
    private:
    public:
        std::vector<DockablePane*> panes;
        std::vector<DockStack*> stacks;
        sigc::signal<void(DockablePane*)> signal_pane_moved;
        LayoutManager();
        std::string get_layout_as_json() const;

        void restore_json_layout(const std::string& json_string);

        void add_pane(DockablePane *pane);

        void add_stack(DockStack *stack);
        void remove_pane(DockablePane *pane);
        void remove_stack(DockStack *stack);
    };

    class BaseStack : public Gtk::Stack {
    public:
        sigc::signal<void(Gtk::Widget*)> signal_child_added;
        sigc::signal<void(Gtk::Widget*)> signal_child_removed;
        BaseStack();
        void add(Gtk::Widget &child, const Glib::ustring &name);
        void remove(Gtk::Widget &child);
        void add(Gtk::Widget &child);
        void add(Gtk::Widget &child, const Glib::ustring &name, const Glib::ustring &title) = delete;
    };

    class DockStack : public BaseStack {
    private:
    public:
        std::shared_ptr<LayoutManager> layout;
        Glib::ustring name;
        std::string id;
        DockablePane *last_pane = nullptr;
        explicit DockStack(std::shared_ptr<LayoutManager> layout, const Glib::ustring &name, const std::string &id);

        void add_pane(DockablePane &child);
    };

    class DockButton : public Gtk::ToggleButton {
    private:
        sigc::connection active_style_handler;
        std::shared_ptr<Gtk::DragSource> drag_source;
        Glib::Value<DockablePane*> value;
        void update_active_style();
    public:
        DockablePane *pane;
        explicit DockButton(DockablePane *pane);
        ~DockButton() override;
    };

    class DockStackSwitcher : public Gtk::Box {
    private:
        DockStack *stack;
        sigc::connection add_handler, remove_handler;
        std::shared_ptr<Gtk::DropTarget> drop_target;
    public:
        void update_buttons();
        explicit DockStackSwitcher(DockStack *stack, Gtk::Orientation orientation = Gtk::Orientation::HORIZONTAL);
        DockStack *get_stack() const;
        ~DockStackSwitcher() override;
    };

    class ContentStack;
    class ContentPage;

    class ContentManager : public Glib::ObjectBase {
    private:
        ContentPage *last_operated_page = nullptr;
    public:
        ContentPage *get_last_operated_page() const;
        void set_last_operated_page(ContentPage *page);
        sigc::signal<void(ContentPage*)> signal_page_operated, signal_page_moving, signal_page_moved, signal_page_closing, signal_page_closed;
        std::vector<ContentStack*> stacks;
        ContentManager();

        void add_stack(ContentStack *stack);
        void remove_stack(ContentStack *stack);
    };

    class ContentPage : public Gtk::Overlay {
    protected:
        ContentStack *stack = nullptr;
        Gtk::Widget *child;
    public:
        Gtk::Widget *tab_widget;
        std::shared_ptr<ContentManager> content_manager;
        ContentStack *last_stack = nullptr;
        ContentPage(std::shared_ptr<ContentManager> content_manager, ContentStack *stack, Gtk::Widget *child, Gtk::Widget *tab_widget);
        sigc::signal<bool()> signal_close;
        sigc::signal<void(bool)> signal_control_status_changed;
        void close();
        Gtk::Widget *get_tab_widget() const;
        void redock(ContentStack *stack);
        void set_tab_widget(Gtk::Widget *tab_widget);
        ContentStack *get_stack() const;
        void lose_visibility();
    };

    class ContentTab : public Gtk::ToggleButton {
    private:
        std::shared_ptr<Gtk::DragSource> drag_source;
        std::shared_ptr<Gtk::DropTarget> drop_target;
        Glib::Value<ContentPage*> value;
        Glib::RefPtr<Gio::Menu> context_menu = Gio::Menu::create();
        sigc::connection drag_end_handler;
        sigc::connection drag_cancel_handler;
        void update_active_style();
        sigc::connection active_style_handler;
        sigc::connection control_status_handler;
        Gtk::PopoverMenu *popover = nullptr;
    public:
        void close();
        explicit ContentTab(ContentPage *page);
        ContentPage *page;
        ~ContentTab() override;
    };

    class ContentStack : public BaseStack {
    private:
        void disable_children();
        void enable_children();
    protected:
        std::function<bool(ContentPage*)> detach_handler;
    public:
        sigc::signal<bool(ContentPage*)> signal_detach;
        std::function<bool(ContentPage*)> get_detach_handler() const;
        std::shared_ptr<ContentManager> content_manager;
        std::shared_ptr<Gtk::DropTarget> drop_target;
        sigc::signal<void()> signal_leave_empty;
        explicit ContentStack(std::shared_ptr<ContentManager> content_manager, std::function<bool(ContentPage*)> detach_handler = nullptr);
        void make_paned(Gtk::Orientation orientation, Gtk::PackType pack_type);
        void add_page(ContentPage &child);
        void remove_with_paned();
    };

    class ContentTabBar : public Gtk::Box {
        // Dragging a tab to an empty space should split the view;
        // dragging a tab outside the window should pop it out with user-defined behaviour
    private:
        ContentStack *stack;
        sigc::connection add_handler, remove_handler;
        std::shared_ptr<Gtk::DropTarget> drop_target;
        Gtk::ScrolledWindow *scrolled_window;
        Gtk::Box *tab_box;
        std::function<Gtk::Widget*(gPanthera::ContentTabBar*)> extra_child_function;
    public:
        void update_buttons();
        explicit ContentTabBar(ContentStack *stack, Gtk::Orientation orientation = Gtk::Orientation::HORIZONTAL, std::function<Gtk::Widget*(gPanthera::ContentTabBar*)> extra_child_function = nullptr);
        void set_orientation(Gtk::Orientation orientation);
        void set_extra_child_function(std::function<Gtk::Widget*(gPanthera::ContentTabBar*)>);
        ContentStack *get_stack() const;
        ~ContentTabBar() override;
        std::function<Gtk::Widget*(gPanthera::ContentTabBar*)> get_extra_child_function() const;
    };

    class ContentNotebook : public Gtk::Box {
    private:
        ContentStack *stack;
        ContentTabBar *switcher;
        Gtk::PositionType tab_position;
    public:
        ContentNotebook(ContentStack *stack, ContentTabBar *switcher, Gtk::PositionType tab_position = Gtk::PositionType::TOP);
        Gtk::PositionType get_tab_position() const;
        ContentStack *get_stack() const;
        ContentTabBar *get_switcher() const;
    };

    class ContentWindow : public Gtk::Window {
    private:
        ContentNotebook *notebook;
    public:
        ContentWindow(ContentNotebook *notebook);
    };
} // namespace gPanthera

#endif // GPANTHERA_LIBRARY_H