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

    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 "lifeograph.hpp"
#include "app_window.hpp"
#include "view_entry.hpp"
#include "panel_extra.hpp"


using namespace LIFEO;

// PSEUDO DIARYELEMENT CLASS FOR HEADERS ===========================================================
class ListHeader : public DiaryElement
{
    public:
                                    ListHeader( DiaryElement::Type type ) : m_type( type ) {}

        void                        show() {}

        int                         get_size() const
        { return 0; }   // redundant
        DiaryElement::Type          get_type() const
        { return m_type; }

    protected:
        DiaryElement::Type          m_type;
};

static ListHeader s_header( DiaryElement::ET_HEADER ), s_none( DiaryElement::ET_NONE );

// TAG PANEL =======================================================================================
PanelExtra::PanelExtra( BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& )
:   Gtk::TreeView( cobject ), m_treestore( nullptr )
{
    Gtk::CellRendererPixbuf* cellr_icon( nullptr );
    Gtk::CellRendererPixbuf* cellr_filter( nullptr );
    Gtk::CellRendererText* cellr_name( nullptr );
    Gtk::TreeViewColumn* column( nullptr );

    try
    {
        cellr_icon = Gtk::manage( new Gtk::CellRendererPixbuf );
        cellr_filter = Gtk::manage( new Gtk::CellRendererPixbuf );
        cellr_name = Gtk::manage( new Gtk::CellRendererText );
        column = Gtk::manage( new Gtk::TreeViewColumn( "" ) );
    }
    catch( ... )
    {
        throw LIFEO::Error( "Failed to create the extra panel" );
    }

    m_treestore = TreeStoreExtra::create();

    cellr_name->property_ellipsize() = Pango::ELLIPSIZE_END;

    column->pack_start( *cellr_icon, false );
    column->pack_start( *cellr_name );
    column->pack_start( *cellr_filter, false );

    column->add_attribute( cellr_icon->property_pixbuf(), m_treestore->colrec.icon );
    column->add_attribute( cellr_name->property_markup(), m_treestore->colrec.name );
    column->add_attribute( cellr_filter->property_pixbuf(), m_treestore->colrec.filter );


    // ALL TAGS
    this->set_model( m_treestore );
    column->set_cell_data_func( *cellr_icon,
                                sigc::mem_fun( this, &PanelExtra::cell_data_func_icon ) );
    column->set_cell_data_func( *cellr_name,
                                sigc::mem_fun( this, &PanelExtra::cell_data_func_text ) );
    column->set_cell_data_func( *cellr_filter,
                                sigc::mem_fun( this, &PanelExtra::cell_data_func_filter ) );

    //column->set_sizing( Gtk::TREE_VIEW_COLUMN_FIXED );

    this->append_column( *column );

    // STYLE
    Glib::ustring data;
    if( gtk_get_minor_version() >= 20 )
        data = "treeview { background-color: transparent } "
               "treeview:selected { color: #bb3333; "
                                   "background-color: #dddddd }";
    else
        data = "GtkTreeView { background-color: transparent } "
               "GtkTreeView:Selected { color: #bb3333; "
                                      "background-color: #dddddd }";

    Glib::RefPtr< Gtk::CssProvider > css = Gtk::CssProvider::create();
    if( css->load_from_data( data ) )
        this->get_style_context()->add_provider( css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION );

    // SIGNALS
    this->signal_row_activated().connect(
            sigc::mem_fun( this, &PanelExtra::handle_treeview_row_activated ) );

    this->signal_row_expanded().connect(
            sigc::mem_fun( this, &PanelExtra::handle_treeview_row_expanded ) );
    this->signal_row_collapsed().connect(
            sigc::mem_fun( this, &PanelExtra::handle_treeview_row_expanded ) );
}

void
PanelExtra::handle_login()
{
    if( Diary::d->is_read_only() )
    {
        unset_rows_drag_source();
        unset_rows_drag_dest();
    }
    else
    {
        enable_model_drag_source( Lifeograph::p->drag_targets_tag );
        enable_model_drag_dest( Lifeograph::p->drag_targets_tag, Gdk::ACTION_MOVE );
        enable_model_drag_dest( Lifeograph::p->drag_targets_entry, Gdk::ACTION_COPY );
    }

    populate();
}

void
PanelExtra::handle_logout()
{
    Lifeograph::s_internaloperation++;

    m_treestore->clear();

    Lifeograph::s_internaloperation--;
}

void
PanelExtra::cell_data_func_icon( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter )
{
    DiaryElement* elem( ( * iter )[ m_treestore->colrec.ptr ] );
    if( elem != nullptr )
        cell->set_property( "visible", elem->get_type() != DiaryElement::ET_HEADER );
}

void
PanelExtra::cell_data_func_text( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter )
{
    DiaryElement* elem( ( * iter )[ m_treestore->colrec.ptr ] );

    if( elem != nullptr )
    {
        switch( elem->get_type() )
        {
            case DiaryElement::ET_HEADER:
                //cell->set_property( "background-gdk", Gdk::Color( "#DDDDDD" ) );
                if( Lifeograph::settings.small_lists )
                    cell->set_property( "scale", 1.0 );
                cell->set_property( "ypad", 5 );
                break;
            default:
                //cell->set_property( "background-set", false );
                if( Lifeograph::settings.small_lists )
                    cell->set_property( "scale", 0.9 );
                cell->set_property( "ypad", 0 );
                break;
        }
    }
}

void
PanelExtra::cell_data_func_filter( Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter )
{
    DiaryElement *elem( ( * iter )[ m_treestore->colrec.ptr ] );
    if( elem != nullptr )
    {
        if( elem->get_type() == DiaryElement::ET_TAG ||
            elem->get_type() == DiaryElement::ET_UNTAGGED )
        {
            if( dynamic_cast< Tag* >( elem ) == Diary::d->get_filter_tag() )
            {
                cell->set_property( "visible", true );
                cell->set_property( "pixbuf", Lifeograph::icons->filter_16 );
                return;
            }
        }

        cell->set_property( "visible", false );
    }
}

void
PanelExtra::populate()
{
    Lifeograph::s_internaloperation++;

    m_treestore->clear();

    Gtk::TreeRow row;

    // TAGS HEADER
    row = * m_treestore->append();
    row[ m_treestore->colrec.ptr ] = &s_header;
    row[ m_treestore->colrec.name ] = Glib::ustring::compose( "<b>%1</b>", _( "TAGS" ) );

    if( Diary::d->get_tags()->size() < 1 && Diary::d->m_tag_categories.size() < 1 )
    {
        row = * m_treestore->append();
        row[ m_treestore->colrec.ptr ] = &s_none;
        row[ m_treestore->colrec.name ] = Glib::ustring::compose( "<i>%1</i>", _( "None" ) );
    }

    // ROOT TAGS
    for( auto& kv_tag : *Diary::d->get_tags() )
    {
        if( kv_tag.second->get_category() == nullptr )
            add_elem_to_list( kv_tag.second );
    }

    // CATEGORIES
    for( auto& kv_ctg : Diary::d->m_tag_categories )
    {
        CategoryTags* category = kv_ctg.second;
        row = * add_elem_to_list( category );

        for( Tag* tag : *category )
        {
            add_elem_to_list( tag, &row.children() );
        }

        if( category->get_expanded() )
            this->expand_row( m_treestore->get_path( row ), false );
    }

    // EMPTY SEPARATOR ROW
    row = * m_treestore->append();
    row[ m_treestore->colrec.ptr ] = &s_none;

    // UNTAGGED PSEUDO TAG
    add_elem_to_list( &Diary::d->m_untagged );

    // EMPTY SEPARATOR ROW
    //row = * m_treestore->append();
    //row[ m_treestore->colrec.ptr ] = &s_none;

    // FILTERS GROUP
    //add_elem_to_list( Diary::d->m_filter_active );

    /*for( FilterVector::iterator iter = )
        add_elem_to_list( *iter );*/

    Lifeograph::s_internaloperation--;
}

inline Gtk::TreeIter
PanelExtra::add_elem_to_list( DiaryElement* elem, const Gtk::TreeNodeChildren* children )
{
    Gtk::TreeRow row = * ( children != nullptr ? m_treestore->append( *children ) :
                                              m_treestore->append() );

    row[ m_treestore->colrec.ptr ] = elem;
    row[ m_treestore->colrec.name ] = elem->get_list_str();
    row[ m_treestore->colrec.icon ] = elem->get_icon();

    elem->m_list_data->treepath = m_treestore->get_path( row );

    return row;
}

Gtk::TreeRow
PanelExtra::get_row( const Gtk::TreePath& path )
{
    return( * m_treestore->get_iter( path ) );
    // TODO: check validity
}

void
PanelExtra::set_filtered_tag( const Tag* tag )
{
    static const Tag *tag_old( nullptr );

    if( tag != tag_old && tag_old != nullptr )
    {
        Gtk::TreeRow row = get_row( tag_old->m_list_data->treepath );
        row[ m_treestore->colrec.filter ] = Lifeograph::icons->filter_16;
    }
    if( tag != nullptr )
    {
        Gtk::TreeRow row = get_row( tag->m_list_data->treepath );
        row[ m_treestore->colrec.filter ] = Lifeograph::icons->filter_16;
    }

    tag_old = tag;
}

void
PanelExtra::refresh_elem( DiaryElement* elem )
{
    Gtk::TreeRow row = get_row( elem->m_list_data->treepath );
    row[ m_treestore->colrec.icon ] = elem->get_icon();
    row[ m_treestore->colrec.name ] = elem->get_list_str();
}

void
PanelExtra::on_drag_begin( const Glib::RefPtr< Gdk::DragContext >& context )
{
    if( Lifeograph::s_elem_dragged )
    {
        Lifeograph::s_flag_dragging = true;

        //Gtk::TreeView::on_drag_begin( context );
        context->set_icon( Lifeograph::s_elem_dragged->get_icon32(), 0, 0 );
    }
}

void
PanelExtra::on_drag_end( const Glib::RefPtr< Gdk::DragContext >& context )
{
    Gtk::TreeView::on_drag_end( context );
    Lifeograph::s_elem_dragged = nullptr;
}

bool
PanelExtra::on_drag_motion( const Glib::RefPtr< Gdk::DragContext >& context,
                                 int x, int y, guint time )
{
    if( ! Lifeograph::s_flag_dragging )
        return false;

    if( ! Lifeograph::s_elem_dragged )
        return false;

    bool retval( false );
    Gdk::DragAction action( Gdk::ACTION_MOVE );
    Gtk::TreePath path;
    DiaryElement* element;

    // AUTOSCROLL
    Gdk::Rectangle rect;
    get_visible_rect( rect );
    int new_y = rect.get_y();
    if( y < ( rect.get_height() * 0.15 ) )
    {
        if( new_y > 0 )
        {
            new_y -= 5;
            if( new_y < 0 )
                new_y = 0;
            scroll_to_point( -1, new_y );
        }
    }
    else
    if( y > ( rect.get_height() * 0.85 ) )
    {
        if( get_vadjustment()->get_value() < get_vadjustment()->get_upper() )
        {
            new_y += 5;
            scroll_to_point( -1, new_y );
        }
    }

    // EVALUATE THE HOVERED ELEMENT
    if( get_path_at_pos( x, y, path ) )
    {
        Gtk::TreeRow row( * m_treestore->get_iter( path ) );
        element = row[ m_treestore->colrec.ptr ];
        if( element != nullptr && element != Lifeograph::s_elem_dragged )
        {
            switch( Lifeograph::s_elem_dragged->get_type() )
            {
                case DiaryElement::ET_TAG:
                {
                    Tag *tag_dragged( dynamic_cast< Tag* >( Lifeograph::s_elem_dragged ) );

                    if( element->get_type() == DiaryElement::ET_TAG_CTG )
                    {
                        CategoryTags* ctg_dropped( dynamic_cast< CategoryTags* >( element ) );
                        if( tag_dragged->get_category() != ctg_dropped )
                            retval = true;
                    }
                    else if( element->get_type() == DiaryElement::ET_TAG )
                    {
                        Tag* tag_dropped( dynamic_cast< Tag* >( element ) );
                        if( tag_dragged->get_category() != tag_dropped->get_category() )
                            retval = true;
                    }
                    break;
                }
                case DiaryElement::ET_ENTRY:
                case DiaryElement::ET_MULTIPLE_ENTRIES:
                    if( element->get_type() == DiaryElement::ET_TAG ||
                        element->get_type() == DiaryElement::ET_UNTAGGED )
                    {
                        retval = true;
                        action = Gdk::ACTION_COPY;
                    }
                    break;
                default:
                    break;
            }
        }
    }

    if( retval )
    {
        m_elem_drop_target = element;
        context->drag_status( action, time );
        get_selection()->select( path );
    }
    else
        get_selection()->unselect_all();

    return retval;
}

bool
PanelExtra::on_drag_drop( const Glib::RefPtr< Gdk::DragContext >& context,
                          int x, int y, guint time )
{
    bool flag_successful( false );

    if( Lifeograph::s_flag_dragging )
    {
        if( Lifeograph::s_elem_dragged != nullptr || m_elem_drop_target != nullptr )
        {
            switch( Lifeograph::s_elem_dragged->get_type() )
            {
                case DiaryElement::ET_TAG:
                {
                    Tag* tag( dynamic_cast< Tag* >( Lifeograph::s_elem_dragged ) );
                    if( m_elem_drop_target->get_type() == DiaryElement::ET_TAG_CTG )
                    {
                        CategoryTags* ctg( dynamic_cast< CategoryTags* >( m_elem_drop_target ) );
                        tag->set_category( ctg );
                        flag_successful = true;
                    }
                    else if( m_elem_drop_target->get_type() == DiaryElement::ET_TAG )
                    {
                        Tag* target_tag( dynamic_cast< Tag* >( m_elem_drop_target ) );
                        tag->set_category( target_tag->get_category() );
                        flag_successful = true;
                    }
                    break;
                }
                case DiaryElement::ET_ENTRY:
                case DiaryElement::ET_MULTIPLE_ENTRIES:
                    if( m_elem_drop_target->get_type() == DiaryElement::ET_TAG ||
                        m_elem_drop_target->get_type() == DiaryElement::ET_UNTAGGED )
                    {
                        Tag* target_tag( dynamic_cast< Tag* >( m_elem_drop_target ) );

                        sigc::slot< void, Entry* > s = sigc::bind(
                                 sigc::mem_fun( this, &PanelExtra::tag_entry ), target_tag );
                        AppWindow::p->panel_diary->do_for_each_selected_entry( s );

                        flag_successful = true;
                    }
                    break;
                default:
                    break;
            }
        }
    }

    if( flag_successful )
    {
        populate();
    }

    context->drag_finish( flag_successful, false, time );
    return flag_successful;
}

bool
PanelExtra::on_button_press_event( GdkEventButton* event )
{
    Lifeograph::s_flag_dragging = false;
    Lifeograph::s_elem_dragged = nullptr;

    Gtk::TreePath path;
    if( this->get_path_at_pos( event->x, event->y, path ) )
    {
        Gtk::TreeRow row = * m_treestore->get_iter( path );
        Lifeograph::s_elem_dragged = row[ m_treestore->colrec.ptr ];
        if( Lifeograph::s_elem_dragged == nullptr )
            return true;    // ignore
        else
        {
            switch( Lifeograph::s_elem_dragged->get_type() )
            {
                case DiaryElement::ET_TAG:
                case DiaryElement::ET_UNTAGGED:
                case DiaryElement::ET_FILTER:   // actually not included now
                case DiaryElement::ET_TAG_CTG:
                    break;
                default:
                    Lifeograph::s_elem_dragged = nullptr;
                    return true;
            }
        }
    }
    else
    {
        return true;
    }

    return Gtk::TreeView::on_button_press_event( event );
}

bool
PanelExtra::on_button_release_event( GdkEventButton* event )
{
    if( ! Lifeograph::s_flag_dragging /*&& event->button != 1*/ )
    {
        if( Lifeograph::s_elem_dragged )
            Lifeograph::s_elem_dragged->show();

        Lifeograph::s_elem_dragged = nullptr;
    }

    get_selection()->unselect_all();

    return Gtk::TreeView::on_button_release_event( event );
}

void
PanelExtra::handle_treeview_row_expanded( const Gtk::TreeIter& iter,
                                          const Gtk::TreePath& path )
{
    if( Lifeograph::s_internaloperation )
        return;

    DiaryElement *element = ( * iter )[ m_treestore->colrec.ptr ];
    if( element )
    {
        if( element->get_type() == DiaryElement::ET_TAG_CTG )
        {
            CategoryTags *category = dynamic_cast< CategoryTags* >( element );
            category->set_expanded( this->row_expanded( path ) );
        }
    }
}

void
PanelExtra::handle_treeview_row_activated( const Gtk::TreePath& path, Gtk::TreeViewColumn* )
{
    Gtk::TreeRow row( * m_treestore->get_iter( path ) );
    DiaryElement* element( row[ m_treestore->colrec.ptr ] );

    if( element == nullptr )
        return;

    if( element->get_type() == DiaryElement::ET_TAG ||
        element->get_type() == DiaryElement::ET_UNTAGGED )
    {
        const Tag* tag( dynamic_cast< Tag* >( element ) );

        if( tag == Diary::d->get_filter_tag() ) // remove filter if already filtered
            tag = nullptr;

        Diary::d->set_filter_tag( tag );
        set_filtered_tag( tag );
        AppWindow::p->panel_diary->handle_filter_changed();
    }
}

// HELPER FUNCTION
void
PanelExtra::tag_entry( Entry* entry, Tag* tag )
{
    if( entry->add_tag( tag ) ) // if does not already have the tag
    {
        if( AppWindow::p->panel_main->is_cur_elem( entry ) )
        {
            AppWindow::p->m_entry_view->update_tag_widget();
            AppWindow::p->m_entry_view->update_theme();
        }
    }
}

// TREESTORE EXTRA =================================================================================
TreeStoreExtra::TreeStoreExtra()
{
    set_column_types( colrec );
}

Glib::RefPtr<TreeStoreExtra> TreeStoreExtra::create()
{
    return Glib::RefPtr< TreeStoreExtra >( new TreeStoreExtra() );
}

bool
TreeStoreExtra::row_draggable_vfunc( const Gtk::TreeModel::Path& path ) const
{
    TreeStoreExtra* unconstThis = const_cast< TreeStoreExtra* >( this );
    const_iterator iter = unconstThis->get_iter( path );

    if( iter )
    {
        Row row = *iter;
        DiaryElement* elem = row[ colrec.ptr ];
        if( !elem )
            return false;
        else
            return( elem->get_type() != DiaryElement::ET_TAG_CTG );
    }

    return Gtk::TreeStore::row_draggable_vfunc( path );
}
