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

    Copyright (C) 2007-2018 Ahmet Öztürk (aoz_2@yahoo.com)

    This file is part of Lifeograph.

    Lifeograph is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Lifeograph is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Lifeograph.  If not, see <http://www.gnu.org/licenses/>.

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


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

#include <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();

    UndoManager::m = new UndoManager;
    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
    {
        Lifeograph::get_builder()->get_widget( "Hb_main", m_Hb_main );
        Lifeograph::get_builder()->get_widget( "St_main", m_St_main );
        m_view_login = new ViewLogin;
    }
    catch( ... )
    {
        throw LIFEO::Error( "Failed to create the login view" );
    }

    m_A_logout_wo_saving = Lifeograph::p->add_action( "logout_wo_saving",
            sigc::bind( sigc::mem_fun( *this, &AppWindow::logout ), false ) );

    m_A_toggle_auto_logout = Lifeograph::p->add_action_bool( "toggle_auto_logout",
            sigc::mem_fun( *this, &AppWindow::handle_auto_logout_toggled ), false );

    m_view_login->handle_start();
}

AppWindow::~AppWindow()
{
    remove();
    delete UndoManager::m;

    if( m_view_login )
        delete m_view_login;

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

bool
AppWindow::on_event( GdkEvent* )
{
    if( !m_auto_logout_status )
    {
        // 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::quit() != true );
}

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

    m_entry_view->sync();

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

void
AppWindow::show_about()
{
    if( m_about_dialog == nullptr )
    {
        Lifeograph::get_builder2()->get_widget( "aboutdialog", m_about_dialog );
        m_about_dialog->set_name( PROGRAM_NAME );
        m_about_dialog->set_version( Lifeograph::PROGRAM_VERSION_STRING );
        m_about_dialog->set_transient_for( *this );
    }

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

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

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

void
AppWindow::handle_login()
{
    // the following must come before handle_login()s
    m_auto_logout_status = ( Lifeograph::settings.autologout && Diary::d->is_encrypted() ) ? 0 : 1;

    Lifeograph::m_actions_read->set_sensitive( true );
    Lifeograph::m_actions_edit->set_visible( Diary::d->is_read_only() == false );
    Lifeograph::m_actions_edit->set_sensitive( Diary::d->is_read_only() == false );

    m_St_main->set_visible_child( *m_paned_main );

    update_toggle_auto_logout_gui( true );
    update_title();
}

// AUTO LOGOUT SYSTEM
bool
AppWindow::handle_idle()
{
    if( m_auto_logout_status )
        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();

        Lifeograph::loginstatus = Lifeograph::LOGGED_TIME_OUT;
        logout( true );
    }

    return false;
}

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

    m_auto_logout_status++;
}

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

    m_auto_logout_status--;
}

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_A_toggle_auto_logout->set_enabled( Diary::d->is_encrypted() &&
                                             Lifeograph::settings.autologout );
        m_A_logout_wo_saving->set_enabled( ! Diary::d->is_read_only() );
    }

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

// LOG OUT
bool
AppWindow::finish_editing( bool opt_save )
{
    // SAVING
    Lifeograph::settings.position_pane = m_paned_main->get_position();
    Lifeograph::settings.position_pane_tags = m_paned_entry->get_position();

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

    if( ! Diary::d->is_read_only() )
    {
        m_entry_view->sync();
        Diary::d->set_last_elem( panel_main->get_cur_elem() );

        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_timeout.disconnect();
    if( ! Diary::d->is_read_only() )
        m_connection_backup.disconnect();

    // CLEARING
    // TODO: m_loginstatus = LOGGING_OUT_IN_PROGRESS;

    Lifeograph::m_actions_read->set_sensitive( false );

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

    Lifeograph::s_internaloperation++;
    panel_main->handle_logout();
    panel_diary->handle_logout();
    m_entry_view->handle_logout();
    panel_extra->handle_logout();

    if( Lifeograph::loginstatus == Lifeograph::LOGGED_IN )
        Lifeograph::loginstatus = Lifeograph::LOGGED_OUT;
    Diary::d->clear();
    Lifeograph::s_internaloperation--;

    return true;
}

void
AppWindow::logout( bool opt_save )
{
    panel_main->hide_popover(); // hide if is visible. necessary when logging out without saving

    Lifeograph::p->m_flag_open_directly = false;   // should be reset to prevent logging in again
    if( finish_editing( opt_save ) )
    {
        m_view_login->handle_logout();
        m_St_main->set_visible_child( "login" );
    }

    m_auto_logout_status = 1;
}

bool
AppWindow::confirm_dismiss_element( const DiaryElement* elem, 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\"", elem->get_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()
{
    Glib::ustring title( PROGRAM_NAME );

    if( Lifeograph::loginstatus == Lifeograph::LOGGED_IN )
    {
        title += " - ";
        title += Diary::d->get_name();
    }

    set_title( title );
}

void
AppWindow::login()
{
    if( m_paned_main == nullptr )
    {
        try
        {
            // PANES
            Lifeograph::get_builder()->get_widget( "P_main", m_paned_main );
            Lifeograph::get_builder()->get_widget( "P_second", m_paned_entry );

            // TAG PANEL
            Lifeograph::get_builder()->get_widget_derived( "TV_all_tags", panel_extra );

            // ICONS
            std::string icon_dir( Lifeograph::ICONDIR );
            if( ! Lifeograph::settings.icon_theme.empty() )
            {
                icon_dir += "/";
                icon_dir += Lifeograph::settings.icon_theme;
            }

            Glib::RefPtr< Gtk::IconTheme > theme = Gtk::IconTheme::get_default();
            theme->append_search_path( icon_dir );

            Lifeograph::icons->diary_16 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/diary-16.png" );
            Lifeograph::icons->diary_32 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/diary-32.png" );
            Lifeograph::icons->entry_16 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/entry-16.png" );
            Lifeograph::icons->entry_32 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/entry-32.png" );
            Lifeograph::icons->favorite_16 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/favorite-16.png" );

            Lifeograph::icons->tag_16 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/tag-16.png" );
            Lifeograph::icons->tag_32 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/tag-32.png" );
            Lifeograph::icons->tag_theme_16 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/tag_theme-16.png" );
            Lifeograph::icons->tag_theme_32 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/tag_theme-32.png" );
            Lifeograph::icons->untagged_16 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/untagged-16.png" );
            Lifeograph::icons->untagged_32 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/untagged-32.png" );
            Lifeograph::icons->tag_category_16 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/tag_category-16.png" );
            Lifeograph::icons->tag_category_32 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/tag_category-32.png" );

            Lifeograph::icons->chapter_free_16 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/chapter_free-16.png" );
            Lifeograph::icons->chapter_free_32 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/chapter_free-32.png" );

            Lifeograph::icons->chapter_temp_16 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/chapter_temp-16.png" );
            Lifeograph::icons->chapter_temp_32 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/chapter_temp-32.png" );

            Lifeograph::icons->chapter_ordr_16 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/chapter_ordr-16.png" );
            Lifeograph::icons->chapter_ordr_32 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/chapter_ordr-32.png" );

            Gtk::IconInfo ii = theme->lookup_icon( "filter-16-symbolic", 16,
                                                   Gtk::IconLookupFlags( 0 ) );
            bool res;
            Lifeograph::icons->filter_16 = ii.load_symbolic( m_paned_main->get_style_context(),
                                                             res );

            Lifeograph::icons->bullet_16 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/bullet-16.png" );
            Lifeograph::icons->todo_auto_32 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/todo_auto-32.png" );
            Lifeograph::icons->todo_open_16 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/todo_open-16.png" );
            Lifeograph::icons->todo_open_32 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/todo_open-32.png" );
            Lifeograph::icons->todo_progressed_16 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/todo_progressed-16.png" );
            Lifeograph::icons->todo_progressed_32 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/todo_progressed-32.png" );
            Lifeograph::icons->todo_done_16 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/todo_done-16.png" );
            Lifeograph::icons->todo_done_32 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/todo_done-32.png" );
            Lifeograph::icons->todo_canceled_16 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/todo_canceled-16.png" );
            Lifeograph::icons->todo_canceled_32 =
                    Gdk::Pixbuf::create_from_file( icon_dir + "/todo_canceled-32.png" );

            // PANELS & VIEWS
            m_filter_view = new FilterView; // this is needed by PanelDiary
            panel_main = new PanelMain;
            panel_diary = new PanelDiary;
            m_diary_view = new DiaryView;
            m_ctg_tags_view = new CategoryTagsView;
            m_chapter_view = new ChapterView;
            m_entry_view = new EntryView;
            m_tag_view = new TagView;
        }
        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_pane *=  float( width ) / Lifeograph::settings.width;
        }

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

        m_paned_main->set_position( Lifeograph::settings.position_pane );
        m_paned_entry->set_position( Lifeograph::settings.position_pane_tags );

        // ACTIONS
        Lifeograph::create_action( true, m_action_undo,"Undo",
                       _( "Undo" ), "",     // this tooltip will be dynamic
                       Gtk::AccelKey( GDK_KEY_z, Gdk::CONTROL_MASK ),
                       sigc::mem_fun( *this, &AppWindow::handle_undo ) );

        Lifeograph::create_action( true, m_action_redo, "Redo",
                       _( "Redo" ), "",     // this tooltip will be dynamic
                       Gtk::AccelKey( GDK_KEY_z, Gdk::CONTROL_MASK | Gdk::SHIFT_MASK ),
                       sigc::mem_fun( *this, &AppWindow::handle_redo ) );
    }

    if( ! Diary::d->is_read_only() )
        m_connection_backup = Glib::signal_timeout().connect_seconds(
                sigc::mem_fun( this, &AppWindow::write_backup ), BACKUP_INTERVAL );

    // LOGIN HANDLERS
    Lifeograph::s_internaloperation++;

    this->handle_login();   // must come first
    panel_main->handle_login(); // must come before m_diary_view->handle_login() for header bar order
    m_view_login->handle_login();
    m_tag_view->handle_login();
    m_filter_view->handle_login();
    m_entry_view->handle_login();
    m_diary_view->handle_login();
    m_chapter_view->handle_login();
    panel_diary->handle_login();
    panel_extra->handle_login();

    Lifeograph::s_internaloperation--;

    Lifeograph::loginstatus = Lifeograph::LOGGED_IN;
}
