/***********************************************************************************

    Copyright (C) 2007-2026 Ahmet Öztürk (aoz_2@yahoo.com)

    This file is part of Lifeograph.

    Lifeograph 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 3 of the License, or
    (at your option) any later version.

    Lifeograph 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.

    You should have received a copy of the GNU General Public License
    along with Lifeograph.  If not, see <http://www.gnu.org/licenses/>.

***********************************************************************************/


#ifndef LIFEOGRAPH_HELPERS_GTK_HEADER
#define LIFEOGRAPH_HELPERS_GTK_HEADER


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <libintl.h>

#include <gtkmm.h>
#include <gcrypt.h>

#include "helpers.hpp"
#include "logid.hpp"


namespace LoG
{
    static const char   MIME_ENTRY[]        = "application/LoGentry";
    static const char   MIME_THEME[]        = "application/LoGtheme";
    static const char   MIME_CHART[]        = "application/LoGchart";
    static const char   MIME_TABLE[]        = "application/LoGtable";
    static const char   MIME_TEXT_LOG[]     = "application/LoGtext";
    static const char   MIME_TEXT_PLAIN[]   = "text/plain;charset=utf-8";
    static const char   MIME_LOG_FILE[]     = "application/x-lifeographdiary";


} // end of namespace LoG

namespace HELPERS
{

// TYPE ALIASES ====================================================================================
using R2Icon            = Glib::RefPtr< Gtk::IconPaintable >;
using R2ActionGroup     = Glib::RefPtr< Gio::SimpleActionGroup >;
using R2Action          = Glib::RefPtr< Gio::SimpleAction >;
using SignalKeyEvaluate = sigc::signal< bool( guint, Gdk::ModifierType ) >;

// ENTRY WIDGET WITH SELF-HANDLED CLEAR ICON =======================================================
class EntryClear : public Gtk::Entry
{
    public:
        EntryClear() : Gtk::Entry() { init(); }

        EntryClear( BaseObjectType* cobject, const Glib::RefPtr< Gtk::Builder >& )
        : Gtk::Entry( cobject ) { init(); }

        virtual ~EntryClear() {}  // does not seem to work...
                                  // ...probably Gtk::Entry's destructor is not virtual

        bool is_empty() const { return get_text().empty(); }

        void clear() { set_text ( "" ); }

    protected:
        void                init();

        void                handle_icon_press( Gtk::Entry::IconPosition );
        void                handle_icon_release( Gtk::Entry::IconPosition );
        virtual void        on_changed() override;
        virtual void        on_key_release_event( guint, guint, Gdk::ModifierType );
        bool                m_F_pressed{ false };
};

// DROPDOWN WIDGET WITH CUSTOM IDS FOR ELEMENTS ====================================================
template< typename T >
class DropDownAdvanced : public Gtk::DropDown
{
public:
    DropDownAdvanced() { init(); }
    DropDownAdvanced( const std::vector< std::pair< T, Ustring > >& items )
    {
        init();
        for( auto& it : items )
            add( it.second, it.first );
    }

    DropDownAdvanced( BaseObjectType* cobject, const Glib::RefPtr< Gtk::Builder >& )
    : Gtk::DropDown( cobject ) { init(); }

    void
    set_model( const Glib::RefPtr< Gtk::StringList >& ) = delete;

    // add an item with label and custom ID
    void
    add( const Ustring& label, const T& id )
    {
        m_F_internal = true;
        m_string_list->append( label );
        m_map.push_back( id );
        m_F_internal = false;
    }

    // get the custom ID of the currently selected item
    T
    get_selected_id() const
    {
        auto pos = get_selected();
        if( pos == GTK_INVALID_LIST_POSITION || pos >= m_map.size() )
            throw std::runtime_error( "No item selected" );

        return( m_map[ pos ] );
    }

    // get the custom ID for a specific position ---maybe later
    // T
    // get_id_at( guint position ) const { }

    // select by custom ID
    bool
    select_by_id( const T& id )
    {
        for( guint i = 0; i < m_map.size(); ++i )
        {
            if( m_map[ i ] == id )
            {
                set_selected( i );
                return true;
            }
        }
        return false;
    }

    // clear all items
    void
    clear()
    {
        m_F_internal = true;
        m_string_list->splice( 0, m_string_list->get_n_items(), {} );
        m_map.clear();
        m_F_internal = false;
    }

    // signal emitted when selection changes, with custom ID
    using SelectionChangedSignal = sigc::signal< void( const T ) >;
    SelectionChangedSignal          Sg_selection_changed;

private:
    inline void
    init()
    {
        m_F_internal = true;

        m_string_list = Gtk::StringList::create();
        Gtk::DropDown::set_model( m_string_list );

        // setup_factory();

        property_selected().signal_changed().connect(
                [ & ]()
                {
                    if( !m_F_internal )
                        try
                        { Sg_selection_changed.emit( get_selected_id() ); }
                        catch( ... ) { } // Ignore if no valid selection
                } );

        auto expression
        {
            Gtk::ClosureExpression< Ustring >::create(
                []( const Glib::RefPtr< Glib::ObjectBase >& item )->Ustring
                {
                    const auto str_obj { std::dynamic_pointer_cast< Gtk::StringObject >( item ) };
                    return str_obj ? str_obj->get_string() : "";
                } )
        };

        set_expression( expression );
        set_search_match_mode( Gtk::StringFilter::MatchMode::SUBSTRING );

        m_F_internal = false;
    }

    // TODO: 3.2: revisit. currently it does not force ellipsizing
    // void
    // setup_factory() {
    //     auto list_factory = Gtk::SignalListItemFactory::create();
    //
    //     list_factory->signal_setup().connect([](const Glib::RefPtr<Gtk::ListItem>& item) {
    //         auto label = Gtk::make_managed<Gtk::Label>();
    //         label->set_xalign(0.0);
    //         label->set_ellipsize( Pango::EllipsizeMode::MIDDLE );
    //         item->set_child(*label);
    //     } );
    //
    //     list_factory->signal_bind().connect([](const Glib::RefPtr<Gtk::ListItem>& item) {
    //         auto label = dynamic_cast<Gtk::Label*>(item->get_child());
    //         auto str_obj = std::dynamic_pointer_cast<Gtk::StringObject>(item->get_item());
    //         if (label && str_obj) {
    //             label->set_text(str_obj->get_string());
    //         }
    //     });
    //
    //     set_factory(list_factory);
    //
    //     auto label_factory = Gtk::SignalListItemFactory::create();
    //
    //     label_factory->signal_setup().connect([](const Glib::RefPtr<Gtk::ListItem>& item) {
    //         auto label = Gtk::make_managed<Gtk::Label>();
    //         label->set_xalign(0.0);
    //         label->set_ellipsize( Pango::EllipsizeMode::MIDDLE );
    //         item->set_child(*label);
    //     });
    //
    //     label_factory->signal_bind().connect([](const Glib::RefPtr<Gtk::ListItem>& item) {
    //         auto label = dynamic_cast<Gtk::Label*>(item->get_child());
    //         auto str_obj = std::dynamic_pointer_cast<Gtk::StringObject>(item->get_item());
    //         if (label && str_obj) {
    //             label->set_text(str_obj->get_string());
    //         }
    //     });
    //
    //     set_list_factory(label_factory);
    // }

    Glib::RefPtr< Gtk::StringList > m_string_list;
    std::vector< T >                m_map;
    bool                            m_F_internal  { false };
};

// specialized Versions:
using DropDownDEID = DropDownAdvanced< LoG::D::DEID >;

// ADVANCED MESSAGE DIALOG LIKE IN THE GOOD OLD DAYS ===============================================
class DialogMessage : public Gtk::Window
{
    public:
        static DialogMessage* init( Gtk::Window*, const Ustring&,
                                                  const Ustring& = _( "_Cancel" ) );

        DialogMessage*        add_extra_info( const Ustring&, bool = false );
        DialogMessage*        add_extra_widget( Gtk::Widget* );
        Gtk::Button*          add_button_b( const Ustring&, const FuncVoid& );
        DialogMessage*        add_cancel_handler( const FuncVoid& );
        DialogMessage*        add_button( const Ustring& l, const FuncVoid& h )
        { add_button_b( l, h ); return this; }
        DialogMessage*        add_button( const Ustring& l, const FuncVoid& h, const Ustring& c )
        { add_button_b( l, h )->add_css_class( c ); return this; }

        Gtk::Box*             m_Bx_content;
        Gtk::Box*             m_Bx_buttons;
        Gtk::Label*           m_L_msg;
        Gtk::Button*          m_B_cancel;

    protected:
        DialogMessage( const Ustring&, const Ustring& );

        static std::vector< DialogMessage* > s_dialog_store;
};

// DIALOGEVENT =====================================================================================
class DialogEvent : public Gtk::Dialog
{
    public:
                            DialogEvent( const Glib::ustring& );
                            DialogEvent( BaseObjectType*,
                                         const Glib::RefPtr< Gtk::Builder >& );

    protected:
        void                handle_logout();

};

// FILE CHOOSER BUTTON REPLACEMENT =================================================================
class FileChooserButton : public Gtk::Button
{
    public:
                            FileChooserButton( const Ustring& name ) : Gtk::Button( name )
                            { init(); }
                            FileChooserButton( BaseObjectType* obj,
                                               const Glib::RefPtr< Gtk::Builder >& builder )
                            : Gtk::Button( obj )
                            { init(); }

        void                set_icon( const String& );

        bool                is_empty() const { return m_uri.empty(); }
        String              get_uri() const { return m_uri; }
        void                set_uri( const String& );
        void                clear_uri( bool = false );

        void                set_info_text( const String& );

        void                add_file_filters( const Ustring&,
                                              const Ustring&,
                                              const Ustring& );
        void                add_image_file_filters();
        void                add_diary_file_filters();
        void                set_pick_folder_mode() { m_F_folder_mode = true; }

        static void         add_file_filters( Glib::RefPtr< Gtk::FileDialog >&,
                                              const Ustring&,
                                              const Ustring&,
                                              const Ustring& );
        static void         add_image_file_filters( Glib::RefPtr< Gtk::FileDialog >& );
        static void         add_diary_file_filters( Glib::RefPtr< Gtk::FileDialog >& );

        SignalVoidString    m_signal_file_set;

    protected:
        void                init();
        void                handle_click();

        Gtk::Label*         m_label;
        Gtk::Image*         m_icon;
        Glib::RefPtr< Gtk::FileDialog >
                            m_dlg;
        bool                m_F_folder_mode { false };
        Ustring             m_empty_text    { _( "Click to select..." ) };
        String              m_uri;
};

// FRAME FOR PRINTING ==============================================================================
//Gtk::Frame* create_frame( const Glib::ustring&, Gtk::Widget& );

// TREEVIEW ========================================================================================
// scroll_to_row( Path& ) does not work for some reason. So, implemented our own
bool is_treepath_less( const Gtk::TreePath&, const Gtk::TreePath& );
bool is_treepath_more( const Gtk::TreePath&, const Gtk::TreePath& );

// FIND CHAR PREDICATES ============================================================================
struct PredicateNL
{
    bool operator()( gunichar ch ) const { return( ch == '\n' ); }
};
struct PredicateBlank
{
    bool operator()( gunichar ch ) const { return( ch == '\n' || ch == ' ' || ch == '\t' ); }
};
struct PredicateNonVariable
{
    bool operator()( gunichar ch ) const { return ( !Glib::Unicode::isalnum( ch ) && ch != '_' ); }
};

// OTHER GTK HELPERS ===============================================================================
static constexpr int CTRL_SHIFT_MASK
{
    ( static_cast< int >( Gdk::ModifierType::CONTROL_MASK ) |
      static_cast< int >( Gdk::ModifierType::SHIFT_MASK ) )
};
static constexpr int ALT_SHIFT_MASK
{
    ( static_cast< int >( Gdk::ModifierType::ALT_MASK ) |
      static_cast< int >( Gdk::ModifierType::SHIFT_MASK ) )
};
static constexpr int CTRL_ALT_SHIFT_MASK
{
    ( static_cast< int >( Gdk::ModifierType::CONTROL_MASK ) |
      static_cast< int >( Gdk::ModifierType::ALT_MASK ) |
#ifdef __APPLE__
      static_cast< int >( Gdk::ModifierType::META_MASK ) |
#endif
      static_cast< int >( Gdk::ModifierType::SHIFT_MASK ) )
};
static constexpr int CTRL_META_MASK
{
    ( static_cast< int >( Gdk::ModifierType::CONTROL_MASK ) |
      static_cast< int >( Gdk::ModifierType::META_MASK ) )
};
static constexpr int CTRL_META_SHIFT_MASK
{
    ( static_cast< int >( Gdk::ModifierType::CONTROL_MASK ) |
      static_cast< int >( Gdk::ModifierType::META_MASK ) |
      static_cast< int >( Gdk::ModifierType::SHIFT_MASK ) )
};

// Gtk::MenuItem*      create_menuitem_markup( const Ustring&,
//                                             const Glib::SignalProxy0< void >::SlotType& );

void                remove_all_children_from_Bx( Gtk::Box* );
void                remove_all_children_from_LBx( Gtk::ListBox* );
void                remove_all_children_from_FBx( Gtk::FlowBox* );

void                flush_gtk_event_queue();

int                 get_LB_item_count( Gtk::ListBox* LB );
void                center_selected_LB_row( Gtk::ListBox* LB );
void                ensure_selected_LB_row_in_view( Gtk::ListBox*, Gtk::ListBoxRow* );
void                select_LB_item_prev( Gtk::ListBox* );
void                select_LB_item_next( Gtk::ListBox* );

Ustring             get_DD_selected_string( const Gtk::DropDown* );
bool                set_DD_selected_by_string( Gtk::DropDown*, const Ustring& );

R2Pixbuf            lookup_theme_icon_pixbuf( const Glib::RefPtr< Gtk::IconTheme >&,
                                              const Ustring& name, int size );

inline R2Icon
lookup_default_icon( const Ustring& name, int size )
{
    return Gtk::IconTheme::get_for_display( Gdk::Display::get_default() )->lookup_icon( name,
                                                                                        size );
}

} // end of namespace HELPERS

#endif

