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

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

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


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

#include <iomanip>

#include "lifeograph.hpp"
#include "app_window.hpp"
#include "ui_diary.hpp"
#include "ui_entry.hpp"
#include "widgets/widget_textview.hpp"


using namespace LIFEO;


UIDiary::UIDiary()
{
    Gtk::ModelButton*   B_export;
    Gtk::Popover*       Po_diary;

    try
    {
        auto            builder{ Lifeograph::get_builder() };

        builder->get_widget( "Bx_entry_header", m_Bx_entry_header );

        builder->get_widget( "MB_diary", m_MB_diary );

        builder->get_widget( "B_enable_edit", m_B_enable_edit );
        builder->get_widget( "B_today", m_B_today );

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

        builder->get_widget( "MB_tools", m_MB_tools );
        builder->get_widget( "Bx_entries_sort", m_Bx_entries_sort_by );
        builder->get_widget( "RB_sort_date", m_RB_sort_by_date );
        builder->get_widget( "RB_sort_name", m_RB_sort_by_name );
        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_asc", m_RB_sort_asc_tem );
        builder->get_widget( "RB_sort_desc", m_RB_sort_desc_tem );
        builder->get_widget( "Bx_sort_temporal", m_Bx_sort_tem );
        builder->get_widget( "RB_sort_ord_asc", m_RB_sort_asc );
        builder->get_widget( "RB_sort_ord_desc", m_RB_sort_desc );

        builder->get_widget( "Bx_search", m_Bx_search );

        builder->get_widget( "Po_diary", Po_diary );
        builder->get_widget( "B_diary_open_folder", m_B_open_folder );
        builder->get_widget( "L_diary_size", m_L_size );
        builder->get_widget( "L_diary_chapters", m_L_chapters );
        builder->get_widget( "Bx_diary_options", m_Bx_options );

        builder->get_widget( "CB_diary_spellcheck", m_CB_spellcheck );

        builder->get_widget_derived( "E_diary_completion_tag", m_WEP_completion_tag );

        builder->get_widget( "CB_diary_startup_type", m_CB_startup_type );
        builder->get_widget( "L_diary_startup_elem", m_L_startup_elem );
        builder->get_widget_derived( "E_diary_startup_entry", m_W_startup_entry );

        builder->get_widget( "B_diary_encryption", m_MoB_encryption );
        builder->get_widget( "B_diary_empty_trash", m_MoB_empty_trash );
        builder->get_widget( "B_diary_sync", m_MoB_sync );
        builder->get_widget( "B_diary_export", B_export );

        builder->get_widget( "Po_entrymini", m_Po_entry );
        builder->get_widget( "Bx_entrymini_edit", m_Bx_entry_edit );
        builder->get_widget( "Bx_entrymini_add_header", m_Bx_entry_add_header );
        builder->get_widget( "Bx_entrymini_add_relative", m_Bx_entry_add_relative );
        builder->get_widget( "Bx_entrymini_add_top", m_Bx_entry_add_top );
        builder->get_widget( "Bx_entrymini_hide", m_Bx_entry_hide );
        builder->get_widget( "RB_entrymini_auto", m_RB_entry_auto );
        builder->get_widget( "RB_entrymini_todo", m_RB_entry_todo );
        builder->get_widget( "RB_entrymini_progressed", m_RB_entry_progressed );
        builder->get_widget( "RB_entrymini_done", m_RB_entry_done );
        builder->get_widget( "RB_entrymini_canceled", m_RB_entry_canceled );
        builder->get_widget( "TB_entrymini_trashed", m_TB_entry_trashed );
        builder->get_widget( "TB_entrymini_favorite", m_TB_entry_favorite );
        builder->get_widget( "B_entrymini_add_child", m_B_entry_add_child );
        builder->get_widget( "B_entrymini_add_sibling", m_B_entry_add_sibling );
        builder->get_widget( "MoB_entrymini_filter_referencers", m_MoB_entry_filter_referencers );

        builder->get_widget_derived( "TV_main", m_TV_entries );
    }
    catch( ... )
    {
        throw LIFEO::Error( "Failed to create the diary panel" );
    }

    m_WEP_completion_tag->set_offer_new( false );

    // HEADERBAR SIGNALS
    m_RB_sort_by_date->signal_toggled().connect(  [ this ](){ update_sorting(); } );
    m_RB_sort_by_name->signal_toggled().connect(  [ this ](){ update_sorting(); } );
    m_RB_sort_by_size->signal_toggled().connect(  [ this ](){ update_sorting(); } );
    // the 4th radiobutton is not necessary as toggle signal takes care of that
    m_RB_sort_asc->signal_toggled().connect(      [ this ](){ update_sorting(); } );
    m_RB_sort_asc_tem->signal_toggled().connect(  [ this ](){ update_sorting(); } );

    Po_diary->signal_show().connect(              [ this ](){ refresh_Po_diary(); } );

    m_CB_spellcheck->signal_changed().connect(    [ this ](){ handle_language_changed(); } );

    m_WEP_completion_tag->signal_updated().connect(
            [ this ]( Entry* e )
            {
                Diary::d->set_completion_tag( e ? e->get_id() : DEID_UNSET );
            } );

    m_CB_startup_type->signal_changed().connect(  [ this ](){ handle_startup_type_changed(); } );

    m_W_startup_entry->signal_updated().connect(
            [ this ]( Entry* e ){ Diary::d->set_startup_entry( e ); } );

    m_B_open_folder->signal_clicked().connect(    [ this ](){ open_diary_folder(); } );

    m_MoB_encryption->signal_clicked().connect(   [ this ](){ start_dialog_password(); } );
    m_MoB_empty_trash->signal_clicked().connect(  [ this ](){ empty_trash(); } );
    m_MoB_sync->signal_clicked().connect(         [ this ](){ start_dialog_sync(); } );
    B_export->signal_clicked().connect(           [ this ](){ start_dialog_export(); } );

    // ENTRY POPOVER SIGNALS
    m_RB_entry_auto->signal_toggled().connect(
            [ this ]()
            {
                m_Po_entry->hide();
                AppWindow::p->UI_entry->set_entries_todo( get_selected_entries(), ES::NOT_TODO );
            } );
    m_RB_entry_todo->signal_toggled().connect(
            [ this ]()
            {
                m_Po_entry->hide();
                AppWindow::p->UI_entry->set_entries_todo( get_selected_entries(), ES::TODO );
            } );
    m_RB_entry_progressed->signal_toggled().connect(
            [ this ]()
            {
                m_Po_entry->hide();
                AppWindow::p->UI_entry->set_entries_todo( get_selected_entries(), ES::PROGRESSED );
            } );
    m_RB_entry_done->signal_toggled().connect(
            [ this ]()
            {
                m_Po_entry->hide();
                AppWindow::p->UI_entry->set_entries_todo( get_selected_entries(), ES::DONE );
            } );
    m_RB_entry_canceled->signal_toggled().connect(
            [ this ]()
            {
                m_Po_entry->hide();
                AppWindow::p->UI_entry->set_entries_todo( get_selected_entries(), ES::CANCELED );
            } );
    m_TB_entry_favorite->signal_toggled().connect(
            [ this ]()
            {
                m_Po_entry->hide();
                AppWindow::p->UI_entry->set_entries_favorite( get_selected_entries(),
                                                              m_TB_entry_favorite->get_active() );
            } );
    m_TB_entry_trashed->signal_toggled().connect(
            [ this ]()
            {
                m_Po_entry->hide();
                AppWindow::p->UI_entry->trash_entries( get_selected_entries(),
                                                       m_TB_entry_trashed->get_active() );
            } );
    m_MoB_entry_filter_referencers->signal_clicked().connect(
            [ this ]()
            {
                hide_entries_not_refing();
            } );
    m_B_entry_add_child->signal_clicked().connect(
            [ this ]()
            {
                add_entry( Diary::d->get_available_order_sub( m_p2entry_cur->get_date_t() ), "" );
            } );
    m_B_entry_add_sibling->signal_clicked().connect(
            [ this ]()
            {
                add_entry( Diary::d->get_available_order_next( m_p2entry_cur->get_date_t() ), "" );
            } );

    // ACTIONS
    Lifeograph::p->add_action( "add_entry_on_date",
            [ this ](){ add_entry( AppWindow::p->UI_extra->get_selected_date(), "" ); } );

    Lifeograph::p->add_action( "add_entry_todo",
            [ this ](){ add_entry( Diary::d->get_available_order_1st( true ), "", true ); } );

    Lifeograph::p->add_action( "add_entry_free",
            [ this ](){ add_entry( Diary::d->get_available_order_1st( true ), "" ); } );

    Lifeograph::p->add_action( "add_entry_numbered",
            [ this ](){ add_entry( Diary::d->get_available_order_1st( false ), "" ); } );

    Lifeograph::p->add_action( "add_chapter",
            [ this ](){ add_chapter( AppWindow::p->UI_extra->get_selected_date() ); } );

    Lifeograph::p->add_action( "hide_entries_before", [ this ](){ hide_entries_before(); } );
    m_A_hide_entry_cur =
    Lifeograph::p->add_action( "hide_entry_cur", [ this ](){ hide_entry_cur(); } );
    Lifeograph::p->add_action( "hide_entries_after", [ this ](){ hide_entries_after(); } );

    Lifeograph::p->add_action( "go_to_today", [ this ](){ show_today(); } );
    Lifeograph::p->add_action( "go_list_prev", [ this ](){ m_TV_entries->go_up( false ); } );
    Lifeograph::p->add_action( "go_list_next", [ this ](){ m_TV_entries->go_down( false ); } );
    Lifeograph::p->add_action( "jump_to_current_entry",
            [ this ](){ show_in_list( AppWindow::p->UI_entry->get_cur_entry() ); } );
    Lifeograph::p->add_action( "go_to_prev_sess_entry", [ this ](){ show_prev_session_elem(); } );
    Lifeograph::p->add_action( "logout", [ this ](){ AppWindow::p->logout( true ); } );

    Lifeograph::p->set_accel_for_action( "app.go_to_today",                 "<Ctrl>T" );
    Lifeograph::p->set_accel_for_action( "app.add_entry_numbered",          "<Ctrl>N" );
    Lifeograph::p->set_accel_for_action( "app.add_entry_todo",              "<Ctrl>D" );
    Lifeograph::p->set_accel_for_action( "app.go_list_prev",                "<Ctrl>Page_Up" );
    Lifeograph::p->set_accel_for_action( "app.go_list_next",                "<Ctrl>Page_Down" );
    Lifeograph::p->set_accel_for_action( "app.jump_to_current_entry",       "<Ctrl>J" );
    Lifeograph::p->set_accel_for_action( "app.go_to_prev_sess_entry",       "<Ctrl>L" );
    Lifeograph::p->set_accel_for_action( "app.logout",                      "<Ctrl>Escape" );
}

inline void
fill_elem_row( Gtk::TreeRow& row, Entry* entry, bool flag_show_child_count )
{
    row[ ListData::colrec->ptr ] = entry; // ptr must be first
    row[ ListData::colrec->info ] = entry->get_list_str( flag_show_child_count );
    row[ ListData::colrec->icon ] = entry->get_icon();
}
inline void
fill_elem_row( Gtk::TreeIter& iter, Entry* entry, bool flag_show_child_count )
{
    Gtk::TreeRow row{ * iter };
    fill_elem_row( row, entry, flag_show_child_count );
}

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

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

            if( ptr->get_expanded() && not( children.empty() ) )
                m_TV_entries->expand_to_path( ptr->m_list_data->treepath );
        }

        fill_treepaths( children );
    }
}

void
UIDiary::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();
            fill_elem_row( row_container, chapter, true );
            flag_chapter_added = true;
        }
        else
            flag_chapter_added = false;

        for( auto 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();
                    fill_elem_row( row_container, chapter, true );
                    flag_chapter_added = true;
                }
                m_entry_count++;
                row_entry = * m_TV_entries->m_treestore->append( row_container.children() );
                fill_elem_row( row_entry, entry, true );
            }
        }
    }
}

Gtk::TreeIter
UIDiary::add_entry_to_list( Entry* entry, std::map< date_t, Gtk::TreeRow >* map_rows )
{
    Gtk::TreeIter iter_elem;
    const date_t  parent_date{ entry->get_date().get_parent() };

    if( parent_date == Date::DATE_MAX ) // 1st level
        iter_elem = m_TV_entries->m_treestore->append();
    else
    {
        auto parent_entry{ entry->get_parent() };
        if( parent_entry != nullptr ) // if parent exists in the diary
        {
            auto&& result{ map_rows->find( parent_date ) };
            Gtk::TreeIter iter_parent =
                    ( result == map_rows->end() ) ? // parent is not in the list
                            add_entry_to_list( parent_entry, map_rows ) : // recursion
                            result->second;
            iter_elem = * m_TV_entries->m_treestore->append( iter_parent->children() );
        }
        else
            iter_elem = m_TV_entries->m_treestore->append();
    }

    fill_elem_row( iter_elem, entry, true );
    map_rows->emplace( entry->get_date_t(), *iter_elem );

    return iter_elem;
}

void
UIDiary::update_entry_list()
{
    PRINT_DEBUG( "update_entry_list()" );
    Lifeograph::START_INTERNAL_OPERATIONS();
    m_flag_calendar_updated = false;

    // store the current scroll position
    Gtk::TreePath path_bgn, path_end;
    m_TV_entries->get_visible_range( path_bgn, path_end );

    // 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->clear();
    m_entry_count = 0;

    // ENTRIES
    if( ( Diary::d->get_sorting_criteria() & SoCr_FILTER_CRTR ) == SoCr_DATE )
    {
        Gtk::TreeRow                     row_elem;
        std::map< date_t, Gtk::TreeRow > map_rows;
        const date_t                     first_chapter_date{
                Diary::d->get_chapter_ctg_cur()->get_date_t() };

        add_chapter_category_to_list( Diary::d->get_chapter_ctg_cur() );

        for( EntryIterReverse iter = Diary::d->m_entries.rbegin();
             iter != Diary::d->m_entries.rend(); ++iter )
        {
            Entry* entry{ iter->second };
            if( entry->get_filtered_out() )
                entry->m_list_data->treepath.clear();
            else if( entry->get_date().is_ordinal() )
            {
                m_entry_count++;
                add_entry_to_list( entry, &map_rows );
            }
            else if( entry->get_date_t() < first_chapter_date ) // orphans
            {
                m_entry_count++;
                Gtk::TreeRow row = * m_TV_entries->m_treestore->append();
                fill_elem_row( row, entry, true );
            }
            // other entries were taken care of in add_chapter_category_to_list()
        }
    }
    else // other sorting criterion than by date
    {
        for( EntryIter iter = Diary::d->m_entries.begin();
             iter != Diary::d->m_entries.end(); ++iter )
        {
            Entry* entry( iter->second );
            if( entry->get_filtered_out() )
                entry->m_list_data->treepath.clear();
            else
            {
                m_entry_count++;
                Gtk::TreeRow row = * m_TV_entries->m_treestore->append();
                fill_elem_row( row, entry, false );
            }
        }
    }

    // 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() );

    // restore scroll position
    if( path_bgn.empty() == false )
        m_TV_entries->scroll_to_row( path_bgn, 0.0 );

    // UPDATE CURRENT ELEM IF LAST ONE IS NOT IN THE LIST ANYMORE
    Entry* entry{ AppWindow::p->UI_entry->get_cur_entry() };
    if( entry && not( entry->m_list_data->treepath.empty() ) )
        m_TV_entries->get_selection()->select( entry->m_list_data->treepath );

    Lifeograph::FINISH_INTERNAL_OPERATIONS();
}

//void // is this function really needed?
//UIDiary::update_entry_list_style()
//{
//    Lifeograph::START_INTERNAL_OPERATIONS();
//
//    update_entry_list();
//    m_TV_entries->columns_autosize();
//
//    Lifeograph::FINISH_INTERNAL_OPERATIONS();
//}

void
UIDiary::update_sorting()
{
    if( Lifeograph::is_internal_operations_ongoing() )
        return;

    SortCriterion sc{ SoCr_CHANGE };

    if     ( m_RB_sort_by_date->get_active() )
        sc = SoCr_DATE;
    else if( m_RB_sort_by_name->get_active() )
        sc = SoCr_NAME;
    else if( m_RB_sort_by_size->get_active() )
        sc = SoCr_SIZE_C;

    sc |= ( m_RB_sort_asc->get_active() ? SoCr_ASCENDING : SoCr_DESCENDING );
    sc |= ( m_RB_sort_asc_tem->get_active() ? SoCr_ASCENDING_T : SoCr_DESCENDING_T );

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

    Diary::d->set_sorting_criteria( sc );

    m_TV_entries->set_sorting_criteria( sc );

    m_Bx_sort_tem->set_visible( ( sc & SoCr_FILTER_CRTR ) == SoCr_DATE );

    update_entry_list();
};

void
UIDiary::handle_login()
{
    const auto sc_diary{ Diary::d->get_sorting_criteria() };

    m_WEP_completion_tag->set_diary( Diary::d );
    m_W_startup_entry->set_diary( Diary::d );
    m_MB_diary->set_visible( true );
    m_MB_logout->set_visible( Diary::d->is_encrypted() );
    m_Bx_entries_sort_by->set_visible( true );


    switch( sc_diary & SoCr_FILTER_CRTR )
    {
        default: // future-proofing
        case SoCr_DATE: // no break
            m_RB_sort_by_date->set_active();
            m_Bx_sort_tem->set_visible( true );
            break;
        case SoCr_NAME:
            m_RB_sort_by_name->set_active();
            break;
        case SoCr_SIZE_C:
            m_RB_sort_by_size->set_active();
            break;
        case SoCr_CHANGE:
            m_RB_sort_by_change->set_active();
            break;
    }

    Lifeograph::START_INTERNAL_OPERATIONS();

    if( ( sc_diary & SoCr_FILTER_DIR ) == SoCr_ASCENDING )
        m_RB_sort_asc->set_active( true );
    else
        m_RB_sort_desc->set_active( true );
    if( ( sc_diary & SoCr_FILTER_DIR_T ) == SoCr_ASCENDING_T )
        m_RB_sort_asc_tem->set_active( true );
    else
        m_RB_sort_desc_tem->set_active( true );

    // not called automatically during internal operation:
    m_TV_entries->set_sorting_criteria( sc_diary & SoCr_FILTER_CRTR );

    //update_entry_list(); <- this is done by filtering in UI_extra

    m_B_today->set_visible( !Lifeograph::settings.rtflag_read_only );
    m_Bx_entry_header->set_visible( true );
    m_Bx_logout->set_visible( !Lifeograph::settings.rtflag_single_file );
    m_Bx_search->set_visible( true );
    m_B_enable_edit->set_visible( !Lifeograph::settings.rtflag_read_only );
    m_B_enable_edit->set_sensitive( Diary::d->can_enter_edit_mode() );

    // SPELL CHECKING
    m_CB_spellcheck->remove_all();
    m_CB_spellcheck->append( _( STRING::OFF ) );
    for( const std::string& lang : Lifeograph::s_lang_list )
    {
        m_CB_spellcheck->append( lang );
    }

    if( Diary::d->m_language.empty() )
        m_CB_spellcheck->set_active_text( _( STRING::OFF ) );
    else
    {
        if( Lifeograph::s_lang_list.find( Diary::d->m_language ) ==
            Lifeograph::s_lang_list.end() )
            m_CB_spellcheck->append( Diary::d->m_language );

        m_CB_spellcheck->set_active_text( Diary::d->m_language );
    }

    m_WEP_completion_tag->set_entry( Diary::d->get_completion_tag() );

    if( Diary::d->m_startup_entry_id <= HOME_LAST_ENTRY )
        m_CB_startup_type->set_active( Diary::d->m_startup_entry_id - 1 );
    else
        m_CB_startup_type->set_active( 2 );

    Lifeograph::FINISH_INTERNAL_OPERATIONS();

    // READ-ONLY RELATED STUFF
    m_MoB_sync->set_visible( false );
    m_Bx_options->set_visible( false );
    m_MoB_encryption->set_visible( false );

    // TOOLTIPS
    m_B_open_folder->set_tooltip_text( Glib::path_get_dirname( Diary::d->m_path ) );
    m_MB_diary->set_tooltip_text( Diary::d->m_path );
}

void
UIDiary::enable_editing()
{
    if( not( Diary::d->is_open() ) || Diary::d->is_in_edit_mode() )
        return;

    if( Diary::d->is_old() )
    {
        Gtk::MessageDialog* messagedialog
                = new Gtk::MessageDialog( *AppWindow::p,
                                          "",
                                          false,
                                          Gtk::MESSAGE_WARNING,
                                          Gtk::BUTTONS_CANCEL,
                                          true );
        messagedialog->set_message( _( "Are You Sure You Want to Upgrade The Diary?" ) );
        messagedialog->set_secondary_text( _( STRING::UPGRADE_DIARY_CONFIRM ) );
        messagedialog->add_button( _( "Upgrade The Diary" ), Gtk::RESPONSE_ACCEPT );

        int response( messagedialog->run() );

        delete messagedialog;

        if( response != Gtk::RESPONSE_ACCEPT )
            return;
    }

    switch( Diary::d->enable_editing() )
    {
        case LIFEO::SUCCESS:
            break;
        case FILE_LOCKED:
            AppWindow::p->show_info(
                    Gtk::MESSAGE_INFO,
                    Ustring::compose( _( STRING::DIARY_LOCKED ),
                        Ustring::compose( "<a href=\"file://%1\" title=\"%2\">%3</a>",
                                          Glib::path_get_dirname( Diary::d->get_path() ),
                                          _( "Click to open the containing folder" ),
                                          Diary::d->get_path() + LOCK_SUFFIX ) ) );
            return;
        case FILE_NOT_WRITABLE:
            AppWindow::p->show_info( Gtk::MESSAGE_ERROR, _( STRING::DIARY_NOT_WRITABLE ) );
            return;
        default:
            AppWindow::p->show_info( Gtk::MESSAGE_ERROR, _( STRING::FAILED_TO_OPEN_DIARY ) );
            return;
    }

    m_MB_logout->set_visible( true );
    m_B_enable_edit->set_visible( false );

    m_TV_entries->set_editable( true );

    m_MoB_sync->set_visible( true );
    m_Bx_options->set_visible( true );
    m_MoB_encryption->set_visible( true );

    m_MoB_encryption->property_text() = ( Diary::d->m_passphrase.empty() ?
            _( "Encrypt..." ) : _( STRING::CHANGE_PASSWORD ) );

    AppWindow::p->handle_edit_enabled();
    AppWindow::p->UI_entry->handle_edit_enabled();
    AppWindow::p->UI_extra->handle_edit_enabled();
}

void
UIDiary::handle_logout()
{
    Lifeograph::START_INTERNAL_OPERATIONS();

    m_B_today->set_visible( false );
    m_Bx_entry_header->set_visible( false );
    m_MB_diary->set_visible( false );
    m_Bx_logout->set_visible( false );
    m_Bx_search->set_visible( false );
    m_B_enable_edit->set_visible( false );
    m_Bx_entries_sort_by->set_visible( false );
    m_Bx_sort_tem->set_visible( false );

    m_TV_entries->set_editable( false );
    m_TV_entries->clear();

    Lifeograph::FINISH_INTERNAL_OPERATIONS();
}

void
UIDiary::show_in_list( const DiaryElement* elem )
{
    // if a list elem
    if( elem && elem->get_type() > DiaryElement::ET_MULTIPLE_ENTRIES )
        m_TV_entries->present_element( elem );
    else
        m_TV_entries->get_selection()->unselect_all();
}

void
UIDiary::show_today()
{
    if( not( Diary::d->is_open() ) )
        return;

    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_in_edit_mode() )
            flag_add = true;
        else
            return;
    }
    else                           // or current entry is already at today
    if( entry_today->get_date().get_pure() ==
            AppWindow::p->UI_entry->get_cur_entry()->get_date().get_pure() )
        flag_add = true;

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

    AppWindow::p->show_entry( entry_today );
    AppWindow::p->UI_entry->get_textview()->grab_focus();
}

void
UIDiary::refresh_Po_diary()
{
    m_L_size->set_text( STR::compose( Diary::d->get_size(), " × ", Diary::d->get_time_span() ) );
    m_L_chapters->set_text( STR::compose( Diary::d->get_chapter_count(), "/",
                                          Diary::d->get_p2chapter_ctgs()->size() ) );

    m_MoB_empty_trash->set_visible( Diary::d->is_trash_empty() == false );
}

void
UIDiary::show_entry_Po( Entry* entry, const Gdk::Rectangle&& rect )
{
    m_p2entry_cur = entry;

    Lifeograph::START_INTERNAL_OPERATIONS();

    m_Bx_entry_edit->set_visible( entry != nullptr && Diary::d->is_in_edit_mode() );
    m_Bx_entry_add_header->set_visible( Diary::d->is_in_edit_mode() );
    m_Bx_entry_add_top->set_visible( Diary::d->is_in_edit_mode() );
    m_Bx_entry_add_relative->set_visible( entry != nullptr && entry->is_ordinal() &&
                                          Diary::d->is_in_edit_mode() );
    m_Bx_entry_hide->set_visible( entry != nullptr );
    m_A_hide_entry_cur->property_enabled() = ( entry && entry->get_type() == entry->ET_ENTRY );

    if( entry ) // has selection
    {
        switch( entry->get_todo_status() )
        {
            case ES::NOT_TODO:
                m_RB_entry_auto->set_active();
                break;
            case ES::TODO:
                m_RB_entry_todo->set_active();
                break;
            case ES::PROGRESSED:
                m_RB_entry_progressed->set_active();
                break;
            case ES::DONE:
                m_RB_entry_done->set_active();
                break;
            case ES::CANCELED:
                m_RB_entry_canceled->set_active();
                break;
        }

        m_TB_entry_trashed->set_active( entry->is_trashed() );
        m_TB_entry_favorite->set_active( entry->is_favored() );
        m_B_entry_add_child->set_sensitive( entry->get_date().get_level() < 3 );
    }

    Lifeograph::FINISH_INTERNAL_OPERATIONS();

    m_Po_entry->set_relative_to( *m_TV_entries );
    m_Po_entry->set_pointing_to( rect );
    m_Po_entry->show();
}

void
UIDiary::show_prev_session_elem()
{
    Entry* entry{ Diary::d->get_prev_session_entry() };
    if( entry )
        AppWindow::p->show_entry( entry );
    else
        print_info( "Previous sesion element cannot be found" );
}

Entry*
UIDiary::add_entry( date_t date, const Ustring& text, bool flag_todo )
{
    if( not( Diary::d->is_in_edit_mode() ) )
        return nullptr;

    if( Date::is_ordinal( date ) == false && Date::get_order_3rd( date ) == 0 )
        date++; // fix order

    Entry* entry = Diary::d->create_entry( date, text );

    if( entry )
    {
        if( flag_todo )
            entry->set_todo_status( ES::TODO );

        update_entry_list();
        AppWindow::p->show_entry( entry );
        AppWindow::p->UI_entry->get_textview()->grab_focus();
    }

    return entry;
}

void
UIDiary::add_chapter( date_t date )
{
    if( Diary::d->is_in_edit_mode() == false )
        return;

    if( Diary::d->get_chapter_ctg_cur()->get_chapter_at( date ) == nullptr )
    {
        Chapter* c{ Diary::d->get_chapter_ctg_cur()->create_chapter( date, false, false, true ) };

        c->set_text( _( STRING::NEW_CHAPTER_NAME ) );

        Diary::d->update_entries_in_chapters();
        update_entry_list();
        AppWindow::p->UI_extra->update_calendar();
        AppWindow::p->UI_entry->show( c );
        AppWindow::p->UI_entry->get_textview()->grab_focus();
    }
}

void
UIDiary::empty_trash()
{
    auto messagedialog = new Gtk::MessageDialog( *AppWindow::p,
                                                 _( "Are You Sure You Want to Empty The Trash?" ),
                                                 false,
                                                 Gtk::MESSAGE_WARNING, Gtk::BUTTONS_CANCEL,
                                                 true );
    messagedialog->add_button( _( "Empty The Trash" ), Gtk::RESPONSE_ACCEPT );

    if( messagedialog->run() == Gtk::RESPONSE_ACCEPT )
    {
        AppWindow::p->show_entry( Diary::d->get_entry_first_untrashed() );

        VecEntries trashed_entries;
        for( auto& kv_entry : Diary::d->get_entries() )
            if( kv_entry.second->is_trashed() )
                trashed_entries.push_back( kv_entry.second );

        for( auto& kv_ctg : *Diary::d->get_p2chapter_ctgs() )
            for( auto& kv_chapter : *kv_ctg.second )
                if( kv_chapter.second->is_trashed() )
                    trashed_entries.push_back( kv_chapter.second );

        for( auto&& entry : trashed_entries )
        {
            AppWindow::p->UI_entry->remove_entry_from_history( entry );

            if( entry->get_type() == DiaryElement::ET_CHAPTER )
                Diary::d->dismiss_chapter( dynamic_cast< Chapter* >( entry ), false );
            else
                Diary::d->dismiss_entry( entry );
        }

        update_entry_list();
    }

    delete messagedialog;
}

void
UIDiary::hide_entry_cur()
{
    AppWindow::p->UI_extra->get_W_filter()->add_filterer_is( m_p2entry_cur->get_id(), false );
    AppWindow::p->UI_extra->set_view( "filter" );
}

void
UIDiary::hide_entries_before()
{
    AppWindow::p->UI_extra->get_W_filter()->add_filterer_between_entries(
            m_p2entry_cur, true, nullptr, false );
    AppWindow::p->UI_extra->set_view( "filter" );
}

void
UIDiary::hide_entries_after()
{
    AppWindow::p->UI_extra->get_W_filter()->add_filterer_between_entries(
            nullptr, false, m_p2entry_cur, true );
    AppWindow::p->UI_extra->set_view( "filter" );
}

void
UIDiary::hide_entries_not_refing()
{
    AppWindow::p->UI_extra->get_W_filter()->add_filterer_tagged_by( m_p2entry_cur, true );
    AppWindow::p->UI_extra->set_view( "filter" );
}

void
UIDiary::refresh_row( const Entry* entry )
{
    if( Lifeograph::is_internal_operations_ongoing() ) return;

    Gtk::TreeRow row = * get_row( entry->m_list_data->treepath );
    row[ ListData::colrec->icon ] = entry->get_icon();
    row[ ListData::colrec->info ] = entry->get_list_str(
            ( Diary::d->get_sorting_criteria() & SoCr_FILTER_CRTR ) == SoCr_DATE );
}
// simpler version of above function
void
UIDiary::handle_entry_title_changed( Entry* entry )
{
    if( Lifeograph::is_internal_operations_ongoing() ) return;

    Gtk::TreeRow row = * get_row( entry->m_list_data->treepath );
    row[ ListData::colrec->info ] = entry->get_list_str(
            ( Diary::d->get_sorting_criteria() & SoCr_FILTER_CRTR ) == SoCr_DATE );
}

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

// DIALOGS
void
UIDiary::start_dialog_password()
{
    AppWindow::p->UI_entry->hide_popover();

    DialogPassword::launch(
            Diary::d->is_encrypted() ? DialogPassword::OT_CHANGE : DialogPassword::OT_ADD,
            Diary::d, nullptr,
            m_MB_diary,
            [ this ]{ m_MoB_encryption->set_label( _( STRING::CHANGE_PASSWORD ) ); },
            [ this ]{} );
}

void
UIDiary::start_dialog_sync()
{
    AppWindow::p->UI_entry->hide_popover();

    if( m_dialog_sync == nullptr )
        Lifeograph::get_builder2()->get_widget_derived( "D_synchronize",
                                                        m_dialog_sync );

    if( m_dialog_sync->run() == Result::OK )
    {
        update_entry_list();
        AppWindow::p->UI_extra->refresh_after_sync();
    }

    m_dialog_sync->hide();
}

void
UIDiary::start_dialog_export()
{
    AppWindow::p->UI_entry->hide_popover();

    Gdk::Rectangle rect;

    auto do_start = [ this ]()
    {
        DialogPassword::finish( Diary::d->get_path() );

        if( m_dialog_export == nullptr )
            Lifeograph::get_builder2()->get_widget_derived( "D_export",
                                                            m_dialog_export );

        m_dialog_export->run();
        m_dialog_export->hide();
    };

    if( Diary::d->is_encrypted() )
        DialogPassword::launch( DialogPassword::OT_AUTHENTICATE, Diary::d,
                                nullptr, m_MB_diary, do_start, []{} );
    else
        do_start();
}

void
UIDiary::open_diary_folder() const
{
    GError* err{ nullptr };
    auto path = Glib::filename_to_uri( Glib::path_get_dirname( Diary::d->m_path ) );
    gtk_show_uri_on_window( static_cast< Gtk::Window* >( AppWindow::p )->gobj(),
                            path.c_str(), GDK_CURRENT_TIME, &err );
}

// DIARY PROPERTIES
void
UIDiary::handle_language_changed()
{
    if( Lifeograph::is_internal_operations_ongoing() )
        return;
    std::string lang( m_CB_spellcheck->get_active_text() );
    Diary::d->set_lang( lang == _( STRING::OFF ) ? "" : lang );
}

void
UIDiary::handle_startup_type_changed()
{
    if( Lifeograph::is_internal_operations_ongoing() == false )
        Diary::d->m_startup_entry_id = std::stol( m_CB_startup_type->get_active_id() );

    const bool flag_show_fixed_item{ Diary::d->m_startup_entry_id >= DEID_MIN };

    m_L_startup_elem->set_visible( flag_show_fixed_item );
    m_W_startup_entry->set_visible( flag_show_fixed_item );
    if( flag_show_fixed_item )
       update_startup_elem();
}

void
UIDiary::update_startup_elem()
{
    m_W_startup_entry->set_entry( Diary::d->get_startup_entry() );
}

bool
UIDiary::go_to_startup_elem( const Ustring& )
{
    Entry* entry{ Diary::d->get_startup_entry() };
    if( entry )
        AppWindow::p->show_entry( entry );

    return true;
}
