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

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

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


#include "../helpers.hpp"
#include "../lifeograph.hpp"
#include "widget_entrypicker.hpp"


using namespace LIFEO;


// TREESTORE DIARY ELEM ============================================================================
/*TreeStoreDiaryElem::TreeStoreDiaryElem()
{
    set_column_types( colrec );
}

Glib::RefPtr< TreeStoreDiaryElem > TreeStoreDiaryElem::create()
{
    return Glib::RefPtr< TreeStoreDiaryElem >( new TreeStoreDiaryElem() );
}*/

// COMPLETION POPOVER ==============================================================================
EntryPickerCompletion::EntryPickerCompletion( const Gtk::Widget& parent, bool flag_editable )
: m_flag_offer_new( flag_editable )
{
    m_Po = new Gtk::Popover( parent );
    auto SW_list{ Gtk::manage( new Gtk::ScrolledWindow ) };
    m_LB = Gtk::manage( new Gtk::ListBox );

    m_Po->set_modal( false );
    SW_list->set_border_width( 5 );
    SW_list->set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC );
    SW_list->set_min_content_width( 400 );
    SW_list->set_max_content_width( 600 );
    SW_list->set_min_content_height( 50 );
    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 );
    m_Po->add( *SW_list );

    SW_list->show_all();

    m_LB->signal_row_activated().connect( [ this ]( Gtk::ListBoxRow* ){
            m_Sg_entry_activated.emit( get_selected() ); popdown(); } );
}

EntryPickerCompletion::~EntryPickerCompletion()
{
    // following creates problems for some reason:
    //if( m_Po )
    //    delete m_Po;
}

void
EntryPickerCompletion::add_item( Entry* entry )
{
    auto Bx_item{ Gtk::manage( new Gtk::Box( Gtk::ORIENTATION_VERTICAL ) ) };
    auto L_item_name{ Gtk::manage( new Gtk::Label( entry->get_name(), Gtk::ALIGN_START ) ) };
    auto L_item_info{ Gtk::manage( new Gtk::Label( entry->get_description(), Gtk::ALIGN_START ) ) };

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

    Pango::AttrList attrlist;
    auto&& attrscale{ Pango::Attribute::create_attr_scale( 0.8 ) };
    attrlist.insert( attrscale );
    L_item_info->set_attributes( attrlist );
    L_item_info->set_sensitive( false );
    L_item_info->set_ellipsize( Pango::ELLIPSIZE_END );

    Bx_item->add( *L_item_name );
    Bx_item->add( *L_item_info );
    Bx_item->show_all();

    m_LB->add( *Bx_item );

    m_items.push_back( entry );
}

void
EntryPickerCompletion::add_new_item( const Ustring& name )
{
    auto&& text{ Ustring::compose( _( "Create New Entry: %1" ),
                                      STR::compose( "<b>", name, "</b>" ) ) };
    auto   Bx_item{ Gtk::manage( new Gtk::Box( Gtk::ORIENTATION_VERTICAL ) ) };
    auto   L_item_name{ Gtk::manage( new Gtk::Label( text, Gtk::ALIGN_START ) ) };

    L_item_name->set_use_markup( true );

    Bx_item->add( *L_item_name );
    Bx_item->show_all();

    m_LB->add( *Bx_item );

    m_items.push_back( nullptr );
}

void
EntryPickerCompletion::clear()
{
    m_LB->foreach( [ this ]( Gtk::Widget& w ){ m_LB->remove( w ); } );
    m_items.clear();
}

void
EntryPickerCompletion::popup( const Ustring& filter )
{
    Gdk::Rectangle rect{ 0, 0,
                         m_Po->get_relative_to()->get_width(),
                         m_Po->get_relative_to()->get_height() };

    popup( rect, filter );
}

void
EntryPickerCompletion::popup( const Gdk::Rectangle& rect, const Ustring& filter )
{
    update_list( filter );

    if( m_LB->get_children().size() == 0 ) // TODO: is this still meaningful?
        return;

    m_LB->select_row( * m_LB->get_row_at_index( 0 ) );
    m_Po->set_pointing_to( rect );
    m_Po->show();
}

void
EntryPickerCompletion::popdown()
{
    m_Po->hide();
}

void
EntryPickerCompletion::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 ) );
    }
}

void
EntryPickerCompletion::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 ) );
    }
}

void
EntryPickerCompletion::update_list( const Ustring& filter )
{
    if( !m_ptr2diary )
        return;

    clear();

    PRINT_DEBUG( "FILTER: ", filter );

    const Ustring&& filter_l{ STR::lowercase( filter ) };
    bool            flag_exact_match{ false };
    int             count{ 0 };

    for( auto& kv : m_ptr2diary->get_entry_names() )
    {
        const Ustring&& entry_name_l{ STR::lowercase( kv.first ) };

        if( entry_name_l.find( filter_l ) != Ustring::npos )
        {
            add_item( kv.second );

            if( entry_name_l == filter_l )
                flag_exact_match = true;

            if( ++count > 16 )
                break;
        }
    }

    if( m_flag_offer_new && not( flag_exact_match ) )
        add_new_item( filter );
}

bool
EntryPickerCompletion::handle_key( guint keyval )
{
    switch( keyval )
    {
        case GDK_KEY_Return:
            m_Sg_entry_activated.emit( get_selected() );
            popdown();
            return true;
        case GDK_KEY_Escape:
            popdown();
            return true;
        case GDK_KEY_Down:
            select_next();
            return true;
        case GDK_KEY_Up:
            select_prev();
            return true;
    }

    return false;
}

Entry*
EntryPickerCompletion::get_selected() const
{
    return m_items[ m_LB->get_selected_row()->get_index() ];
}

// ENTRY PICKER WIDGET =============================================================================
WidgetEntryPicker::WidgetEntryPicker( BaseObjectType* o, const Glib::RefPtr<Gtk::Builder>& b )
: EntryClear( o, b ), m_completion( *this )
{
    initialize();
}

WidgetEntryPicker::WidgetEntryPicker( bool flag_editable, const std::string& icon_name )
: m_completion( *this, flag_editable )
{
    if( not( icon_name.empty() ) )
        set_icon_from_icon_name( icon_name, Gtk::ENTRY_ICON_PRIMARY );

    initialize();
}

void
WidgetEntryPicker::initialize()
{
    m_completion.signal_entry_activated().connect(
            [ this ]( LIFEO::Entry* e ){ handle_entry_selected( e ); } );

    if( get_icon_name( Gtk::ENTRY_ICON_PRIMARY ) == "" ) // if not set in the ui file
        set_icon_from_icon_name( "entry-16-symbolic", Gtk::ENTRY_ICON_PRIMARY );

}

void
WidgetEntryPicker::set_entry( LIFEO::Entry* entry )
{
    Lifeograph::START_INTERNAL_OPERATIONS();
    m_ptr2entry = entry;
    Gtk::Entry::set_text( entry ? entry->get_name() : "" );
    get_style_context()->remove_class( "error" );
    Lifeograph::FINISH_INTERNAL_OPERATIONS();
}

Entry*
WidgetEntryPicker::get_entry_create( Diary* diary )
{
    if( m_ptr2entry )
        return m_ptr2entry;

    if( !diary || get_text().empty() )
        return nullptr;

    return diary->create_entry( diary->get_available_order_1st( true ), get_text() );
}

void
WidgetEntryPicker::on_changed()
{
    if( Lifeograph::is_internal_operations_ongoing() )
        return;

    const Ustring text_new{ get_text() };
    bool flag_ok{ false };

    // empty
    if( text_new.empty() )
    {
        m_ptr2entry = nullptr;
        m_completion.popdown();
        flag_ok = true;
    }
    else
    {
        m_completion.popup( text_new );

        auto&& ev{ m_completion.get_diary()->get_entries_by_name( text_new ) };
        if( ev.size() == 1 )
        {
            m_ptr2entry = ev[ 0 ];
            flag_ok = true;
        }
        else
            m_ptr2entry = nullptr;
    }

    m_Sg_updated.emit( m_ptr2entry );

    if( flag_ok )
        get_style_context()->remove_class( "error" );
    else
        get_style_context()->add_class( "error" );

    EntryClear::on_changed();
}

bool
WidgetEntryPicker::on_key_press_event( GdkEventKey* event )
{
    if( ( event->state & ( Gdk::CONTROL_MASK|Gdk::MOD1_MASK|Gdk::SHIFT_MASK ) ) == 0 )
    {
        if( m_completion.is_on_display() && m_completion.handle_key( event->keyval ) )
            return true;
    }
    return EntryClear::on_key_press_event( event );
}

void
WidgetEntryPicker::handle_entry_selected( LIFEO::Entry* entry )
{
    set_entry( entry );
    m_Sg_updated.emit( entry );
}

bool
WidgetEntryPicker::on_drag_motion( const Glib::RefPtr<Gdk::DragContext>& context,
                                   int x, int y, guint time )
{
    if( Lifeograph::get_dragged_elem() != nullptr &&
        Lifeograph::get_dragged_elem()->get_type() >= DiaryElement::ET_ENTRY )
    {
        context->drag_status( Gdk::ACTION_COPY, time );
        drag_highlight();
        return true;
    }

    drag_unhighlight();  // is called many times unnecessarily :(
    context->drag_refuse( time );
    return false;
}

bool
WidgetEntryPicker::on_drag_drop( const Glib::RefPtr< Gdk::DragContext >& context,
                                 int x, int y, guint time )
{
    context->drag_finish( true, false, time );

    if( Lifeograph::get_dragged_elem() != nullptr &&
        Lifeograph::get_dragged_elem()->get_type() >= DiaryElement::ET_ENTRY )
    {
        handle_entry_selected( dynamic_cast< LIFEO::Entry* >( Lifeograph::get_dragged_elem() ) );
        return true;
    }

    return false;
}

// SIMPLE POPOVER FOR SELECTING AN ENTRY FROM A GROUP ==============================================
void
LIFEO::show_entry_selection_list( const VecEntries& v_entries,
                                  std::function< void( LIFEO::Entry* ) >&& select_handler,
                                  Gtk::Widget& host )
{
    static Gtk::Popover*        po{ nullptr };
    static Gtk::Box*            bx{ nullptr };
    static Gtk::ScrolledWindow* sw{ nullptr };

    Gdk::Rectangle    rect;
    int               x_pointer, y_pointer;
    Gdk::ModifierType modifiers;

    if( !po )
    {
        po = Gtk::manage( new Gtk::Popover );
        bx = Gtk::manage( new Gtk::Box( Gtk::ORIENTATION_VERTICAL ) );
        sw = Gtk::manage( new Gtk::ScrolledWindow );

        sw->set_max_content_width( 500 );
        sw->set_max_content_height( 500 );
        sw->set_propagate_natural_height( true );
        sw->set_propagate_natural_width( true );
        bx->set_spacing( 5 );
        po->add( *sw );
        sw->add( *bx );
    }
    else
        bx->foreach( [ & ]( Gtk::Widget& w ){ bx->remove( w ); } ); // clear

    SetEntriesByName entries; // eliminate duplicates and sort
    for( auto& entry : v_entries )
        entries.emplace( entry );

    for( auto& entry : entries )
    {
        Gtk::ModelButton* mb = Gtk::manage( new Gtk::ModelButton );
        mb->property_text() = entry->get_name();
        mb->signal_clicked().connect( sigc::bind( select_handler, entry ) );
        bx->pack_start( *mb );
    }

    host.get_window()->get_pointer( x_pointer, y_pointer, modifiers );
    rect.set_x( x_pointer );
    rect.set_y( y_pointer );

    po->show_all_children();
    po->set_relative_to( host );
    po->set_pointing_to( rect );
    po->show();
}
