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

    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 <src/dialog_sync.hpp>
#include <cassert>

#include "lifeograph.hpp"
#include "dialog_export.hpp"

using namespace LIFEO;

constexpr char COLOR_IGNORE[]      = "#cccccc";
constexpr char COLOR_IGNORE_FG[]   = "#666666";
constexpr char COLOR_OVERWRITE[]   = "#d0e0bb";
constexpr char COLOR_ADD[]         = "#ccffcc";
constexpr char COLOR_DELETED[]     = "#ffcccc";
constexpr char COLOR_CHANGED[]     = "#ccccff";

DialogSync::DialogSync( BaseObjectType* cobject,
                        const Glib::RefPtr< Gtk::Builder >& refbuilder )
:   DialogEvent( cobject, refbuilder ), m_dialog_password( nullptr ), m_diary( nullptr )
{
    Gtk::CellRendererPixbuf* CRP_icon{ nullptr };
    Gtk::CellRendererText* CRT_name{ nullptr };
    Gtk::CellRendererCombo* CRC_action{ nullptr };
    Gtk::TreeViewColumn* TVC_name{ nullptr };
    Gtk::Button* B_ignore{ nullptr };

    Gtk::CellRendererText* CRT_comp_l{ nullptr };
    Gtk::CellRendererText* CRT_comp_r{ nullptr };
    Gtk::TreeViewColumn* TVC_comp_l{ nullptr };
    Gtk::TreeViewColumn* TVC_comp_r{ nullptr };

    try
    {
        m_TS_content = TreeStoreImport::create();

        refbuilder->get_widget( "B_sync_go", m_B_go );
        refbuilder->get_widget( "B_sync_cancel", m_B_cancel );
        refbuilder->get_widget( "IB_sync", m_IB_sync );
        refbuilder->get_widget( "L_sync_info", m_L_info );
        refbuilder->get_widget( "FB_sync_diary", m_FCB_diary );
        refbuilder->get_widget_derived( "E_sync_add_tag", m_tag_widget );
        refbuilder->get_widget( "RB_sync_changed_ignore", m_RB_changed_ignore );
        refbuilder->get_widget( "RB_sync_changed_overwrite", m_RB_changed_overwrite );
        refbuilder->get_widget( "RB_sync_changed_add", m_RB_changed_add );
        refbuilder->get_widget( "RB_sync_new_ignore", m_RB_new_ignore );
        refbuilder->get_widget( "RB_sync_new_add", m_RB_new_add );
        refbuilder->get_widget( "B_sync_apply_scheme", m_B_apply_scheme );

        refbuilder->get_widget( "TV_sync_content", m_TV_contents );
        refbuilder->get_widget( "P_sync", m_P_contents );
        refbuilder->get_widget( "Bx_sync_operations", m_Bx_operations );
        refbuilder->get_widget( "TV_sync_compare", m_TV_compare );
        refbuilder->get_widget( "Sw_sync_similar", m_Sw_similar );
        refbuilder->get_widget( "Po_sync_status", m_Po_status );
        refbuilder->get_widget( "Po_sync_action", m_Po_action );
        refbuilder->get_widget( "B_sync_ignore", B_ignore );
        refbuilder->get_widget( "B_sync_overwrite", m_B_ow );
        refbuilder->get_widget( "B_sync_add", m_B_add );
        refbuilder->get_widget( "CB_sync_show_accepted", m_CB_show_accepted );
        refbuilder->get_widget( "CB_sync_show_ignored", m_CB_show_ignored );
        refbuilder->get_widget( "CB_sync_show_new", m_CB_show_new );
        refbuilder->get_widget( "CB_sync_show_changed", m_CB_show_changed );

        CRP_icon = Gtk::manage( new Gtk::CellRendererPixbuf );
        CRT_name = Gtk::manage( new Gtk::CellRendererText );
        CRC_action = Gtk::manage( new Gtk::CellRendererCombo );
        TVC_name = Gtk::manage( new Gtk::TreeViewColumn( _( "Name" ) ) );
        m_TVC_status = Gtk::manage( new Gtk::TreeViewColumn( _( "Status" ),
                                                             m_TS_content->colrec.status ) );
        m_TVC_action = Gtk::manage( new Gtk::TreeViewColumn( _( "Action" ) ) );

        TVC_comp_l = Gtk::manage( new Gtk::TreeViewColumn( _( "Local" ) ) );
        TVC_comp_r = Gtk::manage( new Gtk::TreeViewColumn( _( "Remote" ) ) );
        CRT_comp_l = Gtk::manage( new Gtk::CellRendererText );
        CRT_comp_r = Gtk::manage( new Gtk::CellRendererText );
    }
    catch( ... )
    {
        throw LIFEO::Error( "Failed to create sync dialog" );
    }

    // ACTION COMBOBOX
    m_LS_action_ow = Gtk::ListStore::create( m_colrec_combo );
    auto row = *( m_LS_action_ow->append() );
    row[ m_colrec_combo.name ] = STR0/SI::SI_IGNORE;
    row = *( m_LS_action_ow->append() );
    row[ m_colrec_combo.name ] = STR0/SI::OVERWRITE;

    m_LS_action_add = Gtk::ListStore::create( m_colrec_combo );
    row = *( m_LS_action_add->append() );
    row[ m_colrec_combo.name ] = STR0/SI::SI_IGNORE;
    row = *( m_LS_action_add->append() );
    row[ m_colrec_combo.name ] = STR0/SI::ADD;

    m_LS_action_add_ow = Gtk::ListStore::create( m_colrec_combo );
    row = *( m_LS_action_add_ow->append() );
    row[ m_colrec_combo.name ] = STR0/SI::SI_IGNORE;
    row = *( m_LS_action_add_ow->append() );
    row[ m_colrec_combo.name ] = STR0/SI::ADD;
    row = *( m_LS_action_add_ow->append() );
    row[ m_colrec_combo.name ] = STR0/SI::OVERWRITE;

    // TREEVIEW CONTENT
    m_TV_contents->set_model( m_TS_content );

    // not at all sure about the below approach to sizing
    TVC_name->set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED );
    m_TVC_status->set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED );
    m_TVC_action->set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED );
    m_TVC_status->set_fixed_width( 110 );
    m_TVC_action->set_fixed_width( 110 );

    m_TVC_status->set_sort_indicator( true );
    m_TVC_action->set_sort_indicator( true );
    m_TV_contents->append_column( *TVC_name );
    m_TV_contents->append_column( *m_TVC_status );
    m_TV_contents->append_column( *m_TVC_action );

    // NAME COLUMN
    CRT_name->property_ellipsize() = Pango::ELLIPSIZE_END;
    TVC_name->pack_start( *CRP_icon, false );
    TVC_name->pack_start( *CRT_name );
    TVC_name->add_attribute( CRP_icon->property_pixbuf(), m_TS_content->colrec.icon );
    TVC_name->add_attribute( CRT_name->property_markup(), m_TS_content->colrec.name );
    TVC_name->set_expand( true );

    // STATUS COLUMN
    m_TVC_status->set_clickable( true );
    m_TVC_status->signal_clicked().connect(
            sigc::mem_fun( this, &DialogSync::show_status_filter ) );
    m_TVC_status->set_cell_data_func( * m_TV_contents->get_column_cell_renderer( 1 ),
                                      sigc::mem_fun( this, &DialogSync::set_cell_color_s ) );

    // ACTION COLUMN
    m_TVC_action->pack_start( *CRC_action, false );
    m_TVC_action->add_attribute( CRC_action->property_text(), m_TS_content->colrec.action );
    m_TVC_action->add_attribute( CRC_action->property_model(), m_TS_content->colrec.action_combo );
    m_TVC_action->set_clickable( true );
    CRC_action->property_text_column() = 0;
    CRC_action->property_editable() = true;
    CRC_action->property_has_entry() = false;
    CRC_action->signal_edited().connect(
            sigc::mem_fun( this, &DialogSync::handle_TV_action_edited ) );
    m_TVC_action->signal_clicked().connect(
            sigc::mem_fun( this, &DialogSync::show_action_filter ) );
    m_TVC_action->set_cell_data_func( * m_TV_contents->get_column_cell_renderer( 2 ),
                                      sigc::mem_fun( this, &DialogSync::set_cell_color_a ) );

    // FILTERING
    DialogOpenDiary::init_filters();
    m_FCB_diary->add_filter( DialogOpenDiary::filter_any );
    m_FCB_diary->add_filter( DialogOpenDiary::filter_diary );
    m_FCB_diary->set_filter( DialogOpenDiary::filter_diary );

    // TREEVIEW COMPARE
    CRT_comp_l->property_wrap_mode() = Pango::WRAP_WORD;
    CRT_comp_r->property_wrap_mode() = Pango::WRAP_WORD;
    CRT_comp_l->property_wrap_width() = 300;
    CRT_comp_r->property_wrap_width() = 300;
    m_LS_compare = Gtk::ListStore::create( m_CR_compare );
    m_TV_compare->set_model( m_LS_compare );
    m_TV_compare->append_column( _( "Name" ), m_CR_compare.name );
    TVC_comp_l->pack_start( *CRT_comp_l );
    TVC_comp_r->pack_start( *CRT_comp_r );
    TVC_comp_l->add_attribute( CRT_comp_l->property_text(), m_CR_compare.str_l );
    TVC_comp_r->add_attribute( CRT_comp_r->property_text(), m_CR_compare.str_r );
    TVC_comp_l->set_expand( true );
    TVC_comp_r->set_expand( true );
    m_TV_compare->append_column( *TVC_comp_l );
    m_TV_compare->append_column( *TVC_comp_r );
    TVC_comp_l->set_cell_data_func( * m_TV_compare->get_column_cell_renderer( 1 ),
                                      sigc::mem_fun( this, &DialogSync::set_cell_color_comp_l ) );
    TVC_comp_r->set_cell_data_func( * m_TV_compare->get_column_cell_renderer( 2 ),
                                      sigc::mem_fun( this, &DialogSync::set_cell_color_comp_r ) );

    // BUTTONS
    set_widget_css( B_ignore, Ustring::compose( "button { background: %1 }", COLOR_IGNORE ) );
    set_widget_css( m_B_ow, Ustring::compose( "button { background: %1 }", COLOR_OVERWRITE ) );
    set_widget_css( m_B_add, Ustring::compose( "button { background: %1 }", COLOR_ADD ) );

    m_tag_widget->set_diary( Diary::d );

    // SIGNALS
    m_B_apply_scheme->signal_clicked().connect(
            sigc::mem_fun( this, &DialogSync::update_maps ) );

    m_TV_contents->get_selection()->signal_changed().connect(
            sigc::mem_fun( this, &DialogSync::handle_TV_selection_changed ) );
    m_CB_show_accepted->signal_toggled().connect(
            sigc::mem_fun( *this, &DialogSync::update_filter ) );
    m_CB_show_ignored->signal_toggled().connect(
            sigc::mem_fun( *this, &DialogSync::update_filter ) );
    m_CB_show_new->signal_toggled().connect(
            sigc::mem_fun( *this, &DialogSync::update_filter ) );
    m_CB_show_changed->signal_toggled().connect(
            sigc::mem_fun( *this, &DialogSync::update_filter ) );

    B_ignore->signal_clicked().connect(
            sigc::bind( sigc::mem_fun( this, &DialogSync::change_action ), SI::SI_IGNORE ) );
    m_B_add->signal_clicked().connect(
            sigc::bind( sigc::mem_fun( this, &DialogSync::change_action ), SI::ADD ) );
    m_B_ow->signal_clicked().connect(
            sigc::bind( sigc::mem_fun( this, &DialogSync::change_action ), SI::OVERWRITE ) );

    m_FCB_diary->signal_file_set().connect(
            sigc::mem_fun( this, &DialogSync::open_remote_diary ) );

    m_B_go->signal_clicked().connect(
            sigc::bind( sigc::mem_fun( this, &DialogEvent::response ), Result::OK ) );
    m_B_cancel->signal_clicked().connect(
            sigc::bind( sigc::mem_fun( this, &DialogEvent::response ), Result::ABORTED ) );

    m_IB_sync->signal_response().connect( [ this ]( int ){ m_IB_sync->set_visible( false ); } );
}

void
DialogSync::on_show()
{
    Gtk::Dialog::on_show();
    set_not_ready( _( "Select a diary file for synchronizing" ) );

    m_Bx_operations->set_visible( false );
    m_CB_show_changed->set_active( true );
    m_CB_show_new->set_active( true );
    m_CB_show_accepted->set_active( true );
    m_CB_show_ignored->set_active( true );

    m_RB_changed_add->set_active( true );
    m_RB_new_add->set_active( true );

    m_FCB_diary->unselect_all();
    m_tag_widget->clear();

    m_FCB_diary->grab_focus();

    m_content_filter = FILTER_DEFAULT;
}

void
DialogSync::open_remote_diary()
{
    if( m_diary != nullptr )
        delete m_diary;

    m_diary = new Diary;

    if( m_diary->set_path( m_FCB_diary->get_filename(), Diary::SPT_READ_ONLY ) == SUCCESS )
    {
        if( m_diary->read_header() == SUCCESS )
        {
            if( m_diary->is_encrypted() )
                ask_for_password();
            else
                open_remote_diary2();
        }
        else
        {
            set_not_ready( _( "Selected file does not seem to be a valid diary" ) );
        }
    }
    else
        set_not_ready( _( "Selected file cannot be read" ) );
}

void
DialogSync::open_remote_diary2()
{
    switch( m_diary->read_body())
    {
        case SUCCESS:
        {
            DialogPassword::finish( m_diary->get_path() );
            m_P_contents->set_position( get_height() * 0.55 );
            update_maps();
            populate_content();
            if( m_flag_identical )
                set_not_ready( "Diaries are identical" );
            else
            {
                set_ready();
                m_L_info->set_text(_( "Please select what to import/sync and press Go" ) );
            }
        }
            break;
        case WRONG_PASSWORD:
            open_remote_diary();
            break;
        default:
            set_not_ready( _( "Selected file cannot be read" ) );
            break;
    }
}

void
DialogSync::ask_for_password()
{
    DialogPassword::launch( DialogPassword::OT_OPEN,
                            m_diary, nullptr, m_FCB_diary,
                            sigc::mem_fun( this, &DialogSync::open_remote_diary2 ),
                            [ this ] { m_FCB_diary->unselect_all(); } );
}

void
DialogSync::update_maps()
{
    PRINT_DEBUG( "DialogSync::update_maps()" );

    m_comparison_map.clear();
    m_action_map.clear();
    m_flag_identical = true;

    for( auto kv_elem : m_diary->m_ids )
    {
        const DiaryElement* elem_l;
        SI cmp_res{ Diary::d->compare_foreign_elem( kv_elem.second, elem_l ) };
        m_comparison_map[ kv_elem.first ] = cmp_res;

        if( cmp_res == SI::CHANGED )
        {
            if( m_RB_changed_ignore->get_active() )
                m_action_map[ kv_elem.first ] = SI::SI_IGNORE;
            else if( m_RB_changed_overwrite->get_active() )
                m_action_map[ kv_elem.first ] = SI::OVERWRITE;
            else //if( m_RB_changed_add->get_active() )
                m_action_map[ kv_elem.first ] =
                        ( elem_l->get_type() == DiaryElement::ET_ENTRY ) ?
                                SI::ADD : SI::OVERWRITE;

            if( m_flag_identical )
                m_flag_identical = false;
        }
        else if( cmp_res == SI::NEW )
        {
            if( m_RB_new_ignore->get_active() )
                m_action_map[ kv_elem.first ] = SI::SI_IGNORE;
            else //if( m_RB_new_add->get_active() )
                m_action_map[ kv_elem.first ] = SI::ADD;

            if( m_flag_identical )
                m_flag_identical = false;
        }
        else
            m_action_map[ kv_elem.first ] = SI::SI_IGNORE;
    }

    populate_content();
}

void
DialogSync::handle_TV_selection_changed()
{
    if( !is_ready() )
        return;

    m_LS_compare->clear();
    m_Bx_operations->set_visible( false );
    m_B_ow->set_visible( false );
    m_B_add->set_visible( false );

    auto&&              iter{ m_TV_contents->get_selection()->get_selected() };

    if( !iter )
        return;

    Gtk::TreeRow        row{ *( iter ) };
    const DiaryElement* elem_r{ row[ m_TS_content->colrec.ptr ] };
    const SI            si{ row[ m_TS_content->colrec.comp_res ] };

    if( elem_r == nullptr || si == SI::INTACT )
        return;

    m_Bx_operations->set_visible( true );

    switch( elem_r->get_type() )
    {
        case DiaryElement::ET_ENTRY:
            m_B_add->set_visible( true );
            m_B_ow->set_visible( si == SI::CHANGED );
            break;
        default:
            m_B_add->set_visible( si == SI::NEW );
            m_B_ow->set_visible( si == SI::CHANGED );
            break;
    }

    const DiaryElement* elem_l{ Diary::d->get_corresponding_elem( elem_r ) };

    auto add_item = [ & ]( const SI id, const Ustring& str_l, const Ustring& str_r, SI result )
    {
        row = * ( m_LS_compare->append() );

        row[ m_CR_compare.name ] = STR0/id;
        row[ m_CR_compare.str_l ] = str_l;
        row[ m_CR_compare.str_r ] = str_r;
        row[ m_CR_compare.result ] = result;
    };

    if( elem_l )
    {
        const SKVVec&&  props_l{ elem_l->get_as_skvvec() };
        const SKVVec&&  props_r{ elem_r->get_as_skvvec() };
        unsigned        i_l{ 0 }, i_r{ 0 };

        while( i_l < props_l.size() || i_r < props_r.size() )
        {
            // PAST THE END OF THE LOCAL ITEMS - ADD AS A DELETED ITEM
            if( i_l >= props_l.size() )
            {
                add_item( props_r[ i_r ].first, "", props_r[ i_r ].second, SI::DELETED );
                i_r++;
            }
            // PAST THE END OF THE REMOTE ITEMS - ADD AS A NEW ITEM
            else
            if( i_r >= props_r.size() )
            {
                add_item( props_l[ i_l ].first, props_l[ i_l ].second, "", SI::NEW );
                i_l++;
            }
            // DIFFERENT TYPE OF ITEM - NEW OR DELETED ITEM DEPENDING ON THE COMPARSION OF ORDERS
            else
            if( props_l[ i_l ].first != props_r[ i_r ].first )
            {
                if( props_l[ i_l ].first < props_r[ i_r ].first )
                {
                    add_item( props_l[ i_l ].first, props_l[ i_l ].second, "", SI::NEW );
                    i_l++;
                }
                else
                {
                    add_item( props_r[ i_r ].first, "", props_r[ i_r ].second, SI::DELETED );
                    i_r++;
                }
            }
            // DIFFERENT ITEM VALUES
            else
            if( props_l[ i_l ].second != props_r[ i_r ].second )
            {
                int j_l{ -1 }, j_r{ -1 };
                // LOOK DOWN IN LOCAL ITEMS TO SEE IF THERE IS A MATCH.
                // if so, that will mean new item in local
                for( int i = 1; i_l + i < props_l.size(); i++ )
                    if( props_l[ i_l + i ].first == props_r[ i_r ].first &&
                        props_l[ i_l + i ].second == props_r[ i_r ].second )
                    {
                        j_l = i;
                        break;
                    }
                // LOOK DOWN IN REMOTE ITEMS TO SEE IF THERE IS A MATCH.
                // if so, that will mean deleted item in local
                for( int i = 1; i_r + i < props_r.size(); i++ )
                    if( props_l[ i_l ].first == props_r[ i_r + i ].first &&
                        props_l[ i_l ].second == props_r[ i_r + i ].second )
                    {
                        j_r = i;
                        break;
                    }

                if( j_l > 0 && ( j_r < 0 || j_l <= j_r ) )
                {
                    add_item( props_l[ i_l ].first, props_l[ i_l ].second, "", SI::NEW );
                    i_l++;
                }
                else
                if( j_r > 0 && ( j_l < 0 || j_r <= j_l ) )
                {
                    add_item( props_r[ i_r ].first, "", props_r[ i_r ].second, SI::DELETED );
                    i_r++;
                }
                else
                {
                    add_item( props_l[ i_l ].first, props_l[ i_l ].second,
                              props_r[ i_r ].second, SI::CHANGED );
                    i_l++;
                    i_r++;
                }
            }
            else
            {
                if(props_l[ i_l ].second.empty() == false )
                    add_item( props_l[ i_l ].first, props_l[ i_l ].second,
                              props_r[ i_r ].second, SI::INTACT );
                i_l++;
                i_r++;
            }
        }
    }
}

void
DialogSync::handle_TV_action_edited( const Ustring& path_str, const Ustring& new_text )
{
    Gtk::TreePath path( path_str );
    auto iter = m_TS_content->get_iter( path );
    if( iter )
    {
        auto row = *iter;
        DiaryElement* elem{ row[ m_TS_content->colrec.ptr ] };
        SI action{ new_text == STR0/SI::ADD ? SI::ADD :
                ( new_text == STR0/SI::SI_IGNORE ? SI::SI_IGNORE : SI::OVERWRITE ) };
        if( elem )
        {
            m_action_map[ elem->get_id() ] = action;

            if( m_Sw_similar->get_active() )
            {
                for( auto& kv : m_action_map )
                {
                    DiaryElement* elem_i{ m_diary->get_element( kv.first ) };
                    if( elem_i->get_type() == elem->get_type() &&
                        m_comparison_map[ kv.first ] == m_comparison_map[ elem->get_id() ] )
                        m_action_map[ kv.first ] = action;
                }
            }
        }

        populate_content();
    }
}

void
DialogSync::update_filter()
{
    if( ! m_diary )
        return;

    m_content_filter = 0;

    if( m_CB_show_changed->get_active() )
        m_content_filter |= FILTER_SHOW_CHANGED;
    if( m_CB_show_new->get_active() )
        m_content_filter |= FILTER_SHOW_NEW;

    if( m_CB_show_ignored->get_active() )
        m_content_filter |= FILTER_SHOW_IGNORED;
    if( m_CB_show_accepted->get_active() )
        m_content_filter |= FILTER_SHOW_ACCEPTED;

    populate_content();
}

void
DialogSync::show_status_filter()
{
    Gdk::Rectangle rect{ m_TVC_status->get_x_offset(), 0,
                         m_TVC_status->get_width(),
                         m_TVC_status->get_button()->get_allocated_height() };
    m_Po_status->set_relative_to( *m_TV_contents );
    m_Po_status->set_pointing_to( rect );
    m_Po_status->show();
}

void
DialogSync::show_action_filter()
{
    Gdk::Rectangle rect{ m_TVC_action->get_x_offset(), 0,
                         m_TVC_action->get_width(),
                         m_TVC_action->get_button()->get_allocated_height() };
    m_Po_action->set_relative_to( *m_TV_contents );
    m_Po_action->set_pointing_to( rect );
    m_Po_action->show();
}

void
DialogSync::change_action( SI si )
{
    auto treesel{ m_TV_contents->get_selection() };
    if( treesel->count_selected_rows() < 1 )
        return;

    handle_TV_action_edited( m_TS_content->get_path( treesel->get_selected() ).to_string(),
                             STR0/si );
}

void
DialogSync::populate_content()
{
    auto add_header = [ & ]( int header_type )
    {
        Gtk::TreeRow row{ * m_TS_content->append() };
        row[ m_TS_content->colrec.ptr ] = nullptr;
        //row[ m_TS_content->colrec.icon ] = icon;
        row[ m_TS_content->colrec.header_type ] = header_type;
        row[ m_TS_content->colrec.comp_res ] = SI::_NONE_;
        return row;
    };

    auto update_header = [ & ]( Gtk::TreeRow& row, const Ustring& name, const int size )
    {
        if( row.children().empty() )
            m_TS_content->erase( row );
        else
            row[ m_TS_content->colrec.name ] = Glib::ustring::compose( "<b>%1</b> (%2/%3)",
                                                                       name,
                                                                       row.children().size(),
                                                                       size );
    };

    auto get_action_combo = [ & ]( DiaryElement* elem, SI si )
    {
        switch( elem->get_type() )
        {
            case DiaryElement::ET_ENTRY:
                return( si == SI::NEW ? m_LS_action_add : m_LS_action_add_ow );
            default:
                return( si == SI::NEW ? m_LS_action_add : m_LS_action_ow );
        }
    };

    auto add_elem = [ & ]( DiaryElement* elem_r, Gtk::TreeRow& parent )
    {
        SI cmp_res{ m_comparison_map[ elem_r->get_id() ] };
        SI action{ m_action_map[ elem_r->get_id() ] };

        if( cmp_res == SI::INTACT ||
            ( cmp_res == SI::NEW && !( m_content_filter & FILTER_SHOW_NEW ) ) ||
            ( cmp_res == SI::CHANGED && !( m_content_filter & FILTER_SHOW_CHANGED ) ) ||
            ( action == SI::SI_IGNORE && !( m_content_filter & FILTER_SHOW_IGNORED ) ) ||
            ( action != SI::SI_IGNORE && !( m_content_filter & FILTER_SHOW_ACCEPTED ) ) )
            return;

        Gtk::TreeRow row = *( m_TS_content->append( parent.children() ) );

        row[ m_TS_content->colrec.name ] = elem_r->get_list_str();
        row[ m_TS_content->colrec.ptr ] = elem_r;
        row[ m_TS_content->colrec.icon ] = elem_r->get_icon();
        row[ m_TS_content->colrec.status ] = STR0/cmp_res;
        row[ m_TS_content->colrec.action ] = STR0/action;
        row[ m_TS_content->colrec.action_combo ] = get_action_combo( elem_r, cmp_res );
        row[ m_TS_content->colrec.header_type ] = 0; //TreeStoreImport::HT_NONE;
        row[ m_TS_content->colrec.comp_res ] = cmp_res;
    };

    Gtk::TreePath path_top, path_btm;
    m_TV_contents->get_visible_range( path_top, path_btm );

    m_TV_contents->unset_model();
    m_TS_content->clear();

    // CHAPTER CATEGORIES
    Gtk::TreeRow row_header, row_chapter;
    for( auto& kv_ctg : m_diary->m_chapter_categories )
    {
        SI cmp_res{ m_comparison_map[ kv_ctg.second->get_id() ] };
        row_header = add_header( TreeStoreImport::HT_CHAPTER_CTG );
        // Chapter Categories are not plain headers but also elements per se
        // so we set element related fields manually:
        row_header[ m_TS_content->colrec.ptr ] = kv_ctg.second;
        row_header[ m_TS_content->colrec.status ] = STR0/cmp_res;
        row_header[ m_TS_content->colrec.action ] = STR0/m_action_map[ kv_ctg.second->get_id() ];
        row_header[ m_TS_content->colrec.comp_res ] = cmp_res;
        // DATED CHAPTERS
        for( auto& kv_chapter : * kv_ctg.second )
            add_elem( kv_chapter.second, row_header );

        if( cmp_res == SI::INTACT && row_header.children().empty() )
            m_TS_content->erase( row_header );
        else
            update_header( row_header, kv_ctg.second->get_name(), kv_ctg.second->size() );
    }

    // OTHER ELEMENTS
    auto&& row_header_entries{ add_header( TreeStoreImport::HT_ENTRIES ) };
    auto&& row_header_themes{ add_header( TreeStoreImport::HT_THEMES ) };
    auto&& row_header_filters{ add_header( TreeStoreImport::HT_FILTERS ) };
    auto&& row_header_charts{ add_header( TreeStoreImport::HT_CHARTS ) };
    auto&& row_header_tables{ add_header( TreeStoreImport::HT_TABLES ) };
    for( auto& kv_elem : m_action_map )
    {
        DiaryElement* elem{ m_diary->get_element( kv_elem.first ) };
        switch( elem->get_type() )
        {
            case DiaryElement::ET_ENTRY:
                add_elem( elem, row_header_entries );
                break;
            case DiaryElement::ET_THEME:
                add_elem( elem, row_header_themes );
                break;
            case DiaryElement::ET_FILTER:
                add_elem( elem, row_header_filters );
                break;
            case DiaryElement::ET_CHART:
                add_elem( elem, row_header_charts );
                break;
            case DiaryElement::ET_TABLE:
                add_elem( elem, row_header_tables );
                break;
            default:
                break;
        }
    }
    update_header( row_header_entries, _( "ENTRIES" ), m_diary->m_entries.size() );
    update_header( row_header_themes, _( "THEMES" ), m_diary->m_themes.size() );
    update_header( row_header_filters, _( "FILTERS" ), m_diary->m_filters.size() );
    update_header( row_header_charts, _( "CHARTS" ), m_diary->m_charts.size() );
    update_header( row_header_tables, _( "TABLES" ), m_diary->m_tables.size() );

    m_TV_contents->set_model( m_TS_content );
    m_TV_contents->expand_all();

    if( path_top.empty() == false )
        m_TV_contents->scroll_to_row( path_top, 0.0 );
}

void
DialogSync::set_cell_color_s( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter )
{
    Gdk::RGBA color_b{ COLOR_IGNORE };
    Gdk::RGBA color_f{ "#000000" };

    switch( ( * iter )[ m_TS_content->colrec.comp_res ]  )
    {
        case SI::CHANGED:
            color_b.set( "#ffcc66" );
            color_f.set( "#664422" );
            break;
        case SI::NEW:
            color_b.set( "#66cc66" );
            color_f.set( "#446644" );
            break;
        case SI::DELETED:
            color_b.set( "#cc6666" );
            color_f.set( "#664444" );
            break;
        default:
            break;
    }

    cell->property_cell_background_rgba() = color_b;
    dynamic_cast< Gtk::CellRendererText* >( cell )->property_foreground_rgba() = color_f;
}

void
DialogSync::set_cell_color_a( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter )
{
    DiaryElement* elem{ ( * iter )[ m_TS_content->colrec.ptr ] };

    if( elem != nullptr )
    {
        switch( m_action_map[ elem->get_id() ] )
        {
            case SI::ADD:
                cell->property_cell_background_rgba() = Gdk::RGBA( COLOR_ADD );
                break;
            case SI::OVERWRITE:
                cell->property_cell_background_rgba() = Gdk::RGBA( COLOR_OVERWRITE );
                break;
            default:
                cell->property_cell_background_rgba() = Gdk::RGBA( COLOR_IGNORE );
                break;
        }
    }
    else
        cell->property_cell_background_rgba() = Gdk::RGBA( COLOR_IGNORE );

    dynamic_cast< Gtk::CellRendererText* >( cell )->property_foreground_rgba() =
            Gdk::RGBA( "#000000" );
}

void
DialogSync::set_cell_color_comp_l( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter )
{
    Gdk::RGBA   color_f{ "#000000" };

    switch( ( * iter )[ m_CR_compare.result ] )
    {
        case SI::NEW:
            cell->property_cell_background_rgba() = Gdk::RGBA( COLOR_ADD );
            break;
        case SI::CHANGED:
            cell->property_cell_background_rgba() = Gdk::RGBA( COLOR_CHANGED );
            break;
        case SI::DELETED:
            cell->property_cell_background_rgba() = Gdk::RGBA( COLOR_DELETED );
            break;
        default:
            cell->property_cell_background_rgba() = Gdk::RGBA( COLOR_IGNORE );
            color_f.set( COLOR_IGNORE_FG );
            break;
    }

    dynamic_cast< Gtk::CellRendererText* >( cell )->property_foreground_rgba() = color_f;
}

void
DialogSync::set_cell_color_comp_r( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter )
{
    Gdk::RGBA   color_f{ "#000000" };

    switch( ( * iter )[ m_CR_compare.result ] )
    {
        case SI::NEW:
            cell->property_cell_background_rgba() = Gdk::RGBA( COLOR_DELETED );
            break;
        case SI::CHANGED:
            cell->property_cell_background_rgba() = Gdk::RGBA( COLOR_CHANGED );
            break;
        case SI::DELETED:
            cell->property_cell_background_rgba() = Gdk::RGBA( COLOR_ADD );
            break;
        default:
            cell->property_cell_background_rgba() = Gdk::RGBA( COLOR_IGNORE );
            color_f.set( COLOR_IGNORE_FG );
            break;
    }

    dynamic_cast< Gtk::CellRendererText* >( cell )->property_foreground_rgba() = color_f;
}

inline bool
DialogSync::is_ready()
{
    return( not( m_flag_identical ) && m_flag_diary_is_ready );
}

void
DialogSync::set_ready()
{
    m_B_apply_scheme->set_sensitive( true );
    m_B_go->set_sensitive( true );
    m_flag_diary_is_ready = true;
}

void
DialogSync::set_not_ready( const Ustring& message )
{
    m_flag_diary_is_ready = false;

    m_B_go->set_sensitive( false );
    m_B_apply_scheme->set_sensitive( false );

    m_LS_compare->clear();
    m_TS_content->clear();
    m_action_map.clear();

    if( m_diary )
    {
        delete m_diary;
        m_diary = nullptr;
    }

    if( ! m_IB_sync->is_visible() )
        m_IB_sync->set_visible( true );

    m_L_info->set_text( message );
}

void
DialogSync::on_response( int response )
{
    if( response == Result::OK )
    {
        // prepare the new tag
        Entry*  tag_all{ m_tag_widget->get_entry_create( Diary::d ) };
        SI      action;

        for( auto& kv_theme : m_diary->m_themes )
        {
            action = m_action_map[ kv_theme.second->get_id() ];
            if( action != SI::SI_IGNORE )
                Diary::d->import_theme( kv_theme.second, action == SI::ADD );
        }

        for( auto& kv_filter : m_diary->m_filters )
        {
            action = m_action_map[ kv_filter.second->get_id() ];
            if( action != SI::SI_IGNORE )
                Diary::d->import_filter( kv_filter.second, action == SI::ADD );
        }

        for( auto& kv_chart : m_diary->m_charts )
        {
            action = m_action_map[ kv_chart.second->get_id() ];
            if( action != SI::SI_IGNORE )
                Diary::d->import_chart( kv_chart.second, action == SI::ADD );
        }

        for( auto& kv_table : m_diary->m_tables )
        {
            action = m_action_map[ kv_table.second->get_id() ];
            if( action != SI::SI_IGNORE )
                Diary::d->import_table( kv_table.second, action == SI::ADD );
        }

        for( auto& kv_cpt_ctg : m_diary->m_chapter_categories )
        {
            action = m_action_map[ kv_cpt_ctg.second->get_id() ];
            if( action != SI::SI_IGNORE )
                Diary::d->import_chapter_ctg( kv_cpt_ctg.second, action == SI::ADD );

            for( auto& kv_cpt : * kv_cpt_ctg.second )
            {
                action = m_action_map[ kv_cpt.second->get_id() ];
                if( action != SI::SI_IGNORE )
                    Diary::d->import_chapter( kv_cpt.second, action == SI::ADD );
            }
        }

        for( auto& kv_entry : m_diary->m_entries )
        {
            action = m_action_map[ kv_entry.second->get_id() ];
            if( action != SI::SI_IGNORE )
                Diary::d->import_entry( kv_entry.second, tag_all, action == SI::ADD );
        }
    }

    set_not_ready( "" );

    Gtk::Dialog::on_response( response );
}
