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

    Copyright (C) 2007-2018 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/>.

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


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

#include <iomanip>

#include "lifeograph.hpp"
#include "app_window.hpp"
#include "panel_diary.hpp"
#include "view_entry.hpp"
#include "widget_textview.hpp"


using namespace LIFEO;

// TEMPORARY: until SearchEntry::signal_search_changed() is implemented in Gtkmm
void handle_search_changed_tmp( GtkSearchEntry* entry, gpointer data )
{
    PanelDiary* pd( reinterpret_cast< PanelDiary* >( data ) );
    pd->handle_search_string_changed();
}

PanelDiary::PanelDiary()
{
    Gtk::Button* button_today;
    try
    {
        auto builder{ Lifeograph::get_builder() };
        builder->get_widget( "G_elem_header", m_G_elem_header );
        builder->get_widget( "Bx_new_elem", m_Bx_new_elem );
        builder->get_widget( "B_today", button_today );

        builder->get_widget( "MB_diary_add", m_MB_add );
        builder->get_widget( "L_add_entry", m_L_add_entry );
        builder->get_widget( "L_add_chapter", m_L_add_chapter );
        builder->get_widget( "L_add_topic", m_L_add_topic );
        builder->get_widget( "L_add_group", m_L_add_group );

        builder->get_widget( "Bx_logout", m_Bx_logout );
        builder->get_widget( "B_logout", m_B_logout );
        builder->get_widget( "MB_logout", m_MB_logout );

        builder->get_widget( "MB_tools", m_B_tools );
        builder->get_widget( "RB_sort_date", m_RB_sort_by_date );
        builder->get_widget( "RB_sort_size", m_RB_sort_by_size );
        builder->get_widget( "RB_sort_change", m_RB_sort_by_change );
        builder->get_widget( "RB_sort_descending", m_RB_sort_descending );
        builder->get_widget( "RB_sort_ascending", m_RB_sort_ascending );

        builder->get_widget( "Bx_search", m_Bx_search );
        builder->get_widget( "Po_filter", m_Po_filter );
        builder->get_widget( "B_filter_revert", m_B_filter_revert );
        builder->get_widget( "E_search", m_E_search );
        builder->get_widget_derived( "E_replace", m_E_replace );
        builder->get_widget( "B_match_prev", m_B_match_prev );
        builder->get_widget( "B_match_next", m_B_match_next );
        builder->get_widget( "Po_replace", m_Po_replace );
        builder->get_widget( "B_replace", m_B_replace );
        builder->get_widget( "B_replace_all", m_B_replace_all );

        // THEMABLE ICONS
        Gtk::Image* img;
        builder->get_widget( "I_add_entry", img );
        img->set( Lifeograph::icons->entry_32 );
        builder->get_widget( "I_add_chapter", img );
        img->set( Lifeograph::icons->chapter_temp_32 );
        builder->get_widget( "I_add_topic", img );
        img->set( Lifeograph::icons->chapter_ordr_32 );
        builder->get_widget( "I_add_group", img );
        img->set( Lifeograph::icons->chapter_free_32 );

        m_A_add_entry = Lifeograph::p->add_action(
                "add_entry", sigc::mem_fun( this, &PanelDiary::handle_add_entry ) );
        m_A_add_chapter = Lifeograph::p->add_action(
                "add_chapter", sigc::mem_fun( this, &PanelDiary::handle_add_chapter ) );
        m_A_add_topic = Lifeograph::p->add_action(
                "add_topic", sigc::mem_fun( this, &PanelDiary::handle_add_topic ) );
        m_A_add_group = Lifeograph::p->add_action(
                "add_group", sigc::mem_fun( this, &PanelDiary::handle_add_group ) );

        // FALLBACK ICON FOR SYSTEMS WITHOUT SYMBOLIC ICONS
        Glib::RefPtr< Gtk::IconTheme > theme = Gtk::IconTheme::get_default();
        if( ! theme->has_icon( "preferences-system-symbolic" ) &&
            theme->has_icon( "gtk-preferences" ) )
        {
            Glib::RefPtr< Gdk::Pixbuf > icon_tools = theme->load_icon( "gtk-preferences", 16 );
            img = Gtk::manage( new Gtk::Image( icon_tools ) );
            m_B_tools->set_image( *img );
        }

        builder->get_widget( "calendar_main", m_calendar );
        builder->get_widget_derived( "treeview_main", m_TV_entries );

        m_Po_replace->set_relative_to( *m_E_search );
    }
    catch( ... )
    {
        throw LIFEO::Error( "Failed to create the diary panel" );
    }



    // SIGNALS
    m_B_logout->signal_clicked().connect(
            sigc::bind( sigc::mem_fun( AppWindow::p, &AppWindow::logout ), true ) );

    m_RB_sort_by_date->signal_toggled().connect(
            sigc::mem_fun( this, &PanelDiary::update_sorting ) );

    m_RB_sort_by_size->signal_toggled().connect(
            sigc::mem_fun( this, &PanelDiary::update_sorting ) );
    // the 3rd radiobutton is not necessary as untoggle signal takes care of that
    m_RB_sort_descending->signal_toggled().connect(
            sigc::mem_fun( this, &PanelDiary::update_sorting ) );

    m_calendar->signal_day_selected_double_click().connect(
            sigc::mem_fun( this, &PanelDiary::handle_add_entry ) );
    m_calendar->signal_month_changed().connect(
            sigc::mem_fun( this, &PanelDiary::handle_calendar_monthchanged ) );
    m_calendar->signal_day_selected().connect(
            sigc::mem_fun( this, &PanelDiary::handle_calendar_dayselected ) );

    m_TV_entries->signal_row_expanded().connect(
            sigc::mem_fun( this, &PanelDiary::handle_treeview_row_expanded ) );
    m_TV_entries->signal_row_collapsed().connect(
            sigc::mem_fun( this, &PanelDiary::handle_treeview_row_expanded ) );

    m_Po_filter->signal_show().connect(
            sigc::mem_fun( AppWindow::p->m_filter_view, &FilterView::update ) );

    m_B_filter_revert->signal_clicked().connect(
            sigc::mem_fun( this, &PanelDiary::revert_filter ) );

    m_E_search->signal_changed().connect(
            sigc::mem_fun( this, &PanelDiary::handle_search_string_changed ) );

    g_signal_connect( m_E_search->gobj(), "search-changed",
                      ( GCallback ) handle_search_changed_tmp, this );
    m_E_search->signal_activate().connect(
            sigc::mem_fun( this, &PanelDiary::go_next_match ) );

    m_E_replace->signal_activate().connect(
            sigc::mem_fun( this, &PanelDiary::replace_match ) );
    m_B_replace->signal_clicked().connect(
            sigc::mem_fun( this, &PanelDiary::replace_match ) );
    m_B_replace_all->signal_clicked().connect(
            sigc::mem_fun( this, &PanelDiary::replace_all_matches ) );

    // ACTIONS
    Lifeograph::create_action(
            true, m_action_today, "Today", _( "Today" ),
            _( "Go to today (creates a new entry if there isn't any or when already at today)" ),
            Gtk::AccelKey( GDK_KEY_e, Gdk::CONTROL_MASK ),
            sigc::mem_fun( this, &PanelDiary::show_today ),
            button_today );

    Lifeograph::create_action(
            true, m_action_today, "AddTodo", _( "Add Todo" ),
            "",
            Gtk::AccelKey( GDK_KEY_d, Gdk::CONTROL_MASK ),
            sigc::mem_fun( this, &PanelDiary::add_todo_entry ) );

    Lifeograph::create_action(
            false, m_action_previous_list, "ListPrevious", _( "Previous" ),
            _( "Go to the previous entry in the list" ),
            Gtk::AccelKey( GDK_KEY_Page_Up, Gdk::CONTROL_MASK ),
            sigc::bind( sigc::mem_fun( m_TV_entries, &WidgetEntryList::go_up ), false ) );

    Lifeograph::create_action(
            false, m_action_next_list, "ListNext", _( "Next" ),
            _( "Go to the next entry in the list" ),
            Gtk::AccelKey( GDK_KEY_Page_Down, Gdk::CONTROL_MASK ),
            sigc::bind( sigc::mem_fun( m_TV_entries, &WidgetEntryList::go_down ), false ) );

    Lifeograph::create_action(
            false, m_action_jump2current, "Jump",
            "", "",     // no grachical interface yet!
            Gtk::AccelKey( GDK_KEY_j, Gdk::CONTROL_MASK ),
            sigc::mem_fun( this, &PanelDiary::present_current_row ) );

    Lifeograph::create_action(
            false, m_action_show_prev_session_elem, "PrevSessElem",
            "", "",     // no grachical interface yet!
            Gtk::AccelKey( GDK_KEY_l, Gdk::CONTROL_MASK ),
            sigc::mem_fun( this, &PanelDiary::show_prev_session_elem ) );

    m_B_logout->add_accelerator(
            "clicked",
            AppWindow::p->get_accel_group(),
            GDK_KEY_Escape,
            Gdk::CONTROL_MASK,
            Gtk::ACCEL_VISIBLE );

    // ACTIONS
    Lifeograph::create_action(
            false, m_action_focusfilter, "FocusFilter",
            "", "",     // no grachical interface yet!
            Gtk::AccelKey( GDK_KEY_f, Gdk::CONTROL_MASK ),
            sigc::mem_fun( m_E_search, &Gtk::Widget::grab_focus ) );

    Lifeograph::create_action(
            false, m_action_previous_match, "MatchPrevious",
// TRANSLATORS: label and tooltip of the go-to-previous-match-of-filtered-text action
            _( "Previous Match" ),
            _( "Go to previous match" ),
            Gtk::AccelKey( GDK_KEY_F3, Gdk::SHIFT_MASK ),
            sigc::mem_fun( this, &PanelDiary::go_prev_match ),
            m_B_match_prev );

    Lifeograph::create_action(
            false, m_action_next_match, "MatchNext",
// TRANSLATORS: label and tooltip of the go-to-next-match-of-filtered-text action
            _( "Next Match" ),
            _( "Go to next match" ),
            Gtk::AccelKey( GDK_KEY_F3, Gdk::ModifierType( 0 ) ),
            sigc::mem_fun( this, &PanelDiary::go_next_match ),
            m_B_match_next );
}

inline void
fill_elem_row( Gtk::TreeRow& row, DiaryElement* elem )
{
    row[ ListData::colrec->ptr ] = elem; // ptr must be first
    row[ ListData::colrec->info ] = elem->get_list_str();
    row[ ListData::colrec->icon ] = elem->get_icon();
}

void
PanelDiary::fill_treepaths( const Gtk::TreeModel::Children& children )
{
    for( Gtk::TreeModel::Children::const_iterator iter = children.begin();
            iter != children.end(); ++iter )
    {
        Gtk::TreeModel::Row row = *iter;
        DiaryElement* ptr = row[ ListData::colrec->ptr ];

        if( ptr )
            ptr->m_list_data->treepath = m_TV_entries->m_treestore->get_path( row );

        switch( ptr->get_type() )
        {
            case DiaryElement::ET_CHAPTER:
            case DiaryElement::ET_TOPIC:
            case DiaryElement::ET_GROUP:
                if( dynamic_cast< Chapter* >( ptr )->get_expanded() )
                    m_TV_entries->expand_to_path( ptr->m_list_data->treepath );
                break;
            default:
                break;
        }

        fill_treepaths( row.children() );
    }
}

void
PanelDiary::add_chapter_category_to_list( const CategoryChapters* chapters )
{
    Gtk::TreeRow    row_container;
    Gtk::TreeRow    row_entry;
    Chapter*        chapter = nullptr;
    Entry*          entry = nullptr;
    bool            flag_chapter_added;

    for( CategoryChapters::const_iterator iter_chapter = chapters->begin();
         iter_chapter != chapters->end();
         ++iter_chapter )
    {
        chapter = iter_chapter->second;

        // show empty folders only when text search is not active
        if( ! Diary::d->is_search_active() )
        {
            row_container = * m_TV_entries->m_treestore->append( m_row_diary.children() );
            fill_elem_row( row_container, chapter );
            flag_chapter_added = true;
        }
        else
            flag_chapter_added = false;

        for( Chapter::Set::iterator iter_entry = chapter->begin();
             iter_entry != chapter->end(); ++iter_entry )
        {
            entry = *iter_entry;

            if( entry->get_filtered_out() == false )
            {
                if( !flag_chapter_added )
                {
                    row_container = * m_TV_entries->m_treestore->append( m_row_diary.children() );
                    fill_elem_row( row_container, chapter );
                    flag_chapter_added = true;
                }
                m_entry_count++;
                row_entry = * m_TV_entries->m_treestore->append( row_container.children() );
                fill_elem_row( row_entry, entry );
            }
        }
    }
}

void
PanelDiary::update_entry_list()
{
    PRINT_DEBUG( "update_entry_list()" );
    Lifeograph::s_internaloperation++;
    m_flag_calendar_updated = false;

    AppWindow::p->m_entry_view->sync();

    // REMOVE MODEL AND DISBLE SORTING FOR SPEED-UP
    m_TV_entries->unset_model();
    m_TV_entries->m_treestore->set_sort_column(
            Gtk::TreeSortable::DEFAULT_UNSORTED_COLUMN_ID, Gtk::SORT_ASCENDING );

    m_TV_entries->m_treestore->clear();
    m_entry_count = 0;

    // DIARY ITEM
    m_row_diary = * m_TV_entries->m_treestore->append();
    fill_elem_row( m_row_diary, Diary::d );

    // CHAPTERS
    if( Diary::d->get_sorting_criteria() == SC_DATE )
    {
        add_chapter_category_to_list( Diary::d->get_topics() );
        add_chapter_category_to_list( Diary::d->get_groups() );
        add_chapter_category_to_list( Diary::d->get_current_chapter_ctg() );

        // ENTRIES NOT PART OF A CHAPTER (ORPHANED)
        for( EntrySetIter iter = Diary::d->m_orphans.begin();
             iter != Diary::d->m_orphans.end(); ++iter )
        {
            Entry* entry = *iter;
            if( entry->get_filtered_out() == false )
            {
                m_entry_count++;
                Gtk::TreeRow row = * m_TV_entries->m_treestore->append( m_row_diary.children() );
                fill_elem_row( row, entry );
            }
        }
    }
    else
    {
        for( EntryIter iter = Diary::d->m_entries.begin();
             iter != Diary::d->m_entries.end(); ++iter )
        {
            Entry* entry( iter->second );
            if( entry->get_filtered_out() == false )
            {
                m_entry_count++;
                Gtk::TreeRow row = * m_TV_entries->m_treestore->append( m_row_diary.children() );
                fill_elem_row( row, entry );
            }

        }
    }

    // RESTORE MODEL AND SORTING (FOR SPEED-UP)
    m_TV_entries->m_treestore->set_sort_column(
            Gtk::TreeSortable::DEFAULT_SORT_COLUMN_ID, Gtk::SORT_ASCENDING );
    m_TV_entries->set_model( m_TV_entries->m_treestore );

    // set treepath values of listed elements
    fill_treepaths( m_TV_entries->m_treestore->children() );

    // always expand diary
    m_TV_entries->expand_element( Diary::d );

    // UPDATE CURRENT ELEM IF LAST ONE IS NOT IN THE LIST ANYMORE
    DiaryElement* elem = AppWindow::p->panel_main->get_cur_elem();
    if( elem != nullptr )
    {
        if( elem->get_filtered_out() )
        {
            elem = Diary::d->get_entry_first();
            if( elem == nullptr ) // if there is no entry
                elem = Diary::d;
            elem->show();
        }
        else if( elem->get_type() >= DiaryElement::ET_DIARY )
            m_TV_entries->present_element( elem );
    }
    else // the case at the start-up
    {
        elem = Diary::d->get_startup_elem();
        if( elem->get_filtered_out() ) // if startup item is trashed
            elem = Diary::d->get_entry_first();
        if( elem == nullptr ) // if there is no unfiltered entry
            elem = Diary::d;
        elem->show();
    }

    Diary::d->get_filter()->set_status_applied();

    Lifeograph::s_internaloperation--;
}

void
PanelDiary::update_entry_list_style() // TODO: is this function really needed?
{
    Lifeograph::s_internaloperation++;

    update_entry_list();

    if( AppWindow::p->panel_main->get_cur_elem_type() != DiaryElement::ET_ENTRY )
        Diary::d->show();

    m_TV_entries->columns_autosize();

    Lifeograph::s_internaloperation--;
}

void
PanelDiary::update_sorting()
{
    SortingCriteria sc{ SC_CHANGE };

    if( m_RB_sort_by_date->get_active() )
        sc = SC_DATE;
    else if( m_RB_sort_by_size->get_active() )
        sc = SC_SIZE_C;

    SortingCriteria sd{ m_RB_sort_ascending->get_active() ? SD_ASCENDING : SD_DESCENDING };

    if( sc == Diary::d->get_sorting_criteria() && sd == Diary::d->get_sorting_dir() )
        return;

    Diary::d->set_sorting_criteria( sc );
    Diary::d->set_sorting_dir( sd );

    m_TV_entries->handle_sorting_criteria_changed( sc, sd );

    update_entry_list();
};

void
PanelDiary::update_calendar()
{
    guint year, month, day;

    m_calendar->get_date( year, month, day );
    month++;	// fix month
    Entry* entry;
    m_calendar->clear_marks();

    for( EntryIterConst itr_entry = Diary::d->get_entries().begin();
         itr_entry != Diary::d->get_entries().end();
         ++itr_entry )
    {
        entry = itr_entry->second;
        if( entry->get_filtered_out() || entry->get_date().is_ordinal() )
            continue;

        if( entry->get_date().get_year() == year )
        {
            if( entry->get_date().get_month() == month )
            {
                m_calendar->mark_day( entry->get_date().get_day() );
            }
            else
            if( entry->get_date().get_month() < month )
                break;
        }
        else
        if( entry->get_date().get_year() < year )
            break;
    }

    m_flag_calendar_updated = true;
}

void
PanelDiary::handle_login()
{
    m_MB_logout->set_visible( Diary::d->is_encrypted() || ! Diary::d->is_read_only() );

    switch( Diary::d->get_sorting_criteria() )
    {
        default: // future-proofing
        case SC_DATE: // no break
            m_RB_sort_by_date->set_active();
            break;
        case SC_SIZE_C:
            m_RB_sort_by_size->set_active();
            break;
        case SC_CHANGE:
            m_RB_sort_by_change->set_active();
            break;
    }
    if( Diary::d->get_sorting_dir() == SD_DESCENDING )
        m_RB_sort_descending->set_active();
    else
        m_RB_sort_ascending->set_active();

    // not called automatically because of m_internaloperation:
    m_TV_entries->reset_sorting_criteria();

    if( Diary::d->is_read_only() )
    {
        m_TV_entries->unset_rows_drag_source();
        m_TV_entries->unset_rows_drag_dest();
    }
    else
    {
        m_TV_entries->enable_model_drag_source( Lifeograph::p->drag_targets_entry );
        m_TV_entries->enable_model_drag_dest( Lifeograph::p->drag_targets_entry,
                                              Gdk::ACTION_MOVE );
    }

    update_entry_list();
    if( !m_flag_calendar_updated )
        update_calendar();  // if calendar is not updated in update_entry_list()

    m_MB_add->set_visible( ! Diary::d->is_read_only() );
    m_Bx_new_elem->set_visible( true );
    m_G_elem_header->set_visible( true );
    m_Bx_logout->set_visible( true );
    m_B_tools->set_visible( true );
    m_Bx_search->set_visible( true );
}

void
PanelDiary::handle_logout()
{
    Lifeograph::s_internaloperation++;

    m_Po_replace->hide();

    m_Bx_new_elem->set_visible( false );
    m_G_elem_header->set_visible( false );
    m_Bx_logout->set_visible( false );
    m_B_tools->set_visible( false );
    m_Bx_search->set_visible( false );
    m_B_filter_revert->set_visible( false );

    m_TV_entries->clear();

    m_E_search->set_text( "" );
    m_E_replace->set_text( "" );

    Lifeograph::s_internaloperation--;
}

void
PanelDiary::show( const DiaryElement* element )
{
    if( element->get_type() >= DiaryElement::ET_DIARY ) // if a list element
        m_TV_entries->present_element( element );
    else // this may cause conflicts with multiple selections but very unlikely
        m_TV_entries->get_selection()->unselect_all();

    m_date_cur = element->get_date();

    if( element->get_type() > DiaryElement::ET_DIARY )
    {
        if( m_date_cur.is_ordinal() )
        {
            if( element->get_type() == DiaryElement::ET_ENTRY )
                m_date_cur = Date::make_date_from_ctime(
                            dynamic_cast< const Entry* >( element )->get_date_created() );
            else
                m_date_cur.set_now();
        }
    }
    else
    {
        m_date_cur.set_now();
    }

    select_date_in_calendar( m_date_cur );

    if( Diary::d->get_current_chapter_ctg()->get_chapter( m_date_cur.m_date ) == nullptr )
    {
        m_A_add_chapter->set_enabled( true );
        m_L_add_chapter->set_text( Ustring::compose( _( "Dated Chapter on %1" ),
                                                     m_date_cur.format_string() ) );
    }
    else
    {
        m_A_add_chapter->set_enabled( false );
        m_L_add_chapter->set_text( _( "To Add a Chapter Please Select an Empty Date" ) );
    }
    m_L_add_entry->set_text( Ustring::compose( _( "Entry on %1" ), m_date_cur.format_string() ) );
}

void
PanelDiary::show_today()
{
    Entry* entry_today = Diary::d->get_entry_today();
    bool flag_add = false;

    if( entry_today == nullptr )   // add new entry if no entry exists on selected date
    {
        if( Diary::d->is_read_only() )
            return;
        else
            flag_add = true;
    }
    else                        // or current entry is already at today
    if( entry_today->get_date().get_pure() ==
            AppWindow::p->panel_main->get_cur_elem()->get_date().get_pure() )
        flag_add = true;

    if( flag_add )
    {
        entry_today = Diary::d->add_today();
        update_entry_list();
    }

    entry_today->show();
}

void
PanelDiary::add_todo_entry()
{
    if( Lifeograph::p->loginstatus != Lifeograph::LOGGED_IN || Diary::d->is_read_only() )
        return;

    Entry* entry{ Diary::d->add_today() };
    if( entry )
    {
        entry->set_todo_status( ES::TODO );
        update_entry_list();
        entry->show();
    }
}

void
PanelDiary::show_prev_session_elem()
{
    DiaryElement* elem( Diary::d->get_prev_session_elem() );
    if( elem != nullptr )
        elem->show();
    else
        print_info( "Previous sesion element cannot be found!" );
}

void
PanelDiary::present_current_row()
{
    Gtk::TreeIter iter = m_TV_entries->get_selection()->get_selected();
    if( iter == m_TV_entries->m_treestore->children().end() )
        return;
    Gtk::TreePath path = m_TV_entries->m_treestore->get_path( iter );

    m_TV_entries->scroll_to_row( path );
}

void
PanelDiary::handle_calendar_dayselected()
{
    if( Lifeograph::s_internaloperation ) return;

    guint year, month, day;
    m_calendar->get_date( year, month, day );
    Date date( year, month + 1, day );

    Entry* entry;
    if( date.m_date == ( AppWindow::p->panel_main->get_cur_elem()->get_date().get_pure() ) )
        entry = Diary::d->get_entry_next_in_day(
                AppWindow::p->panel_main->get_cur_elem()->get_date() );
    else
        entry = Diary::d->get_entry( date.m_date + 1 ); // 1 increment to fix order

    if( entry )
    {
        entry->show();
        return;
    }
    else if( day == 0 )
        date.set_now();

    m_date_cur = date;

    if( Diary::d->get_current_chapter_ctg()->get_chapter( date.m_date ) == nullptr )
    {
        m_A_add_chapter->set_enabled( true );
        m_L_add_chapter->set_text( Ustring::compose( _( "Dated Chapter on %1" ),
                                                     date.format_string() ) );
    }
    else
    {
        m_A_add_chapter->set_enabled( false );
        m_L_add_chapter->set_text( _( "To Add a Chapter Please Select an Empty Date" ) );
    }
    m_L_add_entry->set_text( Ustring::compose( _( "Entry on %1" ), date.format_string() ) );
}

void
PanelDiary::handle_calendar_monthchanged()
{
    if( Lifeograph::s_internaloperation ) return;

    update_calendar();
}

void
PanelDiary::handle_add_entry()
{
    if( Diary::d->is_read_only() )
        return;

    Entry* entry{ Diary::d->create_entry( m_date_cur.m_date ) };

    update_entry_list();
    update_calendar();

    entry->show();

    // this is the only way i could find to give focus to textview
    //FIXME
//  Glib::signal_timeout().connect_once(
//          sigc::mem_fun( m_textviewdiary, &Gtk::Widget::grab_focus ), 200 );
}

void
PanelDiary::handle_add_group()
{
    if( Diary::d->is_read_only() )
        return;

    Chapter* c( Diary::d->get_groups()->create_chapter_ordinal( _( STRING::NEW_CHAPTER_NAME ) ) );
    Diary::d->update_entries_in_chapters();
    update_entry_list();
    c->show();
    AppWindow::p->panel_main->start_title_edit();
}

void
PanelDiary::handle_add_topic()
{
    if( Diary::d->is_read_only() )
        return;

    Chapter* c( Diary::d->get_topics()->create_chapter_ordinal( _( STRING::NEW_CHAPTER_NAME ) ) );
    Diary::d->update_entries_in_chapters();
    update_entry_list();
    c->show();
    AppWindow::p->panel_main->start_title_edit();
}

void
PanelDiary::handle_add_chapter()
{
    if( Diary::d->is_read_only() )
        return;

    if( Diary::d->get_current_chapter_ctg()->get_chapter( m_date_cur.m_date ) == nullptr )
    {
        Chapter* c( Diary::d->get_current_chapter_ctg()->create_chapter(
                _( "Untitled chapter" ), m_date_cur.m_date ) );

        Diary::d->update_entries_in_chapters();
        update_entry_list();
        c->show();
        AppWindow::p->panel_main->start_title_edit();
    }
}

void
PanelDiary::select_date_in_calendar( const Date& date )
{
    ++Lifeograph::s_internaloperation;

    // CALENDAR
    // the current day has to be deselected first to avoid gtk's complaints
    m_calendar->select_day( 0 );
    if( ! date.is_ordinal() )
    {
        guint year, month, day;
        m_calendar->get_date( year, month, day );

        if( year != date.get_year() || month != date.get_month() - 1 )
        {
            m_calendar->select_month( date.get_month() - 1, date.get_year() );
            update_calendar();
        }
        //if( date.get_day() ) should not be necessary
        m_calendar->select_day( date.get_day() );
    }

    --Lifeograph::s_internaloperation;
}

void
PanelDiary::go_prev_match()
{
    if( m_entry_count < 1 )
        return;

    // TODO: move to Entry
    if( AppWindow::p->panel_main->get_cur_elem_type() == DiaryElement::ET_ENTRY )
        if( AppWindow::p->m_entry_view->get_buffer()->select_searchstr_previous() )
            return;

    int i = 0;
    do
    {
        m_TV_entries->go_up( true );
        // put cursor to the end:
        AppWindow::p->m_entry_view->get_buffer()->select_range(
                AppWindow::p->m_entry_view->get_buffer()->end(),
                AppWindow::p->m_entry_view->get_buffer()->end() );
        i++;
    }
    while( ! AppWindow::p->m_entry_view->get_buffer()->select_searchstr_previous() &&
           i < m_entry_count );
}

void
PanelDiary::go_next_match()
{
    if( m_entry_count < 1 || m_E_search->get_text().empty() )
        return;

    // TODO: move to Entry
    if( AppWindow::p->panel_main->get_cur_elem_type() == DiaryElement::ET_ENTRY )
        if( AppWindow::p->m_entry_view->get_buffer()->select_searchstr_next() )
            return;

    int i = 0;
    do
    {
        m_TV_entries->go_down( true );
        i++;
    }
    while( ! AppWindow::p->m_entry_view->get_buffer()->select_searchstr_next() &&
           i < m_entry_count );
}

void
PanelDiary::handle_elem_changed( const DiaryElement* element )
{
    if( Lifeograph::s_internaloperation ) return;

    Gtk::TreeRow row = * get_row( element->m_list_data->treepath );
    row[ ListData::colrec->icon ] = element->get_icon();
    row[ ListData::colrec->info ] = element->get_list_str();
}
// simpler version of above function
void
PanelDiary::handle_elem_title_changed( const DiaryElement* element )
{
    if( Lifeograph::s_internaloperation ) return;

    Gtk::TreeRow row = * get_row( element->m_list_data->treepath );
    row[ ListData::colrec->info ] = element->get_list_str();
}

Gtk::TreeRow
PanelDiary::get_row( const Gtk::TreePath& path )
{
    return( * m_TV_entries->m_treestore->get_iter( path ) );
    // TODO: check validity
}

void
PanelDiary::handle_treeview_row_expanded( const Gtk::TreeIter& iter,
                                          const Gtk::TreePath& path )
{
    //if( Lifeograph::m_internaloperation )
        //return;

    DiaryElement* element = ( * iter )[ ListData::colrec->ptr ];
    switch( element->get_type() )
    {
        case DiaryElement::ET_CHAPTER:
        case DiaryElement::ET_TOPIC:
        case DiaryElement::ET_GROUP:
        {
            Chapter* chapter = dynamic_cast< Chapter* >( element );
            chapter->set_expanded( m_TV_entries->row_expanded( path ) );
        }
        break;
    default:
        break;
    }
}

// FILTER, SEARCH & REPLACE
void
PanelDiary::revert_filter()
{
    Diary::d->get_filter()->set( Diary::d->get_filter_default() );
    Diary::d->get_filter()->set_status_outstanding();
    update_calendar();
    update_entry_list();
    AppWindow::p->panel_extra->set_filtered_tag( Diary::d->get_filter_default()->get_tag() );
    m_B_filter_revert->set_visible( false );
}

void
PanelDiary::handle_filter_changed()
{
    m_B_filter_revert->set_visible(
            !( *Diary::d->get_filter() == *Diary::d->get_filter_default() ) );

    update_calendar();
    update_entry_list();
}

void
PanelDiary::handle_search_string_changed()
{
    if( Lifeograph::s_internaloperation ) return;
    // current entry must be closed here or else it loses...
    // ...changes since it was selected:
    //AppWindow::p->m_entry_view->sync(); update_entry_list() does the syncing

    const std::string filterstring( m_E_search->get_text().lowercase() );

    Diary::d->set_search_text( filterstring );
    update_entry_list();    // syncs current entry

    if( filterstring.size() > 0 )
    {
        if( m_entry_count > 0 )
        {
            AppWindow::p->m_entry_view->get_buffer()->set_search_str( filterstring );
            if( AppWindow::p->panel_main->get_cur_elem_type() == DiaryElement::ET_ENTRY )
                AppWindow::p->m_entry_view->get_buffer()->select_searchstr_next();
        }

        m_E_search->set_tooltip_markup(
                Glib::ustring::compose(
                        _( "Found in <b>%1</b> of <b>%2</b> entrie(s)" ),
                        m_entry_count, Diary::d->get_size() ) );
    }
    else
    {
        AppWindow::p->m_entry_view->get_buffer()->set_search_str( "" );
        m_E_search->set_tooltip_text( _( "Search text within entries" ) );
    }

    update_calendar();

    if( m_entry_count < 1 || filterstring.size() < 1 )
        m_Po_replace->hide();
    else
    {
        m_Po_replace->show();
        bool show_replace( ! Diary::d->is_read_only() );
        m_E_replace->set_visible( show_replace );
        m_B_replace->set_visible( show_replace );
        m_B_replace_all->set_visible( show_replace );
    }
}

void
PanelDiary::replace_match()
{
    if( ! AppWindow::p->m_entry_view->get_buffer()->get_has_selection() )
        go_next_match();
    if( AppWindow::p->m_entry_view->get_buffer()->get_has_selection() )
    {
        AppWindow::p->m_entry_view->get_buffer()->erase_selection();
        AppWindow::p->m_entry_view->get_buffer()->insert_at_cursor( m_E_replace->get_text() );
        go_next_match();
    }
}

void
PanelDiary::replace_all_matches()
{
    AppWindow::p->m_entry_view->sync();

    Diary::d->replace_text( m_E_replace->get_text() );
    // update current entry:
    if( AppWindow::p->panel_main->get_cur_elem_type() == DiaryElement::ET_ENTRY )
    {
        Entry* entry = dynamic_cast< Entry* >( AppWindow::p->panel_main->get_cur_elem() );
        AppWindow::p->m_entry_view->get_buffer()->set_richtext( entry );
    }
}
