/*
 *       File name:  aghermann/ui/sf/montage_cb.cc
 *         Project:  Aghermann
 *          Author:  Andrei Zavada <johnhommer@gmail.com>
 * Initial version:  2011-07-02
 *
 *         Purpose:  scoring facility: montage drawing area callbacks
 *
 *         License:  GPL
 */

#include <sys/time.h>
#include <cairo/cairo.h>

#include "aghermann/ui/misc.hh"
#include "aghermann/ui/mw/mw.hh"
#include "channel.hh"
#include "sf.hh"


using namespace std;
using namespace agh::ui;

using agh::str::sasprintf;
using agh::str::homedir2tilda;


extern "C" {

gboolean
daSFMontage_configure_event_cb(
        GtkWidget*,
        GdkEventConfigure *event,
        const gpointer userdata)
{
         if ( event->type == GDK_CONFIGURE ) {
                 auto& SF = *(SScoringFacility*)userdata;
                 SF.da_wd = event->width;
                 // don't care about height: it's our own calculation
         }
         return FALSE;
}




// -------------------- Page

gboolean
daSFMontage_draw_cb(
        GtkWidget*,
        cairo_t *cr,
        const gpointer userdata)
{
        auto& SF = *(SScoringFacility*)userdata;
        SF.draw_montage( cr);
        return TRUE;
}


namespace {
void
radio_item_setter( GtkWidget *i, const gpointer u)
{
        const char *label = gtk_menu_item_get_label( (GtkMenuItem*)i);
        if ( strcmp(label, (const char*)u) == 0 )
                gtk_check_menu_item_set_active( (GtkCheckMenuItem*)i, TRUE);
}
} // namespace


gboolean
daSFMontage_button_press_event_cb(
        GtkWidget *wid,
        GdkEventButton *event,
        const gpointer userdata)
{
        auto& SF = *(SScoringFacility*)userdata;

        if ( SF.mode == SScoringFacility::TMode::showing_ics ) {
                if ( SF.ica_components.size() == 0 )
                        return TRUE;

                SF.using_ic = SF.ic_near( event->y);

                if ( event->button == 1 &&
                     (SF.remix_mode == SScoringFacility::TICARemixMode::punch ||
                      SF.remix_mode == SScoringFacility::TICARemixMode::zero) ) {
                        SF.ica_map[SF.using_ic].m =
                                (SF.ica_map[SF.using_ic].m == -1) ? 0 : -1;
                        gtk_widget_queue_draw( wid);
                } else if ( SF.remix_mode == SScoringFacility::TICARemixMode::map ) {
                        const char *mapped =
                                (SF.ica_map[SF.using_ic].m != -1)
                                ? SF.channel_by_idx( SF.ica_map[SF.using_ic].m) . name()
                                : SScoringFacility::ica_unmapped_menu_item_label;
                        SF.suppress_redraw = true;
                        gtk_container_foreach(
                                (GtkContainer*)SF.iiSFICAPage,
                                radio_item_setter, (gpointer)mapped);
                        SF.suppress_redraw = false;
                        gtk_menu_popup( SF.iiSFICAPage,
                                        NULL, NULL, NULL, NULL, 3, event->time);
                }
                return TRUE;
        }

        if ( SF.mode == SScoringFacility::TMode::showing_remixed ) {
                if ( SF.ica_components.size() == 0 )
                        return TRUE;

                SF.using_channel = SF.channel_near( event->y);
                //SF.using_ic = SF.ic_of( SF.using_channel);

                if ( event->button == 1 ) {
                        SF.using_channel->apply_reconstituted =
                                !SF.using_channel->apply_reconstituted;
                        gtk_widget_queue_draw( wid);
                }
                return TRUE;
        }

        if ( SF.mode == SScoringFacility::TMode::shuffling_channels ) {
                SF.mode = SScoringFacility::TMode::scoring;
                return TRUE;
        }

        auto Ch = SF.using_channel = SF.channel_near( event->y);

        if ( Ch->type() == sigfile::SChannel::TType::eeg &&
             (Ch->draw_psd || Ch->draw_mc) && event->y > Ch->zeroy ) {
                switch ( event->button ) {
                case 1:
                        if ( !(event->state & GDK_MODIFIER_MASK) )
                                SF.set_cur_vpage( (event->x / SF.da_wd) * SF.total_vpages());
                        // will eventually call set_cur_vpage(), which will do redraw
                    break;
                case 2:
                        Ch->draw_psd_bands = !Ch->draw_psd_bands;
                        gtk_widget_queue_draw( wid);
                    break;
                case 3:
                        Ch->update_power_menu_items();
                        gtk_menu_popup(
                                SF.iiSFPower,
                                NULL, NULL, NULL, NULL, 3, event->time);
                    break;
                }

        } else if ( Ch->type() == sigfile::SChannel::TType::emg &&
                    Ch->draw_emg && event->y > Ch->zeroy ) {
                switch ( event->button ) {
                case 1:
                        SF.set_cur_vpage( (event->x / SF.da_wd) * SF.total_vpages());
                    break;
                default:
                    break;
                }

        } else {
                double cpos = SF.time_at_click( event->x);
                bool in_selection =
                        agh::alg::overlap(
                                Ch->selection_start_time, Ch->selection_end_time,
                                cpos, cpos);

                switch ( event->button ) {
                case 2:
                        Ch->signal_display_scale =
                                agh::alg::calibrate_display_scale(
                                        Ch->draw_filtered_signal ? Ch->signal_filtered : Ch->signal_original,
                                        SF.vpagesize() * Ch->samplerate() * min (Ch->crecording.full_pages(), (size_t)10),
                                        SF.interchannel_gap / 2);
                        if ( event->state & GDK_CONTROL_MASK )
                                for ( auto& H : SF.channels )
                                        H.signal_display_scale = Ch->signal_display_scale;

                        gtk_widget_queue_draw( wid);
                    break;

                case 3:
                        if ( (event->state & GDK_MOD1_MASK && SF.n_hidden > 0) ||
                             !(SF.n_hidden < (int)SF.channels.size()) )
                                gtk_menu_popup( SF.iiSFPageHidden,
                                                NULL, NULL, NULL, NULL, 3, event->time);
                        else {
                                Ch->update_channel_menu_items( event->x);
                                Ch->update_power_menu_items();
                                gtk_menu_popup(
                                        in_selection ? SF.iiSFPageSelection : SF.iiSFPage,
                                        NULL, NULL, NULL, NULL, 3, event->time);
                        }
                    break;

                case 1:
                        if ( event->state & GDK_MOD1_MASK ) {
                                if ( in_selection ) {
                                        SF.moving_selection_handle_offset =
                                                cpos - Ch->selection_start_time;
                                        SF.mode = SScoringFacility::TMode::moving_selection;
                                } else {
                                        SF.event_y_when_shuffling = event->y;
                                        SF.zeroy_before_shuffling = Ch->zeroy;
                                        SF.mode = SScoringFacility::TMode::shuffling_channels;
                                }
                        } else {
                                SF.mode = SScoringFacility::TMode::marking;
                                Ch->marquee_mstart = Ch->marquee_mend = event->x;
                        }
                        gtk_widget_queue_draw( wid);
                    break;
                }
        }
        return TRUE;
}



namespace {
inline double
timeval_elapsed( const struct timeval &x, const struct timeval &y)
{
        return y.tv_sec - x.tv_sec
                + 1e-6 * (y.tv_usec - x.tv_usec);
}
}

gboolean
daSFMontage_motion_notify_event_cb(
        GtkWidget *wid,
        GdkEventMotion *event,
        const gpointer userdata)
{
        auto& SF = *(SScoringFacility*)userdata;
        if ( SF.mode == SScoringFacility::TMode::showing_ics )
                return TRUE;

        static struct timeval last_page_flip = {0, 0};
        if ( last_page_flip.tv_sec == 0 )
                gettimeofday( &last_page_flip, NULL);

        switch ( SF.mode ) {

        case SScoringFacility::TMode::shuffling_channels:
        {
                SF.using_channel->zeroy = SF.zeroy_before_shuffling + (event->y - SF.event_y_when_shuffling);
                gtk_widget_queue_draw( wid);
        }
        break;

        case SScoringFacility::TMode::marking:
        {
                if ( SF.channel_near( event->y) != SF.using_channel ) // user has dragged too much vertically
                        return TRUE;
                SF.using_channel->marquee_mend = event->x;

                struct timeval currently;
                gettimeofday( &currently, NULL);
                if ( (int)event->x > SF.da_wd && SF.cur_vpage() < SF.total_vpages()-1 ) {
                        if ( timeval_elapsed( last_page_flip, currently) > .4 ) {
                                // x (1+2a) = y
                                SF.using_channel->marquee_mstart -= SF.da_wd / (1. + 2*SF.skirting_run_per1);
                                SF.set_cur_vpage( SF.cur_vpage()+1);
                                gettimeofday( &last_page_flip, NULL);
                        }
                } else if ( (int)event->x < 0 && SF.cur_vpage() > 0 ) {
                        if ( timeval_elapsed( last_page_flip, currently) > .4 ) {
                                SF.using_channel->marquee_mstart += SF.da_wd / (1. + 2*SF.skirting_run_per1);
                                SF.set_cur_vpage( SF.cur_vpage()-1);
                                gettimeofday( &last_page_flip, NULL);
                        }
                }

                SF.using_channel->marquee_to_selection(); // to be sure, also do it on button_release
                if ( event->state & GDK_SHIFT_MASK )
                        for( auto &H : SF.channels )
                                if ( &H != SF.using_channel ) {
                                        H.marquee_mstart = SF.using_channel->marquee_mstart;
                                        H.marquee_mend = event->x;
                                        H.marquee_to_selection();
                                }
                gtk_widget_queue_draw( wid);
        }
        break;

        case SScoringFacility::TMode::moving_selection:
        {
                auto    new_start_time = SF.time_at_click( event->x) - SF.moving_selection_handle_offset,
                        new_end_time = new_start_time + (SF.using_channel->selection_end_time - SF.using_channel->selection_start_time);
                auto& H = *SF.using_channel;
                // reposition marquee
                H.marquee_mstart =
                        (new_start_time - SF.cur_xvpage_start()) / SF.xvpagesize() * SF.da_wd;
                H.marquee_mend =
                        (new_end_time - SF.cur_xvpage_start()) / SF.xvpagesize() * SF.da_wd;

                H.marquee_to_selection(); // to be sure, also do it on button_release
                H.put_selection( H.selection_start, H.selection_end);

                gtk_widget_queue_draw( wid);
        }
        break;

        default:
        break;
        }

        if ( SF.draw_crosshair ) {
                SF.crosshair_at = event->x;
                SF.crosshair_at_time = SF.time_at_click( event->x);
                gtk_widget_queue_draw( wid);
        }

        if ( SF.mode == SScoringFacility::TMode::scoring ) {
                gtk_label_set_text(
                        SF.lSFOverChannel,
                        SF.channel_near( event->y) -> name());
        } else
                gtk_label_set_text( SF.lSFOverChannel, "");

      // current pos
        SF.draw_current_pos( event->x);

        return TRUE;
}


gboolean
daSFMontage_leave_notify_event_cb(
        GtkWidget*,
        GdkEventMotion*,
        const gpointer userdata)
{
        auto& SF = *(SScoringFacility*)userdata;
        gtk_label_set_text( SF.lSFOverChannel, "");
        SF.draw_current_pos( NAN);
        return TRUE;
}


gboolean
daSFMontage_button_release_event_cb(
        GtkWidget *wid,
        GdkEventButton *event,
        const gpointer userdata)
{
        auto& SF = *(SScoringFacility*)userdata;
        if ( SF.mode == SScoringFacility::TMode::showing_ics ||
             SF.mode == SScoringFacility::TMode::showing_remixed )
                return TRUE;

        auto Ch = SF.using_channel;

        if ( SF.channel_near( event->y) != SF.using_channel ) // user has dragged too much vertically
                 return TRUE;

        switch ( event->button ) {
        case 1:
                if ( SF.mode == SScoringFacility::TMode::marking ) {
                        SF.mode = SScoringFacility::TMode::scoring;
                        Ch->put_selection( Ch->selection_start, Ch->selection_end);
                        Ch->selectively_enable_selection_menu_items();
                        Ch->update_channel_menu_items( event->x);
                        if ( fabs(SF.using_channel->marquee_mstart - SF.using_channel->marquee_mend) > 5 ) {
                                gtk_menu_popup( SF.iiSFPageSelection,
                                                NULL, NULL, NULL, NULL, 3, event->time);
                        }
                        gtk_widget_queue_draw( wid);

                } else if ( Ch->type() == sigfile::SChannel::TType::eeg &&
                            (Ch->draw_psd || Ch->draw_mc) && event->y > Ch->zeroy )
                        SF.set_cur_vpage( (event->x / SF.da_wd) * SF.total_vpages());

                else {
                        SF.using_channel->marquee_to_selection();
                        SF.mode = SScoringFacility::TMode::scoring;
                        gtk_widget_queue_draw( wid);
                }
            break;
        case 3:
            break;
        }

        return TRUE;
}





gboolean
daSFMontage_scroll_event_cb(
        GtkWidget *wid,
        GdkEventScroll *event,
        const gpointer userdata)
{
        auto& SF = *(SScoringFacility*)userdata;
        auto Ch = SF.using_channel = SF.channel_near( event->y);

        if ( (event->state & GDK_MOD1_MASK) and
             not (event->state & GDK_SHIFT_MASK) ) {
                switch ( event->direction ) {
                case GDK_SCROLL_UP:
                        if ( SF.da_ht > (int)(SF.channels.size() - SF.n_hidden) * 20 ) {
                                SF.expand_by_factor( (double)(SF.da_ht - 10)/ SF.da_ht);
                                gtk_widget_queue_draw( wid);
                        }
                    break;
                case GDK_SCROLL_DOWN:
                        SF.expand_by_factor( (double)(SF.da_ht + 10) / SF.da_ht);
                        gtk_widget_queue_draw( wid);
                    break;
                default:
                    break;
                }

        } else if ( event->y > Ch->zeroy ) {
                if ( event->state & GDK_SHIFT_MASK && Ch->draw_psd ) {
                        switch ( event->direction ) {
                        case GDK_SCROLL_DOWN:
                                if ( Ch->draw_psd_bands ) {
                                        if ( Ch->psd.focused_band > metrics::TBand::delta ) {
                                                --Ch->psd.focused_band;
                                                if ( Ch->autoscale_profile )
                                                        Ch->update_profile_display_scales();
                                                gtk_widget_queue_draw( wid);
                                        }
                                } else
                                        if ( Ch->psd.from > 0 ) {
                                                Ch->psd.from -= .5;
                                                Ch->psd.upto -= .5;
                                                Ch->get_psd_course();
                                                if ( Ch->autoscale_profile )
                                                        Ch->update_profile_display_scales();
                                                gtk_widget_queue_draw( wid);
                                        }
                                break;
                        case GDK_SCROLL_UP:
                                if ( Ch->draw_psd_bands ) {
                                        if ( Ch->psd.focused_band < Ch->psd.uppermost_band ) {
                                                ++Ch->psd.focused_band;
                                                if ( Ch->autoscale_profile )
                                                        Ch->update_profile_display_scales();
                                                gtk_widget_queue_draw( wid);
                                        }
                                } else {
                                        auto& R = Ch->crecording;
                                        if ( Ch->psd.upto < R.psd_profile.binsize * R.psd_profile.bins() ) {
                                                Ch->psd.from += .5;
                                                Ch->psd.upto += .5;
                                                Ch->get_psd_course();
                                                if ( Ch->autoscale_profile )
                                                        Ch->update_profile_display_scales();
                                                gtk_widget_queue_draw( wid);
                                        }
                                }
                                break;
                        case GDK_SCROLL_LEFT:
                        case GDK_SCROLL_RIGHT:
                        default:
                                break;
                        }
                } else {
                        switch ( event->direction ) {
                        case GDK_SCROLL_DOWN:
                                if ( Ch->draw_psd )
                                        Ch->psd.display_scale /= SF._p.scroll_factor;
                                if ( Ch->draw_swu )
                                        Ch->swu.display_scale /= SF._p.scroll_factor;
                                if ( Ch->draw_mc )
                                        Ch->mc.display_scale  /= SF._p.scroll_factor;
                            break;
                        case GDK_SCROLL_UP:
                                if ( Ch->draw_psd )
                                        Ch->psd.display_scale *= SF._p.scroll_factor;
                                if ( Ch->draw_swu )
                                        Ch->swu.display_scale *= SF._p.scroll_factor;
                                if ( Ch->draw_mc )
                                        Ch->mc.display_scale  *= SF._p.scroll_factor;
                            break;
                        case GDK_SCROLL_LEFT:
                                if ( SF.cur_vpage() > 0 )
                                        SF.set_cur_vpage( SF.cur_vpage() - 1);
                        case GDK_SCROLL_RIGHT:
                                if ( SF.cur_vpage() < SF.total_vpages() )
                                        SF.set_cur_vpage( SF.cur_vpage() + 1);
                            break;
                        default:
                            break;
                        }
                        if ( event->state & GDK_CONTROL_MASK )
                                for ( auto& H : SF.channels ) {
                                        if ( Ch->type() == sigfile::SChannel::TType::eeg &&
                                             H.type() == sigfile::SChannel::TType::eeg ) {
                                                H.psd.display_scale = Ch->psd.display_scale;
                                                H.mc.display_scale  = Ch->mc.display_scale;
                                                H.swu.display_scale = Ch->swu.display_scale;
                                        } else if ( Ch->type() == sigfile::SChannel::TType::emg &&
                                             H.type() == sigfile::SChannel::TType::emg )
                                                H.signal_display_scale = Ch->signal_display_scale;
                                }
                        gtk_widget_queue_draw( wid);
                }
        } else {
                switch ( event->direction ) {
                case GDK_SCROLL_DOWN:
                        Ch->signal_display_scale /= SF._p.scroll_factor;
                        break;
                case GDK_SCROLL_UP:
                        Ch->signal_display_scale *= SF._p.scroll_factor;
                        break;
                default:
                        break;
                }

                if ( event->state & GDK_CONTROL_MASK )
                        for ( auto& H : SF.channels )
                                H.signal_display_scale = Ch->signal_display_scale;
                gtk_widget_queue_draw( wid);
        }

        return TRUE;
}




} // extern "C"

// Local Variables:
// Mode: c++
// indent-tabs-mode: nil
// tab-width: 8
// c-basic-offset: 8
// End:
