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

    Copyright (C) 2007-2020 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_WIDGETPICKER_HEADER
#define LIFEOGRAPH_WIDGETPICKER_HEADER


#include <gtkmm.h>

#include "../diary.hpp"


namespace LIFEO
{

// WIDGET FOR SELECTING FROM DEFINED FILTERS IN THE DIARY ==========================================
template< class T >
class WidgetPicker : public Gtk::Entry
{
    public:
        WidgetPicker( BaseObjectType* o, const Glib::RefPtr< Gtk::Builder >& )
        : Gtk::Entry( o ) { initialize(); }
        WidgetPicker()
        { initialize(); }

        void                        set_map( std::map< Ustring, T*, FuncCmpStrings >* map,
                                             T* const * active )
        { m_p2map = map; m_p2p2active = active; }

        void                        set_text( const Ustring& text )
        {
            m_flag_internal = true;
            Gtk::Entry::set_text( text );
            m_flag_internal = false;
        }

        void                        set_clearable( bool F_clearable )
        { m_flag_clearable = F_clearable; }

        void                        set_select_only( bool F_select_only )
        {
            m_flag_select_only = F_select_only;

            m_B_add->set_visible( !F_select_only );
            m_Po->set_modal( !F_select_only );
        }

        void                        clear()
        { set_text( "" ); }
        bool                        update_list()
        {
            if( !m_p2map )
                return false;

            bool         f_exact_match{ false };
            const auto&& lowercase_filter{ STR::lowercase( m_text_cur ) };

            clear_list();

            if( m_flag_clearable )
                add_none_item_to_list();

            for( auto& kv : *m_p2map )
            {
                if( m_flag_select_only == false || m_flag_ignore_entered_text ||
                    STR::lowercase( kv.first ).find( lowercase_filter ) != Ustring::npos )
                    add_item_to_list( kv.first );
                if( kv.first == m_text_cur )
                {
                    m_LB->select_row( *m_LB->get_row_at_index( m_LB->get_children().size() - 1 ) );
                    f_exact_match = true;
                }
            }

            return f_exact_match;
        }

        SignalBoolUstring           signal_name_edited()
        { return m_Sg_name_edited; }
        SignalVoidUstring           signal_sel_changed()
        { return m_Sg_selection_changed; }
        SignalVoid                  signal_add()
        { return m_Sg_add; }
        SignalVoidUstring           signal_dismiss()
        { return m_Sg_dismiss; }

    protected:
        void                        initialize()
        {
            m_Po = new Gtk::Popover( *this );
            auto Bx_list{ Gtk::manage( new Gtk::Box( Gtk::ORIENTATION_VERTICAL, 5 ) ) };
            auto SW_list{ Gtk::manage( new Gtk::ScrolledWindow ) };
            m_LB = Gtk::manage( new Gtk::ListBox );
            auto I_add{ Gtk::manage( new Gtk::Image ) };
            m_B_add = Gtk::manage( new Gtk::Button );

            set_icon_from_icon_name( "pan-down-symbolic", Gtk::ENTRY_ICON_SECONDARY );

            I_add->set_from_icon_name( "list-add-symbolic", Gtk::ICON_SIZE_BUTTON );
            m_B_add->add( *I_add );

            Bx_list->set_border_width( 5 );
            SW_list->set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC );
            SW_list->set_min_content_width( 300 );
            SW_list->set_max_content_width( 500 );
            SW_list->set_min_content_height( 40 );
            SW_list->set_max_content_height( 350 );
            SW_list->set_propagate_natural_width( true );
            SW_list->set_propagate_natural_height( true );
            SW_list->set_shadow_type( Gtk::SHADOW_IN );

            SW_list->add( *m_LB );
            Bx_list->add( *SW_list );
            Bx_list->add( *m_B_add );
            m_Po->add( *Bx_list );

            m_Po->set_modal( !m_flag_select_only );

            Bx_list->show_all();
            if( m_flag_select_only )
                m_B_add->set_visible( false );

            signal_icon_release().connect(
                    [ this ]( Gtk::EntryIconPosition pos, const GdkEventButton* )
                    {
                        if( pos == Gtk::ENTRY_ICON_SECONDARY && m_Po->is_visible() == false )
                        {
                            m_flag_ignore_entered_text = true;
                            popup_list();
                            m_flag_ignore_entered_text = false;
                        }
                        else
                            m_Po->hide();
                    } );
            m_LB->signal_row_activated().connect( [ this ]( Gtk::ListBoxRow* ){
                    handle_selection_changed(); } );

            m_B_add->signal_clicked().connect(
                    [ this ]()
                    {
                        if( !m_p2p2active ) return;
                        m_Sg_add.emit();
                        set_text( ( *m_p2p2active )->get_name() );
                        update_list();
                        m_Po->hide();
                    } );
        }

        void                        add_item_to_list( const Ustring& name )
        {
            auto Bx_item{ Gtk::manage( new Gtk::Box( Gtk::ORIENTATION_HORIZONTAL, 5 ) ) };
            auto L_item_name{ Gtk::manage( new Gtk::Label( name, Gtk::ALIGN_START ) ) };

            L_item_name->set_margin_right( 15 ); // this makes it look better
            L_item_name->set_ellipsize( Pango::ELLIPSIZE_END );

            Bx_item->pack_start( *L_item_name );

            m_LB->add( *Bx_item );

            if( not( m_flag_select_only ) && m_p2map->size() > 1 )
            {
                auto B_remove{ Gtk::manage( new Gtk::Button ) };
                auto I_remove{ Gtk::manage( new Gtk::Image ) };

                I_remove->set_from_icon_name( "list-remove-symbolic", Gtk::ICON_SIZE_BUTTON );
                B_remove->add( *I_remove );
                B_remove->set_relief( Gtk::RELIEF_NONE );

                Bx_item->pack_end( *B_remove, Gtk::PACK_SHRINK );

                B_remove->signal_clicked().connect(
                        sigc::bind( sigc::mem_fun( m_Sg_dismiss, &SignalVoidUstring::emit ),
                                    name ) );
            }

            Bx_item->show_all();

            m_items.push_back( name );
        }
        void                        add_none_item_to_list()
        {
            auto L_item_none{ Gtk::manage( new Gtk::Label( "<i>&lt;" + STR0/SI::NONE + "&gt;</i>",
                                                           Gtk::ALIGN_START ) ) };
            L_item_none->set_use_markup( true );
            L_item_none->show();
            m_LB->add( *L_item_none );

            m_items.push_back( "" );
        }
        void                        clear_list()
        {
            m_LB->foreach( [ this ]( Gtk::Widget& w ){ m_LB->remove( w ); } );
            m_items.clear();
        }
        void                        popup_list()
        {
            if( m_p2map == nullptr )
                return;

            update_list();

            // select the correct row
            const auto text{ get_text() };
            for( unsigned int i = 0; i < m_items.size(); i++ )
            {
                if( m_items[ i ] == text )
                {
                    m_LB->select_row( * m_LB->get_row_at_index( i ) );
                    break;
                }
            }
            m_Po->set_pointing_to( get_icon_area( Gtk::ENTRY_ICON_SECONDARY ) );
            m_Po->show();
        }

        bool                        on_button_press_event( GdkEventButton* ev ) override
        {
            if( m_flag_select_only && m_Po->is_visible() == false )
            {
                m_flag_ignore_entered_text = true;
                popup_list();
                m_flag_ignore_entered_text = false;
            }

            return Gtk::Entry::on_button_press_event( ev );
        }

        void                        on_changed() override
        {
            m_text_cur = get_text();

            if( !m_flag_internal )
            {
                bool f_ok{ m_flag_select_only ?
                           update_list() : m_Sg_name_edited.emit( m_text_cur ) };

                if( f_ok )
                {
                    get_style_context()->remove_class( "error" );
                    if( m_flag_select_only )
                        m_Sg_selection_changed.emit( m_text_cur );
                }
                else
                    get_style_context()->add_class( "error" );

                if( m_flag_select_only && m_Po->is_visible() == false )
                    popup_list();
            }

            Gtk::Entry::on_changed();
        }

        bool                        on_key_press_event( GdkEventKey* event )
        {
            if( ( event->state & ( Gdk::CONTROL_MASK|Gdk::MOD1_MASK|Gdk::SHIFT_MASK ) ) == 0 )
            {
                if( m_Po->is_visible() && handle_key( event->keyval ) )
                    return true;
            }
            return Gtk::Entry::on_key_press_event( event );
        }

        Ustring                     get_selected_from_list()
        {
            return m_items.at( m_LB->get_selected_row()->get_index() );
        }
        void                        handle_selection_changed()
        {
            if( !m_flag_internal )
            {
                const auto&& filter_name{ get_selected_from_list() };
                set_text( filter_name );
                m_Sg_selection_changed.emit( filter_name );
                if( filter_name.empty() )
                    update_list();
                else
                    m_Po->hide();
                get_style_context()->remove_class( "error" );
            }
        }

        void                        select_next()
        {
            int i{ 0 };
            auto row{ m_LB->get_selected_row() };
            if( row )
            {
                i = ( row->get_index() + 1 ) % m_LB->get_children().size();
                m_LB->select_row( * m_LB->get_row_at_index( i ) );
            }
            else if( m_LB->get_children().empty() == false )
                m_LB->select_row( * m_LB->get_row_at_index( 0 ) );
        }

        void                        select_prev()
        {
            int i{ 0 };
            int size{ int( m_LB->get_children().size() ) };
            auto row{ m_LB->get_selected_row() };
            if( row )
            {
                i = ( row->get_index() + size - 1 ) % size;
                m_LB->select_row( * m_LB->get_row_at_index( i ) );
            }
            else if( m_LB->get_children().empty() == false )
                m_LB->select_row( * m_LB->get_row_at_index( 0 ) );
        }

        bool                        handle_key( guint keyval )
        {
            switch( keyval )
            {
                case GDK_KEY_Return:
                    handle_selection_changed();
                    m_Po->hide();
                    return true;
                case GDK_KEY_Escape:
                    m_Po->hide();
                    return true;
                case GDK_KEY_Down:
                    select_next();
                    return true;
                case GDK_KEY_Up:
                    select_prev();
                    return true;
            }

            return false;
        }

        Gtk::Popover*               m_Po{ nullptr };
        Gtk::ListBox*               m_LB;
        Gtk::Button*                m_B_add;

        std::vector< Ustring >      m_items;

        SignalBoolUstring           m_Sg_name_edited;
        SignalVoidUstring           m_Sg_selection_changed;
        SignalVoid                  m_Sg_add;
        SignalVoidUstring           m_Sg_dismiss;

        const std::map< Ustring, T*, FuncCmpStrings >*
                                    m_p2map{ nullptr };
        const T* const *            m_p2p2active{ nullptr };
        Ustring                     m_text_cur;

        bool                        m_flag_internal{ false };
        bool                        m_flag_clearable{ false };
        bool                        m_flag_select_only{ false };
        bool                        m_flag_ignore_entered_text{ false };
};

} // end of namespace LIFEO

#endif
