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

    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 <sys/stat.h>
#include <utime.h>

#include "helpers.hpp"
#include "lifeograph.hpp"
#include "app_window.hpp"
#include "ui_login.hpp"
#include "dialog_password.hpp"


using namespace LIFEO;
using namespace HELPERS;


UILogin::Colrec*    UILogin::colrec;
std::string         UILogin::m_path_cur;


UILogin::UILogin()
{
    Gtk::TreeView::Column* col_icon;
    Gtk::TreeView::Column* col_diary;
    Gtk::TreeView::Column* col_date;

    try
    {
        colrec = new Colrec;

        auto builder{ Lifeograph::get_builder() };
        builder->get_widget( "Bx_login", m_Bx_login );
        builder->get_widget( "TV_diaries", m_TV_diaries );
        builder->get_widget( "B_login_select", m_B_select );
        builder->get_widget( "B_login_new_diary", m_B_new );
        builder->get_widget( "B_login_open_diary", m_B_open );
        builder->get_widget( "B_login_remove", m_B_remove_diary );

        builder->get_widget( "Bx_diaries_sort_by", m_Bx_diaries_sort_by );
        builder->get_widget( "RB_sort_diaries_name", m_RB_sort_by_name );
        builder->get_widget( "RB_sort_diaries_save", m_RB_sort_by_save );
        builder->get_widget( "RB_sort_diaries_read", m_RB_sort_by_read );

        col_icon = Gtk::manage( new Gtk::TreeView::Column( "", colrec->icon ) );
        col_diary = Gtk::manage( new Gtk::TreeView::Column( _( "Name" ), colrec->name ) );
        col_date = Gtk::manage( new Gtk::TreeView::Column( _( STRING::COLHEAD_BY_LAST_SAVE ),
                                                           colrec->date ) );

        m_LS_diaries = Gtk::ListStore::create( *colrec );
        m_treesel_diaries = m_TV_diaries->get_selection();
    }
    catch( ... )
    {
        throw LIFEO::Error( "Failed to create the login view" );
    }

    m_RB_sort_by_save->set_active();

    // LIST OF DIARIES
    m_TV_diaries->set_model( m_LS_diaries );
    col_diary->set_expand( true );
    m_TV_diaries->append_column( *col_icon );
    m_TV_diaries->append_column( *col_diary );
    m_TV_diaries->append_column( *col_date );
    m_LS_diaries->set_default_sort_func( sigc::mem_fun( this, &UILogin::sort_by_date ) );
    m_LS_diaries->set_sort_column( Gtk::ListStore::DEFAULT_SORT_COLUMN_ID, Gtk::SORT_DESCENDING );
    m_TV_diaries->set_has_tooltip( true );
    m_TV_diaries->drag_dest_set( Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_COPY );
    m_TV_diaries->drag_dest_add_uri_targets();

    // STYLE
    Glib::RefPtr< Gtk::CssProvider > css = Gtk::CssProvider::create();
    if( css->load_from_data(
            "treeview#diary_list { background-color: transparent } "
            "treeview#diary_list:selected { color: #bb3333; background-color: #dddddd } "
            "treeview#diary_list header button { border-color: transparent; "
                                                "background: transparent }" ) )
    Gtk::StyleContext::add_provider_for_screen(
            Gdk::Screen::get_default(), css, GTK_STYLE_PROVIDER_PRIORITY_USER );

    m_TV_diaries->get_column( 1 )->set_cell_data_func(
            * m_TV_diaries->get_column_cell_renderer( 1 ),
            []( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter )
            {
                std::string&& path{ ( * iter )[ colrec->path ] };
                Gtk::CellRendererText* crt{ dynamic_cast< Gtk::CellRendererText* >( cell ) };

                if( path == m_path_cur )
                {
                    cell->property_cell_background_rgba() = Gdk::RGBA( "#dddddd" );
                    crt->property_weight() = Pango::WEIGHT_BOLD;
                    crt->property_foreground_rgba() = Gdk::RGBA( "#bb3333" );
                }
                else
                {
                    cell->property_cell_background_rgba() = Gdk::RGBA( "transparent" );
                    crt->property_weight() = Pango::WEIGHT_NORMAL;
                    crt->property_foreground_set() = false;
                }
            } );

    // SIGNALS
    m_B_select->signal_toggled().connect(
            sigc::mem_fun( this, &UILogin::handle_select_mode_toggled ) );

    m_B_remove_diary->signal_clicked().connect(
            sigc::mem_fun( this, &UILogin::remove_selected_diary ) );

    m_treesel_diaries->signal_changed().connect(
            sigc::mem_fun( this, &UILogin::handle_diary_selection_changed ) );

    m_TV_diaries->signal_row_activated().connect_notify(
            sigc::mem_fun( this, &UILogin::handle_diary_activated ) );
    m_TV_diaries->signal_button_release_event().connect_notify(
            sigc::mem_fun( this, &UILogin::handle_button_release ) );
    m_TV_diaries->signal_key_release_event().connect_notify(
            sigc::mem_fun( this, &UILogin::handle_key_release ) );
    m_TV_diaries->signal_query_tooltip().connect(
            sigc::mem_fun( this, &UILogin::handle_diary_tooltip ) );
    m_TV_diaries->signal_drag_data_received().connect(
            sigc::mem_fun( this, &UILogin::handle_dragged_file ) );

    if( Lifeograph::settings.rtflag_read_only )
        m_B_new->set_visible( false );
    else
        m_B_new->signal_clicked().connect( sigc::mem_fun( this, &UILogin::create_new_diary ) );

    m_B_open->signal_clicked().connect( sigc::mem_fun( this, &UILogin::add_existing_diary ) );

    m_RB_sort_by_name->signal_toggled().connect( [ this ](){ refresh_sort_type(); } );
    m_RB_sort_by_save->signal_toggled().connect( [ this ](){ refresh_sort_type(); } );
    // the 3rd radiobutton is not necessary as untoggle signal takes care of that
}

void
UILogin::handle_start()
{
    // ICONS
    std::string&& icon_dir{ Lifeograph::get_icon_dir() };
    Lifeograph::icons->diary_32 =
            Gdk::Pixbuf::create_from_file( icon_dir + "/diary-32.png" );

    if( Lifeograph::settings.rtflag_force_welcome )
    {
        Gtk::Button* B_new_diary;
        Lifeograph::get_builder2()->get_widget( "G_welcome", m_G_welcome );
        Lifeograph::get_builder2()->get_widget( "B_welcome_new_diary", B_new_diary );

        m_Bx_login->pack_start( *m_G_welcome, Gtk::PACK_SHRINK );

        B_new_diary->signal_clicked().connect(
                sigc::mem_fun( this, &UILogin::create_new_diary ) );
    }

    if( Lifeograph::settings.rtflag_open_directly )
        open_selected_diary();

    if( not( Diary::d->is_open() ) ) // i.e. open directly was not successful
        initialize();
}

void
UILogin::initialize()
{
    m_B_new->set_visible( Lifeograph::settings.rtflag_read_only == false );
    m_B_open->set_visible( Lifeograph::settings.rtflag_read_only == false );
    m_B_select->set_visible( Lifeograph::settings.rtflag_read_only == false );

    populate_diaries();
}

void
UILogin::handle_login()
{
    m_B_select->set_active( false );
    m_B_select->set_visible( false );
    m_B_new->set_visible( false );
    m_B_open->set_visible( false );

    m_Bx_diaries_sort_by->set_visible( false );
}

void
UILogin::handle_logout()
{
    initialize();

    m_Bx_diaries_sort_by->set_visible( true );

    if( Lifeograph::settings.rtflag_force_welcome )
    {
        m_Bx_login->remove( *m_G_welcome );
        Lifeograph::settings.rtflag_force_welcome = false;
    }
}

void
UILogin::populate_diaries()
{
    Gtk::TreeModel::Row row;

    struct stat fst;    // file date stat

    m_LS_diaries->clear();
    for( SetStrings::reverse_iterator iter = Lifeograph::settings.recentfiles.rbegin();
         iter != Lifeograph::settings.recentfiles.rend();
         ++iter )
    {
        row = *( m_LS_diaries->append() );
        row[ colrec->icon ] = Lifeograph::icons->diary_32;
        row[ colrec->name ] = Glib::filename_display_basename( *iter );
        row[ colrec->path ] = *iter;

        if( stat( PATH( *iter ).c_str(), &fst ) == 0 )
        {
            time_t file_time( m_sort_type == DST_READ ? fst.st_atime : fst.st_mtime );
            row[ colrec->date ] = Date::format_string_dt( file_time );
            row[ colrec->date_sort ] = file_time;
        }
        else
        {
            row[ colrec->date ] = "XXXXX";
            row[ colrec->date_sort ] = 0;
        }
    }

    if( m_LS_diaries->children().size() > 0 )
        m_treesel_diaries->select( m_LS_diaries->get_iter( "0" ) );
}

void
UILogin::refresh_sort_type()
{
    if( m_RB_sort_by_name->get_active() )
    {
        m_sort_type = DST_NAME;
        m_TV_diaries->get_column( 2 )->set_title( _( STRING::COLHEAD_BY_LAST_SAVE ) );
        m_LS_diaries->set_default_sort_func( sigc::mem_fun( this, &UILogin::sort_by_name ) );
    }
    else if( m_RB_sort_by_save->get_active() )
    {
        m_sort_type = DST_SAVE;
        m_TV_diaries->get_column( 2 )->set_title( _( STRING::COLHEAD_BY_LAST_SAVE ) );
        m_LS_diaries->set_default_sort_func( sigc::mem_fun( this, &UILogin::sort_by_date ) );
    }
    else
    {
        m_sort_type = DST_READ;
        m_TV_diaries->get_column( 2 )->set_title( _( STRING::COLHEAD_BY_LAST_ACCESS ) );
        m_LS_diaries->set_default_sort_func( sigc::mem_fun( this, &UILogin::sort_by_date ) );
    }

    populate_diaries();
}

int
UILogin::sort_by_date( const Gtk::TreeModel::iterator& itr1, const Gtk::TreeModel::iterator& itr2 )
{
    // SORT BY DATE (ONLY DESCENDINGLY FOR NOW)
    const time_t item1 = ( *itr1 )[ colrec->date_sort ];
    const time_t item2 = ( *itr2 )[ colrec->date_sort ];

    int direction( -1 );

    if( item1 > item2 )
        return( -1 * direction );
    else
    if( item1 < item2 )
        return( 1 * direction );
    else
        return 0;
}

int
UILogin::sort_by_name( const Gtk::TreeModel::iterator& itr1, const Gtk::TreeModel::iterator& itr2 )
{
    // SORT BY DATE (ONLY DESCENDINGLY FOR NOW)
    const Glib::ustring item1 = ( *itr1 )[ colrec->name ];
    const Glib::ustring item2 = ( *itr2 )[ colrec->name ];

    int direction( 1 );

    if( item1 > item2 )
        return( -1 * direction );
    else
    if( item1 < item2 )
        return( 1 * direction );
    else
        return 0;
}

void
UILogin::open_selected_diary()
{
    // BEWARE: clear the diary before returning unless successful

    // SET PATH
    Result result( Diary::d->set_path( m_path_cur,
                                       Lifeograph::settings.rtflag_read_only ?
                                            Diary::SPT_READ_ONLY : Diary::SPT_NORMAL) );
    switch( result )
    {
        case LIFEO::SUCCESS:
            break;
        case FILE_NOT_FOUND:
            AppWindow::p->show_info( Gtk::MESSAGE_INFO, _( STRING::DIARY_NOT_FOUND ) );
            break;
        case FILE_NOT_READABLE:
            AppWindow::p->show_info( Gtk::MESSAGE_INFO, _( STRING::DIARY_NOT_READABLE ) );
            break;
        default:
            AppWindow::p->show_info( Gtk::MESSAGE_ERROR, _( STRING::FAILED_TO_OPEN_DIARY ) );
            break;
    }
    if( result != LIFEO::SUCCESS )
    {
        Diary::d->clear();
        populate_diaries();
        return;
    }

    // FORCE ACCESS TIME UPDATE
    // for performance reasons atime update policy on linux is usually once per day. see: relatime
#ifndef _WIN32
    struct stat fst;    // file date stat
    struct utimbuf utb;
    stat( m_path_cur.c_str(), &fst );
    utb.actime = time( NULL );
    utb.modtime = fst.st_mtime;
    utime( m_path_cur.c_str(), &utb );
#endif

    // READ HEADER
    switch( result = Diary::d->read_header() )
    {
        case LIFEO::SUCCESS:
            break;
        case INCOMPATIBLE_FILE_OLD:
            AppWindow::p->show_info( Gtk::MESSAGE_ERROR, _( STRING::INCOMPATIBLE_DIARY_OLD ) );
            break;
        case INCOMPATIBLE_FILE_NEW:
            AppWindow::p->show_info( Gtk::MESSAGE_ERROR, _( STRING::INCOMPATIBLE_DIARY_NEW ) );
            break;
        case CORRUPT_FILE:
            AppWindow::p->show_info( Gtk::MESSAGE_ERROR, _( STRING::CORRUPT_DIARY ) );
            break;
        default:
            AppWindow::p->show_info( Gtk::MESSAGE_ERROR, _( STRING::FAILED_TO_OPEN_DIARY ) );
            break;
    }
    if( result != LIFEO::SUCCESS )
    {
        Diary::d->clear();
        populate_diaries();
        return;
    }

    // HANDLE ENCRYPTION
    if( Diary::d->is_encrypted() )
    {
        Gtk::TreePath path;
        m_LS_diaries->foreach_iter(
                [ & ]( const Gtk::TreeIter& i )
                {
                    std::string&& sp{ ( *i )[ colrec->path ] };
                    if( sp == m_path_cur )
                    {
                        path = m_LS_diaries->get_path( i );
                        return true;
                    }
                    return false;
                } );
        Gdk::Rectangle rect;
        m_TV_diaries->get_cell_area( path, * m_TV_diaries->get_column( 1 ), rect );
        int x, y;
        m_TV_diaries->convert_tree_to_widget_coords( rect.get_x(), rect.get_y(), x, y );
        rect.set_x( x );
        rect.set_y( y );

        DialogPassword::launch( DialogPassword::OT_OPEN, Diary::d,
                &rect, m_TV_diaries,
                sigc::mem_fun( this, &UILogin::open_selected_diary2 ),
                []{ } );
    }
    else
        open_selected_diary2();
}

void
UILogin::open_selected_diary2()
{
    // FINALLY READ BODY
    switch( Diary::d->read_body() )
    {
        case LIFEO::SUCCESS:
            DialogPassword::finish( Diary::d->get_path() );
            AppWindow::p->login();
            break;
        case WRONG_PASSWORD:
            open_selected_diary();
            break;
        case CORRUPT_FILE:
            AppWindow::p->show_info( Gtk::MESSAGE_ERROR, _( STRING::CORRUPT_DIARY ) );
            // no break
        default:
            DialogPassword::finish( "" );   // "" denotes unsuccessful finish
            break;
    }
}

void
UILogin::remove_selected_diary()
{
    if( m_treesel_diaries->count_selected_rows() == 1 )
    {
        m_path_removed = ( *m_treesel_diaries->get_selected() )[ colrec->path ];
        Lifeograph::settings.recentfiles.erase( m_path_removed );
        populate_diaries();
        AppWindow::p->show_info(
                Gtk::MESSAGE_INFO,
                Glib::ustring::compose( _( "Removed diary: %1" ), m_path_removed ),
                _( "Undo" ), AppWindow::RESP_UNDO_REMOVE_DIARY );
    }
}

void
UILogin::undo_remove_selected_diary()
{
    Lifeograph::settings.recentfiles.insert( m_path_removed );
    populate_diaries();
}

void
UILogin::create_new_diary()
{
    std::string path( Glib::get_home_dir() + "/new diary.diary" );
    std::string pass( "" );

    if( DialogSaveDiary::launch( path, pass ) == Gtk::RESPONSE_ACCEPT )
    {
        switch( Diary::d->init_new( path, pass ) )
        {
            case LIFEO::SUCCESS:
                AppWindow::p->login();
                AppWindow::p->UI_diary->enable_editing();
                break;
            case LIFEO::FILE_LOCKED:
                AppWindow::p->show_info( Gtk::MESSAGE_INFO,
                           Glib::ustring::compose( _( STRING::DIARY_LOCKED ),
                                   Glib::ustring::compose(
                                           "<a href=\"file://%1\" title=\"%2\">%3</a>",
                                           Glib::path_get_dirname( m_path_cur ),
                                           _( "Click to open the containing folder" ),
                                           m_path_cur + LOCK_SUFFIX ) ) );
                break;
            default:
                AppWindow::p->show_info( Gtk::MESSAGE_ERROR, _( STRING::FAILED_TO_OPEN_DIARY ) );
                break;
        }
    }
}

void
UILogin::add_existing_diary()
{
    DialogOpenDiary* filechooserdialog = new DialogOpenDiary;

    if( m_treesel_diaries->count_selected_rows() > 0 )
        filechooserdialog->set_current_folder( Glib::path_get_dirname( m_path_cur ) );
    else
        filechooserdialog->set_current_folder( Glib::get_home_dir() );

    int result( filechooserdialog->run() );

    if( result == Gtk::RESPONSE_OK )
    {
        Lifeograph::settings.recentfiles.insert( filechooserdialog->get_filename() );
        delete filechooserdialog;
        populate_diaries();
    }
    else
        delete filechooserdialog;
}

void
UILogin::handle_select_mode_toggled()
{
    if( m_B_select->get_active() )
    {
        AppWindow::p->enter_selection_mode();
        //m_TV_diaries->get_selection()->set_mode( Gtk::SELECTION_MULTIPLE );
        m_TV_diaries->set_hover_selection( false );
        m_B_remove_diary->set_visible( true );
    }
    else
    {
        AppWindow::p->exit_selection_mode();
        //m_TV_diaries->get_selection()->set_mode( Gtk::SELECTION_SINGLE );
        m_TV_diaries->set_hover_selection( true );
        m_B_remove_diary->set_visible( false );
    }
    populate_diaries();
}

void
UILogin::handle_diary_selection_changed()
{
    //m_flag_diary_activated = false;
}

void
UILogin::handle_diary_activated( const Gtk::TreePath &path, Gtk::TreeView::Column* col )
{
    // not handled directly to prevent BUTTON RELEASE event to be sent to edit screen widgets

    if( ! m_B_select->get_active() )
    {
        //m_flag_diary_activated = true;
        m_path_cur = ( *m_treesel_diaries->get_selected() )[ colrec->path ];
        open_selected_diary();
    }
}

void
UILogin::handle_button_release( GdkEventButton* )
{
//    if( m_flag_diary_activated )
//        open_selected_diary();
}

void
UILogin::handle_key_release( GdkEventKey* event )
{
//    if( m_flag_diary_activated && event->keyval == GDK_KEY_Return )
//        open_selected_diary();
}

bool
UILogin::handle_diary_tooltip( int x, int y, bool, const Glib::RefPtr<Gtk::Tooltip>& tooltip )
{
    Gtk::TreeModel::Path path;
    Gtk::TreeView::Column* column;
    int cell_x, cell_y;
    int bx, by;
    m_TV_diaries->convert_widget_to_bin_window_coords( x, y, bx, by );
    if( ! m_TV_diaries->get_path_at_pos( bx, by, path, column, cell_x, cell_y ) )
        return false;
    Gtk::TreeIter iter( m_TV_diaries->get_model()->get_iter( path ) );
    if( !iter )
        return false;
    Gtk::TreeRow row = *( iter );
    Glib::ustring tooltip_string( row[ colrec->path ] );
    tooltip->set_text( tooltip_string );
    m_TV_diaries->set_tooltip_row( tooltip, path );
    return true;
}

void
UILogin::handle_dragged_file( const Glib::RefPtr<Gdk::DragContext>& context,
                                int, int,
                                const Gtk::SelectionData& seldata,
                                guint info, guint time )
{
    if( seldata.get_length() < 0 )
        return;

    Glib::ustring uri = seldata.get_data_as_string();
    std::string filename = uri.substr( 0, uri.find('\n') - 1 );
    filename = Glib::filename_from_uri( filename );

    if( Glib::file_test( filename, Glib::FILE_TEST_IS_DIR ) )
        return;

    if( Lifeograph::settings.recentfiles.find( filename ) != Lifeograph::settings.recentfiles.end() )
        return;

    Gtk::TreeRow row = *( m_LS_diaries->append() );
    row[ colrec->name ] = "   +++   " + Glib::filename_display_basename( filename );
    row[ colrec->path ] = filename;
    m_TV_diaries->get_selection()->select( row );
    m_TV_diaries->scroll_to_row(  m_LS_diaries->get_path( row ), 0.05 );

    Lifeograph::settings.recentfiles.insert( filename );

    PRINT_DEBUG( Glib::ustring::compose( "dropped: %1", filename ) );

    context->drag_finish( true, false, time );
}
