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

    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 <cairomm/context.h>

#include "../lifeograph.hpp"
#include "../strings.hpp"
#include "../diarydata.hpp"
#include "widget_chart.hpp"


using namespace LIFEO;


// WIDGETCHART =====================================================================================
WidgetChart::WidgetChart()
{
    set_events( Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK |
                Gdk::POINTER_MOTION_MASK | Gdk::LEAVE_NOTIFY_MASK | Gdk::SCROLL_MASK );

    Gtk::Viewport* Vp_chart;
    m_builder = Gtk::Builder::create();
    Lifeograph::load_gui( m_builder, Lifeograph::SHAREDIR + "/ui/chart.ui" );

    m_builder->get_widget( "Ol_chart", m_Ol_chart );
    m_builder->get_widget( "Rv_settings", m_Rv_settings );
    m_builder->get_widget( "Po_properties", m_Po_properties );
    m_builder->get_widget( "Vp_chart", Vp_chart );
    m_builder->get_widget( "CB_value_type", m_CB_value_type );
    m_builder->get_widget( "CB_y_axis", m_CB_y_axis );
    m_builder->get_widget( "Bx_tag_params", m_Bx_tag_params );
    m_builder->get_widget_derived( "E_y_tag", m_WEP_y_tag );
    m_builder->get_widget_derived( "E_y_tag_2nd", m_WEP_para_filter_tag );
    m_builder->get_widget_derived( "E_x_filter", m_W_picker_filter );
    m_builder->get_widget( "Sc_zoom", m_Sc_zoom );
    m_builder->get_widget( "RB_weekly", m_RB_weekly );
    m_builder->get_widget( "RB_monthly", m_RB_monthly );
    m_builder->get_widget( "RB_yearly", m_RB_yearly );
    m_builder->get_widget( "B_tagged_only", m_B_tagged_only );
    m_builder->get_widget( "Bx_underlay", m_Bx_underlay );
    m_builder->get_widget( "ChB_underlay_previous", m_ChB_underlay_previous );
    m_builder->get_widget( "ChB_underlay_planned", m_ChB_underlay_planned );

    Vp_chart->add( *this );
    this->show();

    m_W_picker_filter->set_clearable( true );
    m_W_picker_filter->set_select_only( true );

    m_Rv_settings->property_reveal_child().signal_changed().connect(
            [ this ](){ refresh(); } ); // eliminate artifacts behind revealer

    m_Sc_zoom->signal_change_value().connect(
            [ this ]( Gtk::ScrollType, double value )->bool
            {
                if( ! Lifeograph::is_internal_operations_ongoing() )
                    set_zoom( value );
                return false;
            } );

    m_CB_y_axis->signal_changed().connect( [ this ](){ handle_y_axis_changed(); } );

    m_WEP_y_tag->signal_updated().connect( [ this ]( Entry* e ){ handle_tag_changed( e ); } );
    m_WEP_para_filter_tag->signal_updated().connect(
            [ this ]( Entry* e ){ handle_para_filter_tag_changed( e ); } );

    m_CB_value_type->signal_changed().connect( [ this ](){ handle_value_type_changed(); } );

    m_W_picker_filter->signal_sel_changed().connect(
            [ this ]( const Ustring& filter_name ){ handle_filter_changed( filter_name ); } );

    m_RB_monthly->signal_toggled().connect( [ this ](){ handle_period_changed(); } );
    m_RB_yearly->signal_toggled().connect( [ this ](){ handle_period_changed(); } );

    m_ChB_underlay_previous->signal_toggled().connect(
            [ this ](){ handle_underlay_previous_toggled(); } );
    m_ChB_underlay_planned->signal_toggled().connect(
            [ this ](){ handle_underlay_planned_toggled(); } );

    m_A_tagged_only = Lifeograph::p->add_action_bool( "limit_chart_to_tagged",
            [ this ](){ handle_tagged_only_toggled(); } , false );
}

void
WidgetChart::set_diary( Diary* diary )
{
    Chart::set_diary( diary );
    m_WEP_y_tag->set_diary( diary );
    m_WEP_para_filter_tag->set_diary( diary );
    m_W_picker_filter->set_map( diary->get_p2filters(), &m_data.filter );
    refresh_editabilitiy(); // per the editability of the new diary
}

void
WidgetChart::handle_y_axis_changed()
{
    if( Lifeograph::is_internal_operations_ongoing() ) return;

    switch( m_CB_y_axis->property_active() )
    {
        case 0:
            m_data.set_type_sub( ChartData::COUNT );
            break;
        case 1:
            m_data.set_type_sub( ChartData::TEXT_LENGTH );
            break;
        case 2:
            m_data.set_type_sub( ChartData::MAP_PATH_LENGTH );
            break;
        case 3:
            m_data.set_type_sub( ChartData::TAG_VALUE_ENTRY );
            break;
        case 4:
            m_data.set_type_sub( ChartData::TAG_VALUE_PARA );
            break;
    }

    m_data.refresh_unit();
    update_child_widgets_per_data(); // to show or hide the tag widget
    calculate_and_plot();
}

void
WidgetChart::handle_tag_changed( Entry* tag )
{
    if( Lifeograph::is_internal_operations_ongoing() ) return;

    m_data.tag = tag;
    m_data.refresh_unit();

    calculate_and_plot();
}

void
WidgetChart::handle_para_filter_tag_changed( Entry* tag )
{
    if( Lifeograph::is_internal_operations_ongoing() ) return;

    m_data.para_filter_tag = tag;

    calculate_and_plot();
}

void
WidgetChart::handle_value_type_changed()
{
    if( Lifeograph::is_internal_operations_ongoing() ) return;

    switch( m_CB_value_type->property_active() )
    {
        case 0:
            m_data.set_type_sub( ChartData::CUMULATIVE_PERIODIC );
            break;
        case 1:
            m_data.set_type_sub( ChartData::CUMULATIVE_CONTINUOUS );
            break;
        case 2:
            m_data.set_type_sub( ChartData::AVERAGE );
            break;
    }

    calculate_and_plot();
}

void
WidgetChart::handle_filter_changed( const Ustring& filter_name )
{
    if( Lifeograph::is_internal_operations_ongoing() ) return;

    m_data.filter = m_p2diary->get_filter( filter_name );

    calculate_and_plot();
}

void
WidgetChart::handle_tagged_only_toggled()
{
    if( Lifeograph::is_internal_operations_ongoing() ) return;

    bool state;
    m_A_tagged_only->get_state( state );

    m_data.set_type_sub( state ? -ChartData::TAGGED_ONLY : ChartData::TAGGED_ONLY );

    // The action's state does not change automatically:
    m_A_tagged_only->change_state( !state );

    calculate_and_plot();
}

void
WidgetChart::handle_period_changed()
{
    if( Lifeograph::is_internal_operations_ongoing() ) return;

    if( m_RB_weekly->get_active() )
        m_data.set_type_sub( ChartData::WEEKLY );
    else
    if( m_RB_monthly->get_active() )
        m_data.set_type_sub( ChartData::MONTHLY );
    else
    if( m_RB_yearly->get_active() )
        m_data.set_type_sub( ChartData::YEARLY );

    update_child_widgets_per_data(); // to show or hide the underlay button
    calculate_and_plot();
}

void
WidgetChart::handle_underlay_previous_toggled()
{
    if( Lifeograph::is_internal_operations_ongoing() ) return;

    if( m_ChB_underlay_previous->get_active() )
        m_data.set_type_sub( ChartData::UNDERLAY_PREV_YEAR );
    else
        m_data.set_type_sub( ChartData::UNDERLAY_NONE );

    Lifeograph::START_INTERNAL_OPERATIONS();
    m_ChB_underlay_planned->set_active( false );
    Lifeograph::FINISH_INTERNAL_OPERATIONS();

    calculate_and_plot();
}

void
WidgetChart::handle_underlay_planned_toggled()
{
    if( Lifeograph::is_internal_operations_ongoing() ) return;

    if( m_ChB_underlay_planned->get_active() )
        m_data.set_type_sub( ChartData::UNDERLAY_PLANNED );
    else
        m_data.set_type_sub( ChartData::UNDERLAY_NONE );

    Lifeograph::START_INTERNAL_OPERATIONS();
    m_ChB_underlay_previous->set_active( false );
    Lifeograph::FINISH_INTERNAL_OPERATIONS();

    calculate_and_plot();
}

void
WidgetChart::calculate_and_plot( bool flag_emit_changed )
{
    Lifeograph::START_INTERNAL_OPERATIONS();

    calculate_points();

    m_Sc_zoom->set_value( 1.0 );
    m_Sc_zoom->set_visible( is_zoom_possible() );

    Lifeograph::FINISH_INTERNAL_OPERATIONS();

    if( flag_emit_changed )
        m_Sg_changed.emit();
}

void
WidgetChart::update_child_widgets_per_data()
{
    Lifeograph::START_INTERNAL_OPERATIONS();

    // Y AXIS
    switch( int t = ( m_data.type & ChartData::Y_AXIS_MASK ) )
    {
        case ChartData::COUNT:
            m_CB_y_axis->set_active( 0 );
            m_B_tagged_only->set_visible( false );
            m_Bx_tag_params->set_visible( false );
            m_ChB_underlay_planned->set_visible( false );
            m_WEP_y_tag->set_entry( nullptr );
            m_WEP_para_filter_tag->set_entry( nullptr );
            break;
        case ChartData::TEXT_LENGTH:
            m_CB_y_axis->set_active( 1 );
            m_B_tagged_only->set_visible( false );
            m_Bx_tag_params->set_visible( false );
            m_ChB_underlay_planned->set_visible( false );
            m_WEP_y_tag->set_entry( nullptr );
            m_WEP_para_filter_tag->set_entry( nullptr );
            break;
        case ChartData::MAP_PATH_LENGTH:
            m_CB_y_axis->set_active( 2 );
            m_B_tagged_only->set_visible( false );
            m_Bx_tag_params->set_visible( false );
            m_ChB_underlay_planned->set_visible( false );
            m_WEP_y_tag->set_entry( nullptr );
            m_WEP_para_filter_tag->set_entry( nullptr );
            break;
        case ChartData::TAG_VALUE_ENTRY:
        case ChartData::TAG_VALUE_PARA:
            m_CB_y_axis->set_active( t == ChartData::TAG_VALUE_ENTRY ? 3 : 4  );
            m_B_tagged_only->set_visible( t == ChartData::TAG_VALUE_ENTRY );
            m_Bx_tag_params->set_visible( true );
            m_ChB_underlay_planned->set_visible( true );
            m_WEP_y_tag->set_entry( const_cast< Entry* >( m_data.tag ) );
            m_WEP_para_filter_tag->set_entry( const_cast< Entry* >( m_data.para_filter_tag ) );
            break;
    }

    // VALUE TYPE
    switch( m_data.type & ChartData::VALUE_TYPE_MASK )
    {
        case ChartData::CUMULATIVE_PERIODIC:
            m_CB_value_type->set_active( 0 );
            break;
        case ChartData::CUMULATIVE_CONTINUOUS:
            m_CB_value_type->set_active( 1 );
            break;
        case ChartData::AVERAGE:
            m_CB_value_type->set_active( 2 );
            break;
    }

    // FILTER
    m_W_picker_filter->set_text( m_data.filter ? m_data.filter->get_name() : "" );

    // LIMIT TO TAGGED
    m_A_tagged_only->change_state( m_data.is_tagged_only() );

    // PERIOD
    switch( m_data.type & ChartData::PERIOD_MASK )
    {
        case ChartData::WEEKLY:
            m_RB_weekly->set_active();
            m_ChB_underlay_previous->set_visible( true );
            break;
        case ChartData::MONTHLY:
            m_RB_monthly->set_active();
            m_ChB_underlay_previous->set_visible( true );
            break;
        case ChartData::YEARLY:
            m_RB_yearly->set_active();
            m_ChB_underlay_previous->set_visible( false );
            break;
    }

    // UNDERLAY PREVIOUS YEAR
    m_ChB_underlay_previous->set_active( m_data.is_underlay_prev_year() );
    m_ChB_underlay_planned->set_active( m_data.is_underlay_planned() );

    m_Bx_underlay->set_visible(
            m_ChB_underlay_previous->get_visible() || m_ChB_underlay_planned->get_visible() );

    Lifeograph::FINISH_INTERNAL_OPERATIONS();
}

std::string
WidgetChart::get_as_string()
{
    return m_data.get_as_string();
}

void
WidgetChart::set_from_string( const Ustring& chart_def )
{
    m_data.set_from_string( chart_def );
    update_child_widgets_per_data();
    calculate_and_plot( false );
}

bool
WidgetChart::on_scroll_event( GdkEventScroll* event )
{
    if( event->direction == GDK_SCROLL_UP )
        scroll( -1 );
    else
    if( event->direction == GDK_SCROLL_DOWN )
        scroll( 1 );

    return true;
}

bool
WidgetChart::on_button_press_event( GdkEventButton* event )
{
    if( event->button == 1 && event->y > m_height - m_ov_height )
    {
        int col_start{ int( ( event->x / m_width ) * m_span - m_step_count / 2 ) };
        const unsigned int col_start_max{ m_span - m_step_count };
        if( col_start > int( col_start_max ) )
            col_start = col_start_max;
        else
        if( col_start < 0 )
            col_start = 0;
        m_step_start = col_start;

        refresh();

        m_flag_button_pressed = true;
    }

    return true;
}

bool
WidgetChart::on_button_release_event( GdkEventButton* event )
{
    if( event->button == 1 )
    {
        m_flag_button_pressed = false;
    }

    return true;
}

bool
WidgetChart::on_motion_notify_event( GdkEventMotion* event )
{
    m_Rv_settings->set_reveal_child( event->y < ( get_height() / 3 ) );

    if( m_flag_button_pressed )
    {
        int col_start = ( event->x / m_width ) * m_span - m_step_count / 2;
        if( col_start > int( m_span - m_step_count ) )
            col_start = m_span - m_step_count;
        else
        if( col_start < 0 )
            col_start = 0;

        if( col_start != ( int ) m_step_start )
        {
            m_step_start = col_start;
            refresh();
        }
    }
    int hovered_step{ int( round( ( event->x - s_x_min ) / m_step_x ) ) };

    if( hovered_step < 0 )
        hovered_step = 0;
    else if( hovered_step >= int( m_step_count ) )
        hovered_step = m_step_count - 1;

    bool flag_pointer_hovered = ( event->y > m_height - m_ov_height );
    if( flag_pointer_hovered != m_flag_overview_hovered )
    {
        m_flag_overview_hovered = flag_pointer_hovered;
        refresh();
    }
    else if( hovered_step != m_hovered_step )
    {
        m_hovered_step = hovered_step;
        refresh();  // TODO: limit to tooltip area only
    }

    m_flag_widget_hovered = true;

    return Gtk::DrawingArea::on_motion_notify_event( event );
}

bool
WidgetChart::on_leave_notify_event( GdkEventCrossing* event )
{
    m_flag_overview_hovered = false;
    m_flag_widget_hovered = false;
    refresh();

    return true;
}

void
WidgetChart::on_size_allocate( Gtk::Allocation& allocation )
{
    Gtk::Widget::on_size_allocate( allocation );
    resize( allocation.get_width(), allocation.get_height() );
    m_Sc_zoom->set_visible( is_zoom_possible() );
}

bool
WidgetChart::on_draw( const Cairo::RefPtr< Cairo::Context >& cr )
{
    return( Chart::draw( cr ) );
}
