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

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

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

#include <gtkmm/table.h>

#include "lifeograph.hpp"
#include "dialog_export.hpp"
#include "dialog_password.hpp"
#include "app_window.hpp"

using namespace LIFEO;

// DIALOG SAVE GENERIC =============================================================================
DialogSaveGeneric::DialogSaveGeneric( const Glib::ustring& title )
:   Gtk::FileChooserDialog( title, Gtk::FILE_CHOOSER_ACTION_SAVE )
{
    add_button( _( "_Cancel" ), Gtk::RESPONSE_CANCEL );
    add_button( _( "_Save" ), Gtk::RESPONSE_ACCEPT );
    set_do_overwrite_confirmation( true );
    set_transient_for( * AppWindow::p );
}

DialogSaveGeneric::DialogSaveGeneric( BaseObjectType* cobject,
                                      const Glib::RefPtr< Gtk::Builder > &refbuilder )
:   Gtk::FileChooserDialog( cobject )
{
    add_button( _( "_Cancel" ), Gtk::RESPONSE_CANCEL );
    add_button( _( "_Save" ), Gtk::RESPONSE_ACCEPT );
    set_do_overwrite_confirmation( true );
}

std::string
DialogSaveGeneric::get_filename( const std::string& extension )
{
    std::string fname( FileChooserDialog::get_filename() );

    if( ! extension.empty() && extension != "." )
        if( ! str_ends_with( fname, extension ) )
            fname.append( extension );

    return fname;
}

// DIALOG SAVE DIARY ===============================================================================
DialogSaveDiary *DialogSaveDiary::ptr = nullptr;

DialogSaveDiary::DialogSaveDiary( BaseObjectType* cobject,
                                  const Glib::RefPtr< Gtk::Builder > &refbuilder )
:   DialogSaveGeneric( cobject, refbuilder )
{
    try
    {
        auto builder{ Lifeograph::get_builder2() };
        builder->get_widget( "check_save_encrypt", m_check_encrypt );
        builder->get_widget( "grid_save_password", m_grid_password );
        builder->get_widget( "entry_save_password", m_entry_password );
        builder->get_widget( "entry_save_confirm", m_entry_confirm );
    }
    catch( ... )
    {
        throw LIFEO::Error( "creation of save dialog failed" );
    }

    m_check_encrypt->signal_toggled().connect(
            sigc::mem_fun( this, &DialogSaveDiary::handle_encryption_toggled ) );
    m_entry_password->signal_changed().connect(
            sigc::mem_fun( this, &DialogSaveDiary::handle_password_changed ) );
    m_entry_confirm->signal_changed().connect(
            sigc::mem_fun( this, &DialogSaveDiary::handle_password_changed ) );
}

int
DialogSaveDiary::launch( std::string& path, std::string& passphrase )
{
    if( ptr  == nullptr )
        Lifeograph::get_builder2()->get_widget_derived( "FD_save", ptr );

    ptr->set_transient_for( * AppWindow::p );
    ptr->set_current_folder( Glib::path_get_dirname( path ) );
    ptr->set_current_name( Glib::path_get_basename( path ) );
    ptr->m_check_encrypt->set_active( false );

    int ret_value( ptr->run() );
    path = ptr->get_filename( Lifeograph::settings.diary_extension );
    passphrase = ptr->m_entry_password->get_text();
    ptr->hide();
    ptr->set_current_folder( "/" ); // this prevents gtkrbtree core dumps, details not known

    return ret_value;
}

void
DialogSaveDiary::handle_encryption_toggled()
{
    bool active( m_check_encrypt->get_active() );
    m_grid_password->set_visible( active );
    if( !active )
    {
        m_entry_password->set_text( "" );
        m_entry_confirm->set_text( "" );
    }
    else
    {
        m_entry_password->grab_focus();
    }
    set_response_sensitive( Gtk::RESPONSE_ACCEPT, !active );
}

void
DialogSaveDiary::handle_password_changed()
{
    Glib::ustring passphrase( m_entry_password->get_text() );
    bool pass_ok( passphrase.size() >= LIFEO::PASSPHRASE_MIN_SIZE &&
                  passphrase == m_entry_confirm->get_text() );

    set_response_sensitive( Gtk::RESPONSE_ACCEPT, pass_ok );
}

// DIALOG OPEN DIARY ===============================================================================
bool DialogOpenDiary::flag_filters_created = false;
Glib::RefPtr< Gtk::FileFilter > DialogOpenDiary::filter_any;
Glib::RefPtr< Gtk::FileFilter > DialogOpenDiary::filter_diary;

DialogOpenDiary::DialogOpenDiary()
    // TRANSLATORS: this is the title of file chooser dialog for selecting a new diary
:   Gtk::FileChooserDialog( _( "Select a Diary" ), Gtk::FILE_CHOOSER_ACTION_OPEN )
{
    // Gtk gets confused if cancel is not stock:
    add_button( Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL );
    add_button( _( "Edit" ), RESPONSE_EDIT );
    add_button( _( "Read" ), RESPONSE_READ );

    init_filters();
    add_filter( filter_any );
    add_filter( filter_diary );
    set_filter( filter_diary );

    signal_selection_changed().connect(
            sigc::mem_fun( this, &DialogOpenDiary::handle_selection_changed ) );

    set_transient_for( * AppWindow::p );

    set_default_response( RESPONSE_EDIT );
}

void
DialogOpenDiary::init_filters()
{
    if( flag_filters_created )
        return;

    filter_any = Gtk::FileFilter::create();
    filter_diary = Gtk::FileFilter::create();

    filter_any->set_name( _( "All Files" ) );
    filter_any->add_pattern( "*" );
    filter_diary->set_name( _( "Diary Files" ) );
#ifdef _WIN32
    filter_diary->add_pattern( "*.diary" );
#else
    filter_diary->add_mime_type( "application/x-lifeographdiary" );
#endif

    flag_filters_created = true;
}

void
DialogOpenDiary::handle_selection_changed()
{
    bool flag_enable( get_filename().size() > 0 );  // check if anything is selected
    if( flag_enable )
        flag_enable = ( Glib::file_test( get_filename(), Glib::FILE_TEST_IS_DIR ) == false );

    set_response_sensitive( RESPONSE_READ, flag_enable );
    set_response_sensitive( RESPONSE_EDIT, flag_enable );
}

// DIALOG IMPORT ===================================================================================
DialogImport::DialogImport( BaseObjectType* cobject,
                            const Glib::RefPtr< Gtk::Builder >& refbuilder )
:   DialogEvent( cobject, refbuilder ), m_dialog_password( nullptr ), m_diary( nullptr )
{
    refbuilder->get_widget( "B_import_import", m_B_import );
    refbuilder->get_widget( "B_import_cancel", m_B_cancel );
    refbuilder->get_widget( "FB_import_diary", m_file_chooser_button );
    refbuilder->get_widget( "L_import_info", m_label_info );
    refbuilder->get_widget( "L_import_entries", m_label_entries );
    refbuilder->get_widget( "L_import_tags", m_label_tags );
    refbuilder->get_widget( "L_import_chapters", m_label_chapters );
    refbuilder->get_widget_derived( "E_import_add_tag", m_tag_widget );

    refbuilder->get_widget( "CB_import_entries", m_checkbutton_import_entries );
    refbuilder->get_widget( "CB_import_tags", m_checkbutton_import_tags );
    refbuilder->get_widget( "CB_import_chapters", m_checkbutton_import_chapters );

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

    // SIGNALS
    m_file_chooser_button->signal_file_set().connect(
            sigc::mem_fun( this, &DialogImport::handle_diary_changed ) );

    m_checkbutton_import_entries->signal_toggled().connect(
            sigc::mem_fun( this, &DialogImport::handle_parts_changed ) );
    m_checkbutton_import_tags->signal_toggled().connect(
            sigc::mem_fun( this, &DialogImport::handle_parts_changed ) );
    m_checkbutton_import_chapters->signal_toggled().connect(
            sigc::mem_fun( this, &DialogImport::handle_parts_changed ) );

    m_B_import->signal_clicked().connect(
            sigc::bind( sigc::mem_fun( this, &DialogEvent::response ), RESPONSE_GO ));
    m_B_cancel->signal_clicked().connect(
            sigc::bind( sigc::mem_fun( this, &DialogEvent::response ), RESPONSE_CANCEL ));
}

inline bool
DialogImport::is_options_ready()
{
    return( m_option_import_entries || m_option_import_tags || m_option_import_chapters );
}

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

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

    m_checkbutton_import_entries->set_active( true );
    m_checkbutton_import_tags->set_active( true );
    m_checkbutton_import_chapters->set_active( true );

    m_tag_widget->populate();

    m_file_chooser_button->grab_focus();
}

void
DialogImport::handle_diary_changed()
{
    if( m_diary != nullptr )
        delete m_diary;

    m_diary = new Diary;

    if( m_diary->set_path( m_file_chooser_button->get_filename(), Diary::SPT_READ_ONLY )
            == SUCCESS )
    {
        if( m_diary->read_header() == SUCCESS )
        {
            if( m_diary->is_encrypted() )
            {
                DialogPassword::launch( DialogPassword::OT_OPEN, m_diary );
                // TODO: do something if result != RESPONSE_GO
            }

            Result result = m_diary->read_body();
            if( result == SUCCESS )
            {
                set_ready();
            }
            else if( result == WRONG_PASSWORD )
            {
                set_not_ready( _( "Entered password is wrong; please try again" ) );
            }
            else
            {
                set_not_ready( _( "Selected file cannot be read" ) );
            }
        }
        else
        {
            set_not_ready( _( "Selected file does not seem to be a valid diary" ) );
        }
    }
    else
        set_not_ready( _( "Selected file cannot be read" ) );
}

void
DialogImport::handle_parts_changed()
{
    m_option_import_entries = m_checkbutton_import_entries->get_active();
    m_option_import_tags = m_checkbutton_import_tags->get_active();
    m_option_import_chapters = m_checkbutton_import_chapters->get_active();

    m_tag_widget->set_sensitive( m_option_import_entries );
    m_B_import->set_sensitive( m_flag_diary_is_ready && is_options_ready() );
}

void
DialogImport::set_ready()
{
    m_B_import->set_sensitive( is_options_ready() );

    int n_chapters = 0;
    for( auto& kv_ctg : m_diary->m_chapter_categories )
    {
        n_chapters += kv_ctg.second->size();
    }

    m_label_entries->set_text(
            Glib::ustring::compose( "%1", m_diary->get_size() ) );
    m_label_tags->set_text(
            Glib::ustring::compose( "%1", m_diary->get_tags()->size() ) );
    m_label_chapters->set_text(
            Glib::ustring::compose( "%1/%2", n_chapters, m_diary->m_chapter_categories.size() ) );

    m_label_info->set_text( _( "Diary is ready for importing" ) );
    m_flag_diary_is_ready = true;
}

void
DialogImport::set_not_ready( const Glib::ustring& message )
{
    m_B_import->set_sensitive( false );

    m_label_entries->set_text( "-" );
    m_label_tags->set_text( "-" );
    m_label_chapters->set_text( "-" );

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

    m_label_info->set_text( message );
    m_flag_diary_is_ready = false;
}

void
DialogImport::on_response( int response )
{
    if( response == RESPONSE_GO )
    {
        if( m_option_import_tags )  // tags need to come before entries
        {
            for( auto& kv_tag : m_diary->m_tags )
            {
                Diary::d->import_tag( kv_tag.second );
            }
        }

        if( m_option_import_entries )
        {
            Diary::d->import_entries( *m_diary, m_option_import_tags,
                                      m_tag_widget->get_nav() );
        }

        if( m_option_import_chapters )
        {
            Diary::d->import_chapters( *m_diary );
        }
    }

    delete m_diary;
    m_diary = nullptr;

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

// DIALOG EXPORT ===================================================================================
DialogExport::DialogExport( BaseObjectType* cobject,
        const Glib::RefPtr< Gtk::Builder >& refbuilder )
:   DialogEvent( cobject, refbuilder ), m_ptr2widget_options( nullptr ), m_ptr2diary( Diary::d )
{
    auto builder{ Lifeograph::get_builder2() };
    builder->get_widget( "CB_export_type", m_combo_type );
    builder->get_widget( "CB_export_extent", m_combo_extent );
    builder->get_widget( "F_export_options", m_frame_options );
    builder->get_widget( "A_export_options", m_align_options );
    builder->get_widget( "B_export_go", m_button_export );

    add_saver( new ExporterLifeograph( m_ptr2diary ) );
    add_saver( new ExporterText( m_ptr2diary ) );

    m_combo_extent->append( _( "All Entries" ) );
    m_combo_extent->append( _( "Only Filtered Entries" ) );
    m_combo_extent->set_active( 0 );

    m_combo_type->signal_changed().connect(
            sigc::mem_fun( this, &DialogExport::handle_savetype_changed ) );

    m_combo_type->set_active( 0 );    // defaults to lifeograph diary
}

void
DialogExport::add_saver( Diarysaver* saver )
{
    m_savers.push_back( saver );
    m_combo_type->append( saver->get_name() );
    saver->signal_updated().connect(
            sigc::mem_fun( this, &DialogExport::handle_saver_updated ) );
}

void
DialogExport::handle_saver_updated( bool ready )
{
    m_flag_saverready = ready;
    m_button_export->set_sensitive( ready );
}

void
DialogExport::handle_savetype_changed()
{
    m_saver_cur = m_savers[ m_combo_type->get_active_row_number() ];

    if( m_ptr2widget_options )
    {
        m_align_options->remove();
        delete m_ptr2widget_options;
    }

    m_ptr2widget_options = m_saver_cur->draw_options();

    if( m_ptr2widget_options )
    {
        m_align_options->add( *m_ptr2widget_options );
        m_frame_options->show();
    }
    else
        m_frame_options->hide();

    m_flag_saverready = m_saver_cur->is_ready();

    m_button_export->set_sensitive( m_flag_saverready );
}

void
DialogExport::on_response( int response )
{
    if( response == RESPONSE_GO )
    {
        // TODO: handle result
        m_saver_cur->save( m_combo_extent->get_active() );
    }

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

// EXPORTERS =======================================================================================
Diarysaver::Diarysaver( const Glib::ustring& name, Diary* diary )
:   m_name( name ), m_ptr2diary( diary )
{
}

ExporterLifeograph::ExporterLifeograph( Diary* diary )
:   Diarysaver( _( "Lifeograph Diary File" ), diary )
{
}

Gtk::Widget*
ExporterLifeograph::draw_options()
{
    /*m_entry_passphrase = Gtk::manage( new Gtk::Entry );
    m_entry_confirm = Gtk::manage( new Gtk::Entry );
    m_check_encrypt = Gtk::manage( new Gtk::CheckButton( _( "Encrypt Diary Content" ) ) );
    Gtk::Table *table = new Gtk::Table;

    m_entry_passphrase->set_visibility( false );
    m_entry_confirm->set_visibility( false );

    table->set_border_width( 5 );
    table->set_row_spacings( 2 );
    table->set_col_spacings( 15 );
    table->attach( *m_check_encrypt, 0, 2, 0, 1, Gtk::FILL, Gtk::SHRINK );
    table->attach( *Gtk::manage( new Gtk::Label( _( "Password:" ) ) ), 0, 1, 1, 2, Gtk::SHRINK );
    table->attach( *m_entry_passphrase, 1, 2, 1, 2, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK );
    // TRANSLATORS: confirm here means re-enter password
    table->attach( *Gtk::manage( new Gtk::Label( _( "Confirm:" ) ) ), 0, 1, 2, 3, Gtk::SHRINK );
    table->attach( *m_entry_confirm, 1, 2, 2, 3, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK );

    // TODO: add a match indicator icon

    m_entry_passphrase->set_sensitive( false );
    m_entry_confirm->set_sensitive( false );

    m_entry_passphrase->signal_changed().connect(
            sigc::mem_fun( this, &ExporterLifeograph::handle_passphrase_changed ) );
    m_entry_confirm->signal_changed().connect(
            sigc::mem_fun( this, &ExporterLifeograph::handle_passphrase_changed ) );
    m_check_encrypt->signal_toggled().connect(
            sigc::mem_fun( this, &ExporterLifeograph::handle_encryption_changed ) );

    table->show_all();

    return table;*/
    return nullptr;
}

bool
ExporterLifeograph::is_ready()
{
    //return( ( ! m_flag_encrypted ) || m_passphrase.size() >= LIFEO::PASSPHRASE_MIN_SIZE );
    return true;
}

/*void
ExporterLifeograph::handle_passphrase_changed()
{
    m_passphrase = m_entry_passphrase->get_text();
    if( m_passphrase != m_entry_confirm->get_text() )
        m_passphrase.clear();
    else
        if( m_passphrase.size() < LIFEO::PASSPHRASE_MIN_SIZE )
            m_passphrase.clear();

    m_signal_updated.emit( m_passphrase.size() >= LIFEO::PASSPHRASE_MIN_SIZE );
}

void
ExporterLifeograph::handle_encryption_changed()
{
    m_flag_encrypted = m_check_encrypt->get_active();
    m_entry_passphrase->set_sensitive( m_flag_encrypted );
    m_entry_confirm->set_sensitive( m_flag_encrypted );

    m_signal_updated.emit( ( ! m_flag_encrypted ) ||
                           m_passphrase.size() >= LIFEO::PASSPHRASE_MIN_SIZE );
}*/

Result
ExporterLifeograph::save( bool filtered )
{
    std::string path( m_ptr2diary->get_path() + ".backup" );
    std::string pass;

    if( DialogSaveDiary::launch( path, pass ) != Gtk::RESPONSE_ACCEPT )
        return LIFEO::ABORTED;

    if( pass.size() >= LIFEO::PASSPHRASE_MIN_SIZE )
        return m_ptr2diary->write_copy( path, pass, filtered );
    else
        return m_ptr2diary->write_copy( path, "", filtered );
}

ExporterText::ExporterText( Diary* diary )
        : Diarysaver( _( "Plain Text File" ), diary )
{

}

Result
ExporterText::save( bool filtered )
{
    // TRANSLATORS: this is the title of file chooser dialog for saving a new diary
    DialogSaveGeneric *ds( new DialogSaveGeneric( _( "Where to Save the Text File?" ) ) );
    ds->set_current_folder( Glib::path_get_dirname( m_ptr2diary->get_path() ) );
    ds->set_current_name( Glib::path_get_basename( m_ptr2diary->get_path() ) + ".txt" );
    if( ds->run() != Gtk::RESPONSE_ACCEPT )
    {
        delete ds;  // also removes the dialog from the screen
        return LIFEO::ABORTED;
    }

    std::string path( ds->get_filename() );
    delete ds;
    return m_ptr2diary->write_txt( path, filtered );
}
