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

    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 _WIN32
#include <winsock2.h> // to silence warnings on Windows
#endif

#include <cairomm/context.h>

extern "C"
{
#include <champlain-gtk/champlain-gtk.h>
}

#include "../helpers.hpp"
#include "../lifeograph.hpp"
#include "../strings.hpp"
#include "../app_window.hpp"
#include "widget_map.hpp"


using namespace LIFEO;

// STATIC VARIABLES
const ClutterColor WidgetMap::s_color_entry     = { 0xdd, 0xcc, 0xcc, 0x99 };
const ClutterColor WidgetMap::s_color_entry_cur = { 0xdd, 0x99, 0x99, 0xcc };
const ClutterColor WidgetMap::s_color_path      = { 0x66, 0x33, 0x33, 0xaa };
ClutterColor WidgetMap::s_color_path_sel        = { 0xee, 0x88, 0x88, 0xdd };


WidgetMap::WidgetMap()
{
    GtkWidget* ClE_map = gtk_champlain_embed_new();
    m_W_map = Glib::wrap( ClE_map );
    m_ClV_map = gtk_champlain_embed_get_view( ( GtkChamplainEmbed* ) ClE_map );

    // SCALE
    auto scale = champlain_scale_new ();
    champlain_scale_connect_view( CHAMPLAIN_SCALE( scale ), m_ClV_map );
    clutter_actor_set_x_expand( scale, true );
    clutter_actor_set_y_expand( scale, true );
    clutter_actor_set_x_align( scale, CLUTTER_ACTOR_ALIGN_START );
    clutter_actor_set_y_align( scale, CLUTTER_ACTOR_ALIGN_END );
    clutter_actor_add_child( CLUTTER_ACTOR( m_ClV_map ), scale );

    // LAYERS
    m_path_layer = champlain_path_layer_new ();
    champlain_view_add_layer( m_ClV_map, CHAMPLAIN_LAYER( m_path_layer ) );

    m_marker_layer_path = champlain_marker_layer_new_full( CHAMPLAIN_SELECTION_SINGLE );
    clutter_actor_show( CLUTTER_ACTOR ( m_marker_layer_path ) );
    champlain_view_add_layer( m_ClV_map, CHAMPLAIN_LAYER( m_marker_layer_path ) );

    m_marker_layer_entries = champlain_marker_layer_new_full( CHAMPLAIN_SELECTION_NONE );
    clutter_actor_show( CLUTTER_ACTOR ( m_marker_layer_entries ) );
    champlain_view_add_layer( m_ClV_map, CHAMPLAIN_LAYER( m_marker_layer_entries ) );

    m_EBx = Gtk::manage( new Gtk::EventBox );
    m_EBx->set_can_focus( true );
    m_EBx->set_events( Gdk::KEY_PRESS_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::SCROLL_MASK );
    m_EBx->add( *m_W_map );
    m_EBx->signal_key_press_event().connect(
            [ this ]( GdkEventKey* e ){ return on_key_press_event( e ); } );
    m_EBx->signal_button_press_event().connect(
            [ this ]( GdkEventButton* e ){ return on_button_press_event( e ); } );
    m_EBx->signal_scroll_event().connect(
            [ this ]( GdkEventScroll* e ){ return on_scroll_event( e ); } );
    m_EBx->signal_focus_out_event().connect(
            [ this ]( GdkEventFocus* event )
            {
                if( m_flag_inhibit_deselect == false )
                {
                    champlain_marker_layer_unselect_all_markers( m_marker_layer_path );
                    m_marker_selected = nullptr;
                }
                return false;
            } );

    // POPOVER
    m_builder = Gtk::Builder::create();
    Lifeograph::load_gui( m_builder, Lifeograph::SHAREDIR + "/ui/map.ui" );

    m_builder->get_widget( "Po_actions", m_Po_actions );
    m_builder->get_widget( "B_assign", m_B_assign );
    m_builder->get_widget( "B_dismiss", m_B_dismiss );
    m_builder->get_widget( "B_clipboard_copy", m_B_clipboard_copy );
    m_builder->get_widget( "Bx_header_path", m_Bx_header_path );
    m_builder->get_widget( "B_add_path_point", m_B_add_path_point );
    m_builder->get_widget( "B_remove_path_point", m_B_remove_path_point );
    m_builder->get_widget( "B_clear_path", m_B_clear_path );
    m_builder->get_widget( "Sw_all_locations", m_Sw_all_locations );
    m_IS_entry_icon = Cairo::ImageSurface::create( Cairo::FORMAT_ARGB32, 16, 16 );
    m_IC_entry_icon = Cairo::Context::create( m_IS_entry_icon );

    m_Po_actions->set_relative_to( *m_W_map );

    m_B_assign->signal_clicked().connect(
            [ this ]()
            {   if( m_p2entry_cur )
                {   m_p2entry_cur->set_location( m_latitude, m_longitude );
                    refresh_entry_locations(); } } );
    m_B_dismiss->signal_clicked().connect(
            [ this ]()
            {   if( m_p2entry_cur )
                { m_p2entry_cur->remove_location(); refresh_entry_locations(); } } );
    m_B_clipboard_copy->signal_clicked().connect(
            [ this ]()
            {   Gtk::Clipboard::get()->set_text(
                        STR::compose( "geo:", m_latitude, ",", m_longitude ) ); } );

    m_B_add_path_point->signal_clicked().connect( [ this ]() { add_path_point_to_entry(); } );
    m_B_remove_path_point->signal_clicked().connect(
            [ this ]() { remove_selected_path_point_from_entry(); } );
    m_B_clear_path->signal_clicked().connect(
            [ this ]()
            {
                clear_path();
                if( m_p2entry_cur ) m_p2entry_cur->clear_map_path();
            } );

    m_Sw_all_locations->property_active().signal_changed().connect(
            [ this ]()
            {
                if( Lifeograph::is_internal_operations_ongoing() ) return;
                m_p2diary->set_opt_show_all_entry_locs( m_Sw_all_locations->get_active() );
                refresh_entry_locations();
            } );
}

void
WidgetMap::show_Po( int x, int y )
{
    const bool editable{ m_p2diary->is_in_edit_mode() };

    m_flag_inhibit_deselect = true;

    m_B_assign->set_visible( editable );
    m_B_dismiss->set_visible( editable && m_p2entry_cur && m_p2entry_cur->is_location_set() );
    m_Bx_header_path->set_visible( editable );
    m_B_add_path_point->set_visible( editable );
    m_B_remove_path_point->set_visible( editable && m_marker_selected != nullptr );
    m_B_clear_path->set_visible( editable && m_path.size() > 0 );

    m_Po_actions->set_pointing_to( { x, y, 1, 1 } );
    m_Po_actions->show();

    m_flag_inhibit_deselect = false;
}

void
WidgetMap::refresh_entry_locations()
{
    champlain_marker_layer_remove_all( m_marker_layer_entries );

    if( m_p2diary->get_opt_show_all_entry_locs() )
    {
        for( auto& kv_entry : m_p2diary->get_entries() )
        {
            if( kv_entry.second->is_location_set() )
                add_entry_node( kv_entry.second );
        }
    }
    else if( m_p2entry_cur->is_location_set() )
        add_entry_node( m_p2entry_cur );
}

void
WidgetMap::go_to_entry( ChamplainMarker* marker, ClutterEvent* event, gpointer entry )
{
    if( clutter_event_get_click_count( event ) == 2 )
    {
        AppWindow::p->UI_extra->get_W_map()->set_entry( ( Entry* ) entry, false );
        AppWindow::p->UI_diary->show_in_list( ( Entry* ) entry );
        AppWindow::p->UI_entry->show( ( Entry* ) entry );
        AppWindow::p->UI_entry->get_textview()->grab_focus();
    }
}

void
WidgetMap::update_path_point( ChamplainMarker* marker, ClutterEvent* event, gpointer p )
{
    WidgetMap* ptr{ ( WidgetMap* ) p };
    ptr->m_p2entry_cur->clear_map_path();

    for( auto& actor : ptr->m_path )
    {
        ptr->m_p2entry_cur->add_map_path_point(
                champlain_location_get_latitude( CHAMPLAIN_LOCATION( actor ) ),
                champlain_location_get_longitude( CHAMPLAIN_LOCATION( actor ) ) );
    }

    if( ptr->m_marker_distance )
    {
        champlain_label_set_text( CHAMPLAIN_LABEL( ptr->m_marker_distance ),
                                  ptr->get_path_distance().c_str() );
    }
}

void
WidgetMap::handle_node_click( GObject* gobj, GParamSpec* event, gpointer p )
{
    WidgetMap* ptr{ ( WidgetMap* ) p };
    ptr->m_EBx->grab_focus(); // this is required for the key press events to work

    GList* list_selected = champlain_marker_layer_get_selected( ptr->m_marker_layer_path );
    if( g_list_length( list_selected ) == 1 )
        ptr->m_marker_selected = g_list_nth_data( list_selected, 0 );
    else // probably never occurs
        ptr->m_marker_selected = nullptr;
    g_list_free( list_selected );
}

void
WidgetMap::add_entry_node( Entry* entry )
{
    static const std::string icon_path = Lifeograph::SHAREDIR + "/icons/entry_parent-16.png";
    const bool flag_current{ entry == m_p2entry_cur };
    const Coords&& coords{ entry->get_location() };
    ClutterActor* marker{ champlain_label_new_from_file( icon_path.c_str(), nullptr ) };

    champlain_location_set_location( CHAMPLAIN_LOCATION( marker ),
                                     coords.latitude, coords.longitude );
    champlain_label_set_color( CHAMPLAIN_LABEL( marker ),
                               flag_current ? &s_color_entry_cur : &s_color_entry );

    // maybe later:
//    champlain_label_set_text_color( CHAMPLAIN_LABEL( marker ),
//                               flag_current ? &color_normal : &color_current );
//    champlain_label_set_font_name( CHAMPLAIN_LABEL( marker ), "Sans 11" );
//    champlain_label_set_text( CHAMPLAIN_LABEL( marker ), entry->get_name().c_str() );

    champlain_marker_layer_add_marker( m_marker_layer_entries, CHAMPLAIN_MARKER( marker ) );

    if( flag_current )
        m_marker_entry_cur = marker;

    g_signal_connect( marker, "button-press", ( GCallback ) go_to_entry, entry );
}

void
WidgetMap::create_entry_at_location()
{
    auto entry{ m_p2diary->create_entry( m_p2diary->get_available_order_1st( true ), "" ) };
    entry->set_location( m_latitude, m_longitude );
    m_p2entry_cur = entry;
    refresh_entry_locations();
    AppWindow::p->UI_diary->update_entry_list();
    AppWindow::p->UI_entry->show( entry );
    AppWindow::p->UI_entry->get_textview()->grab_focus();
}

void
WidgetMap::add_path_point_to_entry()
{
    if( m_p2diary->is_in_edit_mode() == false ) return;

    if( m_p2entry_cur )
        m_p2entry_cur->add_map_path_point( m_latitude, m_longitude );

    add_path_point( { m_latitude, m_longitude },
                    m_path.empty() ? ArrayPosition::FIRST : ArrayPosition::LAST );
}

void
WidgetMap::add_path_point( const Coords& pt, const ArrayPosition& position )
{
    ClutterActor* marker{ nullptr };

    auto do_add_point = [ this ]( ClutterActor* ca, double lat, double lon )
    {
        champlain_location_set_location( CHAMPLAIN_LOCATION( ca ), lat, lon );
        champlain_marker_layer_add_marker( m_marker_layer_path, CHAMPLAIN_MARKER( ca ) );
        champlain_path_layer_add_node( m_path_layer, CHAMPLAIN_LOCATION( ca ) );
        champlain_marker_set_draggable( CHAMPLAIN_MARKER( ca ), m_p2diary->is_in_edit_mode() );

        g_signal_connect( ca, "drag-finish", ( GCallback ) update_path_point, this );
        g_signal_connect( ca, "button-press", ( GCallback ) handle_node_click, this );

        m_path.push_back( ca );
    };

    if( m_path.empty() )
        champlain_marker_set_selection_color( &s_color_path_sel );

    switch( position )
    {
        case ArrayPosition::FIRST: // if( m_path.empty() )
            marker = champlain_label_new_with_text( _( "Start" ), "Sans 11", nullptr, nullptr );
            champlain_label_set_color( CHAMPLAIN_LABEL( marker ), &s_color_path );
            break;
        case ArrayPosition::INTERMEDIATE:
            marker = champlain_point_new();
            champlain_point_set_color( CHAMPLAIN_POINT( marker ), &s_color_path );
            break;
        case ArrayPosition::LAST:
            if( m_marker_distance ) // remove and re-add the last node as a plain point
            {
                const double lat{ champlain_location_get_latitude(
                        CHAMPLAIN_LOCATION( m_marker_distance ) ) };
                const double lon{ champlain_location_get_longitude(
                        CHAMPLAIN_LOCATION( m_marker_distance ) ) };

                champlain_marker_layer_remove_marker( m_marker_layer_path,
                                                      CHAMPLAIN_MARKER( m_marker_distance ) );
                champlain_path_layer_remove_node( m_path_layer,
                                                  CHAMPLAIN_LOCATION( m_marker_distance ) );
                m_path.pop_back();

                marker = champlain_point_new();
                champlain_point_set_color( CHAMPLAIN_POINT( marker ), &s_color_path );

                do_add_point( marker, lat, lon );
            }

            m_marker_distance = marker =
                    champlain_label_new_with_text( get_path_distance().c_str(),
                                                   "Sans 10", nullptr, nullptr );
            champlain_label_set_color( CHAMPLAIN_LABEL( marker ), &s_color_path );
            break;
    }

    do_add_point( marker, pt.latitude, pt.longitude );
}

void
WidgetMap::remove_last_path_point_from_entry()
{
    if( m_p2diary->is_in_edit_mode() == false || m_path.empty() ) return;

    m_p2entry_cur->remove_last_map_path_point();
    refresh_path();
}

void
WidgetMap::remove_selected_path_point_from_entry()
{
    if( m_p2diary->is_in_edit_mode() == false || m_path.empty() ) return;

    if( m_marker_selected )
    {
        m_p2entry_cur->remove_map_path_point(
                { champlain_location_get_latitude( CHAMPLAIN_LOCATION( m_marker_selected ) ),
                  champlain_location_get_longitude( CHAMPLAIN_LOCATION( m_marker_selected ) ) } );

        refresh_path();
    }
}

void
WidgetMap::refresh_path()
{
    clear_path();
    if( m_p2entry_cur->is_map_path_set() )
        for( auto& point : m_p2entry_cur->get_map_path() )
        {
            if( m_path.empty() )
                add_path_point( point, ArrayPosition::FIRST );
            else if( m_path.size() == m_p2entry_cur->get_map_path().size() - 1 )
                add_path_point( point, ArrayPosition::LAST );
            else
                add_path_point( point, ArrayPosition::INTERMEDIATE );
        }
}

void
WidgetMap::clear_path()
{
    champlain_path_layer_remove_all( m_path_layer );
    champlain_marker_layer_remove_all( m_marker_layer_path );
    m_marker_distance = nullptr;
    m_marker_selected = nullptr;

    m_path.clear();
}

Ustring
WidgetMap::get_path_distance()
{
    return( STR::compose( m_p2entry_cur ? m_p2entry_cur->get_map_path_length() : 0.0,
                          Lifeograph::settings.use_imperial_units ? " mi" : " km" ) );
}

void
WidgetMap::set_diary( Diary* d )
{
    Lifeograph::START_INTERNAL_OPERATIONS();
    m_p2diary = d;
    m_Sw_all_locations->set_active( d->get_opt_show_all_entry_locs( ));
    Lifeograph::FINISH_INTERNAL_OPERATIONS();
}

void
WidgetMap::set_entry( Entry* entry, bool flag_center_on )
{
    m_p2entry_cur = entry;

    refresh_entry_locations();
    refresh_path();

    if( flag_center_on )
    {
        if( entry->is_location_set() )
            center_on( entry->get_location().latitude, entry->get_location().longitude );
        else if( entry->is_map_path_set() )
            center_on( entry->get_map_path().front().latitude,
                       entry->get_map_path().front().longitude );
    }
}

void
WidgetMap::handle_editing_enabled()
{
    champlain_marker_layer_set_all_markers_draggable( m_marker_layer_path );
    if( m_marker_entry_cur )
        champlain_marker_set_draggable( CHAMPLAIN_MARKER( m_marker_entry_cur ), true );
}

void
WidgetMap::center_on( double lat, double lon )
{
    champlain_view_set_zoom_level( m_ClV_map, 10 );
    champlain_view_center_on( m_ClV_map, lat, lon );
}

bool
WidgetMap::on_scroll_event( GdkEventScroll* event )
{
    if( event->direction == GDK_SCROLL_UP )
        champlain_view_zoom_in( m_ClV_map );
    else
    if( event->direction == GDK_SCROLL_DOWN )
        champlain_view_zoom_out( m_ClV_map );

    return true;
}

bool
WidgetMap::on_button_press_event( GdkEventButton* event )
{
    m_longitude = champlain_view_x_to_longitude( m_ClV_map, event->x );
    m_latitude = champlain_view_y_to_latitude( m_ClV_map, event->y );

    if( event->button == 1 && m_p2diary->is_in_edit_mode() )
    {
        if( event->state & Gdk::SHIFT_MASK )
        {
            m_p2entry_cur->set_location( m_latitude, m_longitude );
            refresh_entry_locations();
        }
        if( event->state & Gdk::CONTROL_MASK )
            add_path_point_to_entry();
        else
        if( event->state & Gdk::MOD1_MASK )
            create_entry_at_location();
    }
    else
    if( event->button == 3 )
    {
        show_Po( event->x, event->y );
    }

    return true;
}

bool
WidgetMap::on_key_press_event( GdkEventKey* event )
{
    switch( event->keyval )
    {
        case GDK_KEY_BackSpace:
            remove_last_path_point_from_entry();
            return true;
        case GDK_KEY_Delete:
            remove_selected_path_point_from_entry();
            return true;
    }

    return false;
}
