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

    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 <string>
#include <cstdlib>
#include <cassert>

#include "strings.hpp"
#include "lifeograph.hpp"
#include "app_window.hpp"
#include "dialog_preferences.hpp"


using namespace LIFEO;

AppWindow* AppWindow::p = nullptr;

// CONSTRUCTOR
AppWindow::AppWindow( BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& b )
:   Gtk::ApplicationWindow( cobject )
{
    p = this;

    Cipher::init();

    Diary::d = new Diary;
    ListData::colrec = new ListData::Colrec;

    // CONFIGURATION
    set_default_size( Lifeograph::settings.width, Lifeograph::settings.height );
    if( Lifeograph::settings.position_x != POSITION_NOTSET &&
        Lifeograph::settings.position_y != POSITION_NOTSET )
        move( Lifeograph::settings.position_x, Lifeograph::settings.position_y );
    if( Lifeograph::settings.state_maximized )
        maximize();

    // DRAW UI
    try
    {
        auto builder{ Lifeograph::get_builder() };
        builder->get_widget( "Bx_main", m_Bx_main );
        builder->get_widget( "Hb_main", m_Hb_main );
        builder->get_widget( "St_main", m_St_main );
        builder->get_widget( "IB_main", m_IB_info );
        builder->get_widget( "L_infobar", m_L_info );
        builder->get_widget( "B_infobar", m_B_info );

        builder->get_widget( "B_back", m_B_back );

        UI_login = new UILogin;
    }
    catch( ... )
    {
        throw LIFEO::Error( "Failed to create the login view" );
    }

#ifdef _WIN32
    m_Hb_main->set_decoration_layout( ":minimize,maximize,close" );
#endif

    // SIGNALS
    m_IB_info->signal_response().connect(
            [ this ]( int r ){ handle_infobar_response( r ); } );

    m_A_logout_wo_saving = Lifeograph::p->add_action( "logout_wo_saving",
            [ this ](){ logout( false ); } );

    m_A_toggle_auto_logout = Lifeograph::p->add_action_bool( "toggle_auto_logout",
            [ this ](){ handle_auto_logout_toggled(); }, false );

    m_A_show_preferences = Lifeograph::p->add_action( "preferences",
            [ this ](){ DialogPreferences::create(); } );

    Lifeograph::p->add_action( "enable_editing", [ this ](){ UI_diary->enable_editing(); } );

    Lifeograph::p->add_action( "refresh_panels", [ this ](){ refresh_panels(); } );

    Lifeograph::p->set_accel_for_action( "app.enable_editing", "F2" );
    Lifeograph::p->set_accel_for_action( "app.refresh_panels", "F5" );

    m_Bx_main->remove( *m_IB_info ); // necessary due to GNOME Bugzilla bug 710888

    m_A_show_preferences->set_enabled( ! Lifeograph::settings.rtflag_read_only );

    UI_login->handle_start();

    update_title();
}

AppWindow::~AppWindow()
{
    remove();

    if( UI_login )
        delete UI_login;

    if( Diary::d )
        delete Diary::d;
}

bool
AppWindow::on_event( GdkEvent* )
{
    if( m_auto_logout_freeze_level == 0 )
    {
        // restart
        m_seconds_remaining = Lifeograph::settings.idletime;
        m_connection_timeout.disconnect();
        m_connection_timeout = Glib::signal_timeout().connect_seconds(
                sigc::mem_fun( this, &AppWindow::handle_idle ),
                Lifeograph::settings.idletime - LOGOUT_COUNTDOWN );

        update_auto_logout_count_down();
    }

    return false;
}

bool
AppWindow::on_delete_event( GdkEventAny* )
{
    PRINT_DEBUG( "AppWindow::on_delete_event()" );

    return( Lifeograph::p->quit() != true );
}

bool
AppWindow::write_backup()
{
    PRINT_DEBUG( "AppWindow::write_backup()" );

    Result result{ Diary::d->write( Diary::d->get_path() + LOCK_SUFFIX ) };
    return( result == SUCCESS );
}

void
AppWindow::refresh_panels()
{
    if( not( Diary::d->is_in_edit_mode() ) ) return;

    UI_extra->refresh_active_panel();
    UI_diary->update_entry_list();
}

void
AppWindow::fix_paned_positions_if_needed()
{
    if( m_Pd_extra->get_position() > ( Lifeograph::settings.height * 0.9 ) )
        m_Pd_extra->set_position( Lifeograph::settings.height / 2 );
}

void
AppWindow::show_entry( Entry* entry )
{
    Lifeograph::START_INTERNAL_OPERATIONS();

    UI_entry->show( entry );
    UI_extra->set_entry( entry );
    UI_diary->show_in_list( entry );

    Lifeograph::FINISH_INTERNAL_OPERATIONS();
}

void
AppWindow::show_about()
{
    static Gtk::AboutDialog* s_about_dialog{ nullptr };

    if( s_about_dialog == nullptr )
    {
        Lifeograph::get_builder2()->get_widget( "aboutdialog", s_about_dialog );
        s_about_dialog->set_name( PROGRAM_NAME );
        s_about_dialog->set_version( Lifeograph::PROGRAM_VERSION_STRING );
        s_about_dialog->set_transient_for( *AppWindow::p );
#ifdef _WIN32
        s_about_dialog->set_logo_default();
#endif
    }

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

void
AppWindow::show_info( Gtk::MessageType type, const Ustring& text,
                      const Ustring& button_text, InfoResponse response )
{
    m_L_info->set_markup( text );
    m_B_info->set_visible( !button_text.empty() );
    m_B_info->set_label( button_text );
    m_resp_cur = response;
    m_IB_info->set_message_type( type );
    if( m_IB_info->get_parent() == nullptr )
        m_Bx_main->pack_start( *m_IB_info, Gtk::PACK_SHRINK ); // necessary due to GNOME Bugzilla bug 710888
    m_IB_info->show();
    m_flag_info_is_visible = true;
}

void
AppWindow::handle_infobar_response( int response )
{
    if( response != Gtk::RESPONSE_CLOSE )
    {
        switch( m_resp_cur )
        {
            case RESP_UNDO_REMOVE_DIARY:
                UI_login->undo_remove_selected_diary();
                break;
            default:
                break;
        }
    }

    hide_infobar();
}

inline void
AppWindow::hide_infobar()
{
    if( m_flag_info_is_visible )
    {
        m_IB_info->hide();
        m_Bx_main->remove( *m_IB_info ); // necessary due to GNOME Bugzilla bug 710888
        m_flag_info_is_visible = false;
    }
}

void
AppWindow::handle_undo()
{
    UI_entry->undo();
}

void
AppWindow::handle_redo()
{
    UI_entry->redo();
}

void
AppWindow::handle_login()
{
    if( m_Pd_main == nullptr ) // first login
    {
        try
        {
            // PANES
            Lifeograph::get_builder()->get_widget( "P_main", m_Pd_main );
            Lifeograph::get_builder()->get_widget( "P_second", m_Pd_extra );

            // ICONS
            Glib::RefPtr< Gtk::IconTheme > theme = Gtk::IconTheme::get_default();
            theme->append_search_path( Lifeograph::get_icon_dir() );

            Lifeograph::icons->entry_16 = theme->load_icon( "entry-16", 16 );
            Lifeograph::icons->entry_32 = theme->load_icon( "entry-32", 32 );
            Lifeograph::icons->entry_parent_16 = theme->load_icon( "entry_parent-16", 16 );
            Lifeograph::icons->entry_parent_32 = theme->load_icon( "entry_parent-32", 32 );
            Lifeograph::icons->chapter_16 = theme->load_icon( "chapter-16", 16 );
            Lifeograph::icons->chapter_32 = theme->load_icon( "chapter-32", 32 );

            Lifeograph::icons->tag_16 = theme->load_icon( "tag-16", 16 );
            Lifeograph::icons->tag_32 = theme->load_icon( "tag-32", 32 );

            Lifeograph::icons->favorite_16 = theme->load_icon( "favorite-16", 16 );

            Lifeograph::icons->filter_16 = theme->load_icon( "filter-16-symbolic", 16 );

            Lifeograph::icons->todo_auto_16 = theme->load_icon( "todo_auto-16", 16 );
            Lifeograph::icons->todo_auto_32 = theme->load_icon( "todo_auto-32", 32 );
            Lifeograph::icons->todo_open_16 = theme->load_icon( "todo_open-16", 16 );
            Lifeograph::icons->todo_open_32 = theme->load_icon( "todo_open-32", 32 );
            Lifeograph::icons->todo_progressed_16 = theme->load_icon( "todo_progressed-16", 16 );
            Lifeograph::icons->todo_progressed_32 = theme->load_icon( "todo_progressed-32", 32 );
            Lifeograph::icons->todo_done_16 = theme->load_icon( "todo_done-16", 16 );
            Lifeograph::icons->todo_done_32 = theme->load_icon( "todo_done-32", 32 );
            Lifeograph::icons->todo_canceled_16 = theme->load_icon( "todo_canceled-16", 16 );
            Lifeograph::icons->todo_canceled_32 = theme->load_icon( "todo_canceled-32", 32 );

            // PANELS & VIEWS
            UI_diary = new UIDiary;
            UI_extra = new UIExtra;
            UI_entry = new UIEntry;
        }
        catch( std::exception &ex )
        {
            throw LIFEO::Error( ex.what() );
        }

        // GEOMETRY
        int width, dummy;
        get_size( width, dummy );
        // try to keep the same ratio between the panels before the resizing occurs:
        if( width != Lifeograph::settings.width )
        {
            Lifeograph::settings.position_paned_main *=  float( width ) / Lifeograph::settings.width;
        }

        // check the diary panel's size against the minimum:
        if( Lifeograph::settings.position_paned_main > ( width - UIDiary::MIN_WIDTH ) )
        {
            PRINT_DEBUG( "Diary panel width set to minimum value" );
            Lifeograph::settings.position_paned_main = width - UIDiary::MIN_WIDTH;
        }

        m_Pd_main->set_position( Lifeograph::settings.position_paned_main );
        m_Pd_extra->set_position( Lifeograph::settings.position_paned_extra );

        // ACTIONS
        Lifeograph::p->add_action( "undo", [ this ](){ handle_undo(); } );
        Lifeograph::p->set_accel_for_action( "app.undo", "<Ctrl>Z" );
        Lifeograph::p->add_action( "redo", [ this ](){ handle_redo(); } );
        Lifeograph::p->set_accel_for_action( "app.redo", "<Ctrl><Shift>Z" );
    }

    m_St_main->set_visible_child( *m_Pd_main );

    update_toggle_auto_logout_gui( true );

    m_B_back->set_visible( true );
}

void
AppWindow::handle_edit_enabled()
{
    m_connection_backup = Glib::signal_timeout().connect_seconds(
            sigc::mem_fun( this, &AppWindow::write_backup ), BACKUP_INTERVAL );

    update_toggle_auto_logout_gui( true );  // must come after m_flag_editing_enabled is set
}

void
AppWindow::handle_logout()
{
    m_B_back->set_visible( false );

    if( Diary::d->is_logged_time_out() )
        show_info( Gtk::MESSAGE_INFO, _( STRING::ENTER_PASSWORD_TIMEOUT ) );
    else if( m_flag_info_is_visible )
        hide_infobar();
}

// AUTO LOGOUT SYSTEM
bool
AppWindow::handle_idle()
{
    if( m_auto_logout_freeze_level > 0 )
        return false;

    if( m_seconds_remaining > LOGOUT_COUNTDOWN )
        m_seconds_remaining = LOGOUT_COUNTDOWN;
    else
        m_seconds_remaining--;

    if( m_seconds_remaining > 0 )
    {
        m_connection_timeout = Glib::signal_timeout().connect_seconds(
                sigc::mem_fun( this, &AppWindow::handle_idle ), 1 );

        update_auto_logout_count_down();
    }
    else
    {
        update_auto_logout_count_down();

        logout( true );
        Diary::d->set_timed_out();
    }

    return false;
}

void
AppWindow::freeze_auto_logout()
{
    if( m_auto_logout_freeze_level == 0 )
    {
        m_connection_timeout.disconnect();
        m_seconds_remaining = Lifeograph::settings.idletime;
        update_auto_logout_count_down();
    }

    m_auto_logout_freeze_level++;
}

void
AppWindow::unfreeze_auto_logout()
{
    if( Lifeograph::settings.idletime > 0 && Diary::d->is_encrypted() &&
        m_auto_logout_freeze_level == 1 )
    {
        m_connection_timeout = Glib::signal_timeout().connect_seconds(
                sigc::mem_fun( this, &AppWindow::handle_idle ),
                Lifeograph::settings.idletime - LOGOUT_COUNTDOWN );
    }

    m_auto_logout_freeze_level--;
}

void
AppWindow::update_auto_logout_count_down()
{
    static Gtk::Window* W_count_down( nullptr );
    static Gtk::Label* L_count_down( nullptr );

    if( m_seconds_remaining > 0  && m_seconds_remaining <= LOGOUT_COUNTDOWN )
    {
        if( W_count_down == nullptr )
        {
            Lifeograph::get_builder()->get_widget( "W_auto_logout", W_count_down );
            Lifeograph::get_builder()->get_widget( "L_auto_logout", L_count_down );

            W_count_down->set_transient_for( *this );
        }

        L_count_down->set_label( Glib::ustring::compose(
                _( "%1 SECONDS TO LOG OUT" ), m_seconds_remaining ) );

        W_count_down->show();
    }
    else if( W_count_down )
        W_count_down->hide();
}

void
AppWindow::handle_auto_logout_toggled()
{
    if( is_auto_logout_in_use() )
        freeze_auto_logout();
    else
        unfreeze_auto_logout();

    update_toggle_auto_logout_gui(); // the action's state does not change automatically
}

void
AppWindow::update_toggle_auto_logout_gui( bool flag_availabilities_too )
{
    if( flag_availabilities_too )
    {
        m_auto_logout_freeze_level =
                ( Lifeograph::settings.idletime && Diary::d->is_encrypted() ) ? 0 : 1;

        m_A_toggle_auto_logout->set_enabled( Diary::d->is_encrypted() &&
                                             Lifeograph::settings.idletime );
        m_A_logout_wo_saving->set_enabled( Diary::d->is_in_edit_mode() );
    }

    m_A_toggle_auto_logout->change_state( ! is_auto_logout_in_use() );
}

// LOG OUT
bool
AppWindow::finish_login_session( bool opt_save )
{
    // SAVING
    Lifeograph::settings.position_paned_main = m_Pd_main->get_position();
    Lifeograph::settings.position_paned_extra = m_Pd_extra->get_position();

    // files added to recent list here if not already there
    if( ! Diary::d->get_path().empty() )
        Lifeograph::settings.recentfiles.insert( Diary::d->get_path() );

    if( Diary::d->is_in_edit_mode() )
    {
        Diary::d->set_last_entry( UI_entry->get_cur_entry() );

        if( opt_save )
        {
            if( Diary::d->write() != SUCCESS )
            {
                Gtk::MessageDialog messagedialog( *this,
                                                  "",
                                                  false,
                                                  Gtk::MESSAGE_WARNING,
                                                  Gtk::BUTTONS_OK,
                                                  true );
                messagedialog.set_message( _( STRING::CANNOT_WRITE ) );
                messagedialog.set_secondary_text( _( STRING::CANNOT_WRITE_SUB ) );
                messagedialog.run();

                return false;
            }
        }
        else
        {
            Gtk::MessageDialog messagedialog( *this,
                                              "",
                                              false,
                                              Gtk::MESSAGE_WARNING,
                                              Gtk::BUTTONS_CANCEL,
                                              true );
            messagedialog.set_message(
                _( "Are you sure you want to log out without saving?" ) );
            messagedialog.set_secondary_text( Glib::ustring::compose(
                _( "Your changes will be backed up in %1. "
                   "If you exit normally, your diary is saved automatically." ),
                "<b>" + Diary::d->get_path() + ".~unsaved~</b>" ), true );
            messagedialog.add_button( _( "_Log out without Saving" ), Gtk::RESPONSE_ACCEPT );

            if( messagedialog.run() != Gtk::RESPONSE_ACCEPT )
                return false;
            // back up changes
            Diary::d->write( Diary::d->get_path() + ".~unsaved~" );
        }

        m_connection_backup.disconnect();
    }

    m_connection_timeout.disconnect();

    Lifeograph::signal_logout().emit(); // only for DialogEvent

    Lifeograph::START_INTERNAL_OPERATIONS();
    UI_diary->handle_logout();
    UI_entry->handle_logout();
    UI_extra->handle_logout();

    Diary::d->remove_lock_if_necessary();
    Diary::d->clear();
    Lifeograph::FINISH_INTERNAL_OPERATIONS();

    return true;
}

void
AppWindow::logout( bool opt_save )
{
    // should be reset to prevent logging in again:
    Lifeograph::settings.rtflag_open_directly = false;
    if( finish_login_session( opt_save ) )
    {
        this->handle_logout();
        UI_login->handle_logout();
        m_St_main->set_visible_child( "login" );
    }

    m_auto_logout_freeze_level = 1;

    update_title();
}

bool
AppWindow::confirm_dismiss_element( const Ustring& name, Gtk::Widget* extra_widget )
{
    Gtk::MessageDialog message( *this, "", false,
                                Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE, true );
    message.set_message(
            Glib::ustring::compose( _( "Are you sure, you want to dismiss %1?" ),
                                    Glib::ustring::compose( "\"%1\"", name ) ) );
    message.set_secondary_text( _( "This operation cannot be undone!" ) );
    message.add_button( _( "_Cancel" ), Gtk::RESPONSE_CANCEL );
    message.add_button( _( "_DISMISS!" ), Gtk::RESPONSE_ACCEPT );

    if( extra_widget )
    {
        extra_widget->show();
        message.get_message_area()->add( *extra_widget );
    }

    if( message.run() != Gtk::RESPONSE_ACCEPT )
        return false;
    else
        return true;
}

void
AppWindow::update_title()
{
    set_title( Diary::d->is_open() ?
               STR::compose( PROGRAM_NAME, " - ", Diary::d->get_name() ) : PROGRAM_NAME );

    static Gtk::Label label;
    if( Diary::d->is_open() ) // just hide the title
        m_Hb_main->set_custom_title( label );
    else // remove custom widget to show the title
        m_Hb_main->property_custom_title() = nullptr;
}

void
AppWindow::login()
{
    Lifeograph::START_INTERNAL_OPERATIONS();

    this->handle_login();       // must come first
    UI_login->handle_login();
    UI_diary->handle_login();
    UI_extra->handle_login();
    UI_entry->handle_login();   // must come last

    show_entry( Diary::d->get_startup_entry() );

    Lifeograph::FINISH_INTERNAL_OPERATIONS();

    // NOTE: LOGGED_IN flag used to be set here, now it is set much earlier

    update_title();
}
