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

    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 "../lifeograph.hpp"
#include "widget_filter.hpp"


using namespace LIFEO;

// FILTERER ========================================================================================
FiltererUI::FiltererUI( FiltererContainer* ptr2container, Filterer* filterer )
: Gtk::Box( Gtk::ORIENTATION_HORIZONTAL, 10 )
{
    if( ptr2container )
    {
        auto I_remove{ Gtk::manage( new Gtk::Image ) };
        m_B_remove = Gtk::manage( new Gtk::Button );

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

        pack_start( *m_B_remove, Gtk::PACK_SHRINK );

        m_B_remove->signal_clicked().connect(
                sigc::bind( sigc::mem_fun( ptr2container, &FiltererContainer::remove_filterer ),
                            filterer ) );
    }
}

// FILTERER STATUS =================================================================================
FiltererStatusUI::FiltererStatusUI( Diary* diary, FiltererContainer* ctr, ElemStatus es )
: FiltererStatus( diary, ctr, es ), FiltererUI( ctr, this )
{
    auto L_text{ Gtk::manage( new Gtk::Label( _( "Whose status is one of" ) ) ) };

    auto Bx_status{ Gtk::manage( new Gtk::Box( Gtk::ORIENTATION_HORIZONTAL, 0 ) ) };
    m_ChB_not_todo = Gtk::manage( new Gtk::ToggleButton );
    m_ChB_open = Gtk::manage( new Gtk::ToggleButton );
    m_ChB_progressed = Gtk::manage( new Gtk::ToggleButton );
    m_ChB_done = Gtk::manage( new Gtk::ToggleButton );
    m_ChB_canceled = Gtk::manage( new Gtk::ToggleButton );

    auto I_not_todo{ Gtk::manage( new Gtk::Image ) };
    auto I_open{ Gtk::manage( new Gtk::Image ) };
    auto I_progressed{ Gtk::manage( new Gtk::Image ) };
    auto I_done{ Gtk::manage( new Gtk::Image ) };
    auto I_canceled{ Gtk::manage( new Gtk::Image ) };

    I_not_todo->set_from_icon_name( "entry-16", Gtk::ICON_SIZE_MENU );
    I_open->set_from_icon_name( "todo_open-16", Gtk::ICON_SIZE_MENU );
    I_progressed->set_from_icon_name( "todo_progressed-16", Gtk::ICON_SIZE_MENU );
    I_done->set_from_icon_name( "todo_done-16", Gtk::ICON_SIZE_MENU );
    I_canceled->set_from_icon_name( "todo_canceled-16", Gtk::ICON_SIZE_MENU );

    m_ChB_not_todo->add( *I_not_todo );
    m_ChB_open->add( *I_open );
    m_ChB_progressed->add( *I_progressed );
    m_ChB_done->add( *I_done );
    m_ChB_canceled->add( *I_canceled );

    Bx_status->get_style_context()->add_class( "linked" );
    Bx_status->pack_start( *m_ChB_not_todo );
    Bx_status->pack_start( *m_ChB_open );
    Bx_status->pack_start( *m_ChB_progressed );
    Bx_status->pack_start( *m_ChB_done );
    Bx_status->pack_start( *m_ChB_canceled );

    m_ChB_not_todo->set_active( es & ES::SHOW_NOT_TODO );
    m_ChB_open->set_active( es & ES::SHOW_TODO );
    m_ChB_progressed->set_active( es & ES::SHOW_PROGRESSED );
    m_ChB_done->set_active( es & ES::SHOW_DONE );
    m_ChB_canceled->set_active( es & ES::SHOW_CANCELED );

    m_ChB_not_todo->signal_toggled().connect( [ this ](){ update_state(); } );
    m_ChB_open->signal_toggled().connect( [ this ](){ update_state(); } );
    m_ChB_progressed->signal_toggled().connect( [ this ](){ update_state(); } );
    m_ChB_done->signal_toggled().connect( [ this ](){ update_state(); } );
    m_ChB_canceled->signal_toggled().connect( [ this ](){ update_state(); } );

    pack_start( *L_text, Gtk::PACK_SHRINK );
    pack_start( *Bx_status, Gtk::PACK_SHRINK );
    show_all();
}

void
FiltererStatusUI::update_state()
{
    m_included_statuses = 0;

    if( m_ChB_not_todo->get_active() )
        m_included_statuses |= ES::SHOW_NOT_TODO;
    if( m_ChB_open->get_active() )
        m_included_statuses |= ES::SHOW_TODO;
    if( m_ChB_progressed->get_active() )
        m_included_statuses |= ES::SHOW_PROGRESSED;
    if( m_ChB_done->get_active() )
        m_included_statuses |= ES::SHOW_DONE;
    if( m_ChB_canceled->get_active() )
        m_included_statuses |= ES::SHOW_CANCELED;

    m_p2container->update_state();
}

// FILTERER FAVORITE ===============================================================================
FiltererFavoriteUI::FiltererFavoriteUI( Diary* diary, FiltererContainer* ctr, bool flag_include )
: FiltererFavorite( diary, ctr, flag_include ), FiltererUI( ctr, this )
{
    m_ChB_item = Gtk::manage( new Gtk::CheckButton( _( "Is favorite" ) ) );

    m_ChB_item->set_active( flag_include );
    m_ChB_item->signal_toggled().connect( [ this ](){
            m_include_favorite = m_ChB_item->get_active();
            m_p2container->update_state(); } );

    pack_start( *m_ChB_item );
    show_all();
}

// FILTERER TRASHED ================================================================================
FiltererTrashedUI::FiltererTrashedUI( Diary* diary, FiltererContainer* ctr, bool flag_include )
: FiltererTrashed( diary, ctr, flag_include ), FiltererUI( ctr, this )
{
    m_ChB_item = Gtk::manage( new Gtk::CheckButton( _( "Is trashed" ) ) );

    m_ChB_item->set_active( flag_include );
    m_ChB_item->signal_toggled().connect( [ this ](){
            m_include_trashed = m_ChB_item->get_active();
            m_p2container->update_state(); } );

    pack_start( *m_ChB_item );
    show_all();
}

// FILTERER IS NOT =================================================================================
FiltererIsUI::FiltererIsUI( Diary* diary, FiltererContainer* ctr, DEID id, bool f_is )
: FiltererIs( diary, ctr, id, f_is ), FiltererUI( ctr, this )
{
    m_CB_type = Gtk::manage( new Gtk::ComboBoxText );
    m_W_entry_picker = Gtk::manage( new WidgetEntryPicker( false ) );

    m_CB_type->append( _( "Is NOT" ) );
    m_CB_type->append( _( "Is" ) );
    m_CB_type->set_active( f_is ? 1 : 0 );
    m_CB_type->signal_changed().connect(
            [ this ]()
            {
                m_f_is = ( m_CB_type->get_active_row_number() == 1 );
                m_p2container->update_state();
            } );

    m_W_entry_picker->set_diary( diary );
    m_W_entry_picker->set_entry( diary->get_entry_by_id( id ) );
    m_W_entry_picker->signal_updated().connect(
            [ this ]( Entry* e ){
                m_id = m_W_entry_picker->get_entry_id();
                m_p2container->update_state(); } );

    pack_start( *m_CB_type, Gtk::PACK_SHRINK );
    pack_start( *m_W_entry_picker, Gtk::PACK_SHRINK );
    show_all();
}

// FILTERER TAGGED BY ==============================================================================
FiltererHasTagUI::FiltererHasTagUI( Diary* diary, FiltererContainer* ctr, Entry* tag,
                                        bool f_has )
: FiltererHasTag( diary, ctr, tag, f_has ), FiltererUI( ctr, this )
{
    m_CB_type = Gtk::manage( new Gtk::ComboBoxText );
    m_W_entry_picker = Gtk::manage( new WidgetEntryPicker( false, "tag-16-symbolic" ) );

    m_CB_type->append( _( "Has NOT the tag" ) );
    m_CB_type->append( _( "Has the tag" ) );
    m_CB_type->set_active( f_has ? 1 : 0 );
    m_CB_type->signal_changed().connect(
            [ this ]()
            {
                m_f_has = ( m_CB_type->get_active_row_number() == 1 );
                m_p2container->update_state();
            } );

    m_W_entry_picker->set_diary( diary );
    m_W_entry_picker->set_entry( tag );
    m_W_entry_picker->signal_updated().connect(
            [ this ]( Entry* e )
            {
                m_tag = m_W_entry_picker->get_entry();
                m_p2container->update_state();
            } );

    pack_start( *m_CB_type, Gtk::PACK_SHRINK );
    pack_start( *m_W_entry_picker, Gtk::PACK_SHRINK );
    show_all();
}

// FILTERER THEME ==================================================================================
FiltererThemeUI::FiltererThemeUI( Diary* diary, FiltererContainer* ctr, Theme* theme, bool f_has )
: FiltererTheme( diary, ctr, theme, f_has ), FiltererUI( ctr, this )
{
    m_CB_type = Gtk::manage( new Gtk::ComboBoxText );
    m_W_theme_picker = Gtk::manage( new WidgetPicker< Theme > );

    m_CB_type->append( _( "Has NOT the theme" ) );
    m_CB_type->append( _( "Has the theme" ) );
    m_CB_type->set_active( f_has ? 1 : 0 );
    m_CB_type->signal_changed().connect(
            [ this ]()
            {
                m_f_has = ( m_CB_type->get_active_row_number() == 1 );
                m_p2container->update_state();
            } );

    m_W_theme_picker->set_map( diary->get_p2themes(), &m_theme );
    m_W_theme_picker->set_select_only( true );
    if( theme )
        m_W_theme_picker->set_text( theme->get_name() );
    m_W_theme_picker->signal_sel_changed().connect(
            [ this ]( const Ustring& name )
            {
                m_theme = m_p2diary->get_theme( name );
                m_p2container->update_state();
            } );

    pack_start( *m_CB_type, Gtk::PACK_SHRINK );
    pack_start( *m_W_theme_picker, Gtk::PACK_SHRINK );
    show_all();
}

// FILTERER BETWEEN DATES ==========================================================================
FiltererBetweenDatesUI::FiltererBetweenDatesUI( Diary* diary, FiltererContainer* ctr,
                                                date_t date_b, bool f_incl_b,
                                                date_t date_e, bool f_incl_e )
: FiltererBetweenDates( diary, ctr, date_b, f_incl_b, date_e, f_incl_e ),
  FiltererUI( ctr, this )
{
    Gtk::Box*    Bx_range;
    Gtk::Button* B_incl_b;
    Gtk::Button* B_incl_e;
    Gtk::Button* B_flip;
    auto&&       builder{ Gtk::Builder::create() };

    Lifeograph::load_gui( builder, Lifeograph::SHAREDIR + "/ui/flt_between_entries.ui" );

    builder->get_widget( "Bx_range", Bx_range );
    builder->get_widget( "B_flip", B_flip );
    builder->get_widget( "B_incl_b", B_incl_b );
    builder->get_widget( "B_incl_e", B_incl_e );
    builder->get_widget( "L_incl_b", m_L_incl_b );
    builder->get_widget( "L_incl_e", m_L_incl_e );
    builder->get_widget_derived( "E_bgn", m_W_date_picker_b );
    builder->get_widget_derived( "E_end", m_W_date_picker_e );

    B_flip->signal_clicked().connect(
            [ this ]()
            {
                m_W_date_picker_b->set_date( m_date_e );
                m_W_date_picker_e->set_date( m_date_b );
                m_date_b = m_date_e;
                m_date_e = m_W_date_picker_e->get_date();
                m_p2container->update_state();
            } );

    m_W_date_picker_b->set_date( date_b );
    m_W_date_picker_b->signal_date_set().connect(
            [ this ]( date_t d ){ m_date_b = d; m_p2container->update_state(); } );

    m_W_date_picker_e->set_date( date_e );
    m_W_date_picker_e->signal_date_set().connect(
            [ this ]( date_t d ){ m_date_e = d; m_p2container->update_state(); } );

    m_L_incl_b->set_label( m_f_incl_b ? "[" : "(" );
    B_incl_b->signal_clicked().connect(
            [ this ]()
            {
                m_f_incl_b = !m_f_incl_b;
                m_L_incl_b->set_label( m_f_incl_b ? "[" : "(" );
                m_p2container->update_state();
            } );

    m_L_incl_e->set_label( m_f_incl_e ? "]" : ")" );
    B_incl_e->signal_clicked().connect(
            [ this ]()
            {
                m_f_incl_e = !m_f_incl_e;
                m_L_incl_e->set_label( m_f_incl_e ? "]" : ")" );
                m_p2container->update_state();
            } );

    pack_start( *Gtk::manage( new Gtk::Label( _( "Between" ) ) ), Gtk::PACK_SHRINK );
    pack_start( *Bx_range, Gtk::PACK_SHRINK );
    show_all();
}

// FILTERER BETWEEN ENTRIES ========================================================================
FiltererBetweenEntriesUI::FiltererBetweenEntriesUI( Diary* diary, FiltererContainer* ctr,
                                                    Entry* entry_b, bool f_incl_b,
                                                    Entry* entry_e, bool f_incl_e )
: FiltererBetweenEntries( diary, ctr, entry_b, f_incl_b, entry_e, f_incl_e ),
  FiltererUI( ctr, this )
{
    Gtk::Box*    Bx_range;
    Gtk::Button* B_incl_b;
    Gtk::Button* B_incl_e;
    Gtk::Button* B_flip;
    auto&&       builder{ Gtk::Builder::create() };

    Lifeograph::load_gui( builder, Lifeograph::SHAREDIR + "/ui/flt_between_entries.ui" );

    builder->get_widget( "Bx_range", Bx_range );
    builder->get_widget( "B_flip", B_flip );
    builder->get_widget( "B_incl_b", B_incl_b );
    builder->get_widget( "B_incl_e", B_incl_e );
    builder->get_widget( "L_incl_b", m_L_incl_b );
    builder->get_widget( "L_incl_e", m_L_incl_e );
    builder->get_widget_derived( "E_bgn", m_WEP_bgn );
    builder->get_widget_derived( "E_end", m_WEP_end );

    B_flip->signal_clicked().connect(
            [ this ]()
            {
                m_WEP_bgn->set_entry( m_entry_e );
                m_WEP_end->set_entry( m_entry_b );
                m_entry_b = m_entry_e;
                m_entry_e = m_WEP_end->get_entry();
                m_p2container->update_state();
            } );

    m_WEP_bgn->set_diary( diary );
    m_WEP_bgn->set_entry( entry_b );
    m_WEP_bgn->signal_updated().connect(
            [ this ]( Entry* e ){ m_entry_b = e; m_p2container->update_state(); } );

    m_WEP_end->set_diary( diary );
    m_WEP_end->set_entry( entry_e );
    m_WEP_end->signal_updated().connect(
            [ this ]( Entry* e ){ m_entry_e = e; m_p2container->update_state(); } );

    m_L_incl_b->set_label( m_f_incl_b ? "[" : "(" );
    B_incl_b->signal_clicked().connect(
            [ this ]()
            {
                m_f_incl_b = !m_f_incl_b;
                m_L_incl_b->set_label( m_f_incl_b ? "[" : "(" );
                m_p2container->update_state();
            } );

    m_L_incl_e->set_label( m_f_incl_e ? "]" : ")" );
    B_incl_e->signal_clicked().connect(
            [ this ]()
            {
                m_f_incl_e = !m_f_incl_e;
                m_L_incl_e->set_label( m_f_incl_e ? "]" : ")" );
                m_p2container->update_state();
            } );

    pack_start( *Gtk::manage( new Gtk::Label( _( "Between" ) ) ), Gtk::PACK_SHRINK );
    pack_start( *Bx_range, Gtk::PACK_SHRINK );
    show_all();
}

// FILTERER COMPLETION =============================================================================
FiltererCompletionUI::FiltererCompletionUI( Diary* diary, FiltererContainer* ctr,
                                            double compl_b, double compl_e )
: FiltererCompletion( diary, ctr, compl_b, compl_e ), FiltererUI( ctr, this )
{
    m_Sc_bgn = Gtk::manage( new Gtk::Scale( Gtk::ORIENTATION_HORIZONTAL ) );
    m_Sc_end = Gtk::manage( new Gtk::Scale( Gtk::ORIENTATION_HORIZONTAL ) );

    m_Sc_bgn->set_has_origin( false );
    m_Sc_bgn->set_value_pos( Gtk::POS_RIGHT );
    m_Sc_bgn->set_adjustment( Gtk::Adjustment::create( m_compl_b, 0.0, 100.0 ) );
    m_Sc_bgn->set_size_request( 160, -1 );
    m_Sc_bgn->signal_change_value().connect(
            [ this ]( Gtk::ScrollType, double value )->bool
            {
                m_compl_b = value;
                m_p2container->update_state();
                return false;
            } );

    m_Sc_end->set_has_origin( false );
    m_Sc_end->set_value_pos( Gtk::POS_LEFT );
    m_Sc_end->set_adjustment( Gtk::Adjustment::create( m_compl_e, 0.0, 100.0 ) );
    m_Sc_end->set_size_request( 160, -1 );
    m_Sc_end->signal_change_value().connect(
            [ this ]( Gtk::ScrollType, double value )->bool
            {
                m_compl_e = value;
                m_p2container->update_state();
                return false;
            } );

    pack_start( *Gtk::manage( new Gtk::Label( _( "Completion is in range" ) ) ), Gtk::PACK_SHRINK );
    pack_start( *m_Sc_bgn, Gtk::PACK_SHRINK );
    pack_start( *m_Sc_end, Gtk::PACK_SHRINK );
    show_all();
}

// WIDGET ENTRY LIST ===============================================================================
WidgetFilter::WidgetFilter( Diary* diary, FiltererContainer* ctr )
: FiltererContainer( diary, ctr, ctr && not( ctr->is_or() ) ), FiltererUI( ctr, this )
{
    // HEADER
    m_Bx_header = Gtk::manage( new Gtk::Box( Gtk::ORIENTATION_VERTICAL, 2 ) );
    m_L_logic = Gtk::manage( new Gtk::Label );
    auto Sp_top{ Gtk::manage( new Gtk::Separator( Gtk::ORIENTATION_VERTICAL ) ) };
    auto Sp_btm{ Gtk::manage( new Gtk::Separator( Gtk::ORIENTATION_VERTICAL ) ) };

    Sp_top->set_halign( Gtk::ALIGN_CENTER );
    Sp_btm->set_halign( Gtk::ALIGN_CENTER );
    update_logic_label();

    m_Bx_header->pack_start( *Sp_top );
    if( ctr )
        m_Bx_header->pack_start( *m_L_logic, Gtk::PACK_SHRINK );
    else
    {
        auto B_logic{ Gtk::manage( new Gtk::Button ) };
        B_logic->set_relief( Gtk::RELIEF_NONE );
        B_logic->add( *m_L_logic );
        B_logic->signal_clicked().connect( [ this ](){ toggle_logic(); } );
        m_Bx_header->pack_start( *B_logic, Gtk::PACK_SHRINK );
    }
    m_Bx_header->pack_start( *Sp_btm );

    // CONTENTS
    m_Bx_contents = Gtk::manage( new Gtk::Box( Gtk::ORIENTATION_VERTICAL, 5 ) );
    m_MB_add = Gtk::manage( new Gtk::MenuButton );
    auto I_add{ Gtk::manage( new Gtk::Image ) };
    m_Po_add = new Gtk::Popover;
    m_Bx_add = Gtk::manage( new Gtk::Box( Gtk::ORIENTATION_VERTICAL, 2 ) );

    declare_filterer< FiltererTrashedUI >( _( "Trashed" ) );
    declare_filterer< FiltererStatusUI >( _( "Status" ) );
    declare_filterer< FiltererFavoriteUI >( _( "Favorite" ) );
    declare_filterer< FiltererIsUI >( _( "Is/Not" ) );
    declare_filterer< FiltererBetweenDatesUI >( _( "Between Dates" ) );
    declare_filterer< FiltererBetweenEntriesUI >( _( "Between Entries" ) );
    declare_filterer< FiltererCompletionUI >( _( "Completion" ) );
    declare_filterer< FiltererHasTagUI >( _( "Has Tag" ) );
    declare_filterer< FiltererThemeUI >( _( "Has Theme" ) );
    if( !ctr ) // number of filter groups are limited to 2
        declare_filterer< WidgetFilter >( _( "Subgroup" ) );

    m_Bx_add->set_border_width( 5 );
    m_Bx_add->set_spacing( 5 );
    m_Bx_add->show_all();
    m_Po_add->add( *m_Bx_add );

    I_add->set_from_icon_name( "list-add-symbolic", Gtk::ICON_SIZE_BUTTON );
    m_MB_add->add( *I_add );
    m_MB_add->set_popover( *m_Po_add );
    m_MB_add->set_valign( Gtk::ALIGN_START );
    m_MB_add->set_halign( Gtk::ALIGN_START );
    m_Bx_contents->pack_end( *m_MB_add );

    pack_start( *m_Bx_header, Gtk::PACK_SHRINK );
    pack_start( *m_Bx_contents );

    show_all();
}

WidgetFilter::~WidgetFilter()
{
    clear();
    if( m_Po_add )
        delete m_Po_add;
}

void
WidgetFilter::remove_filterer( Filterer* filterer )
{
    m_Bx_contents->remove( * dynamic_cast< Gtk::Box* >( filterer ) );
    FiltererContainer::remove_filterer( filterer );
}

void
WidgetFilter::clear()
{
    for( auto& filterer : m_pipeline )
    {
        m_Bx_contents->remove( * dynamic_cast< Gtk::Box* >( filterer ) );
        delete filterer;
    }

    m_pipeline.clear();
}

void
WidgetFilter::set_enabled( bool flag_enabled )
{
    m_Bx_contents->set_sensitive( flag_enabled );
}

void
WidgetFilter::toggle_logic()
{
    m_flag_or = !m_flag_or;
    update_logic_label();

    // recursion
    for( auto& filterer : m_pipeline )
        if( filterer->is_container() )
            dynamic_cast< FiltererContainer* >( filterer )->toggle_logic();

    update_state();
}

void
WidgetFilter::update_logic_label()
{
    if( m_flag_or )
        m_L_logic->set_markup( STR::compose( "<b>", _( "OR" ), "</b>" ) );
    else
        m_L_logic->set_markup( STR::compose( "<b>", _( "AND" ), "</b>" ) );
}

void
WidgetFilter::update_state()
{
    if( m_p2container )
        m_p2container->update_state();
    else
        m_Sg_changed.emit();
}
