/*
 *       File name:  tools/edfed-gtk.cc
 *         Project:  Aghermann
 *          Author:  Andrei Zavada <johnhommer@gmail.com>
 * Initial version:  2011-07-20
 *
 *         Purpose:  EDF header editor utility (using gtk)
 *
 *         License:  GPL
 */


#include <gtk/gtk.h>
#include "project_strings.h"
#include "libsigfile/edf.hh"
#include "libsigfile/typed-source.hh"

#include "aghermann/ui/ui.hh"
using agh::ui::pop_ok_message;


GtkBuilder
        *builder;

GtkDialog
        *wMain;
GtkLabel
        *lLabel,
        *lChannelsNum;

enum TEntry : int {
        PatientID, RecordingID,
        RecordingDate, RecordingTime, Reserved,
        ChannelLabel, ChannelPhysicalDim,
        ChannelPhysicalMin, ChannelPhysicalMax,
        ChannelDigitalMin, ChannelDigitalMax,
        ChannelTransducerType, ChannelFilteringInfo,
        ChannelReserved,
        ChannelSamplesPerRecord,
        _n_entries
};

GtkEntry
        *e[_n_entries];
GtkButton
        *bNext, *bPrevious,
        *bWrite;


int ui_init();
void ui_fini();




sigfile::CEDFFile *Fp;


struct SChannelTmp {
        string  Label, PhysicalDim,
                PhysicalMin, PhysicalMax,
                DigitalMin, DigitalMax,
                TransducerType, FilteringInfo,
                SamplesPerRecord,
                Reserved;
        SChannelTmp( const string& iLabel, const string& iPhysicalDim,
                     const string& iPhysicalMin, const string& iPhysicalMax,
                     const string& iDigitalMin, const string& iDigitalMax,
                     const string& iTransducerType, const string& iFilteringInfo,
                     const string& iSamplesPerRecord,
                     const string& iReserved)
              : Label (iLabel), PhysicalDim (iPhysicalDim),
                PhysicalMin (iPhysicalMin), PhysicalMax (iPhysicalMax),
                DigitalMin (iDigitalMin), DigitalMax (iDigitalMax),
                TransducerType (iTransducerType), FilteringInfo (iFilteringInfo),
                SamplesPerRecord (iSamplesPerRecord),
                Reserved (iReserved)
                {}
};

list<SChannelTmp>
        channels_tmp;
list<SChannelTmp>::iterator
        HTmpi;

void edf_data_to_widgets( const sigfile::CEDFFile&);
void widgets_to_edf_data( sigfile::CEDFFile&);
void current_channel_data_to_widgets();
void widgets_to_current_channel_data();
void sensitize_channel_nav_buttons();

size_t channel_no;




int
main( int argc, char **argv)
{
        int     c;
        while ( (c = getopt( argc, argv, "h")) != -1 )
                switch ( c ) {
                case 'h':
                        printf( "Usage: %s file.edf\n", argv[0]);
                        return 0;
                }

        gtk_init( &argc, &argv);

        const char *fname;
        if ( optind < argc )
                fname = argv[optind];
        else {
                if ( isatty( fileno( stdin)) ) {
                        printf( "Usage: %s file.edf\n", argv[0]);
                        return 0;
                } else {
                        // pop_ok_message( NULL, "Usage: %s file.edf", argv[0]);
                        GtkWidget *f_chooser = gtk_file_chooser_dialog_new(
                                "edfhed-gtk: Choose a file to edit",
                                NULL,
                                GTK_FILE_CHOOSER_ACTION_OPEN,
                                "_Cancel", GTK_RESPONSE_CANCEL,
                                "_Open", GTK_RESPONSE_ACCEPT,
                                NULL);
                        GtkFileFilter *file_filter = gtk_file_filter_new();
                        gtk_file_filter_set_name( file_filter, "EDF recordings");
                        gtk_file_filter_add_pattern( file_filter, "*.edf");
                        gtk_file_chooser_add_filter( (GtkFileChooser*)f_chooser, file_filter);

                        if ( gtk_dialog_run( GTK_DIALOG (f_chooser)) == GTK_RESPONSE_ACCEPT )
                                fname = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER (f_chooser));
                        else
                                return 0;
                        gtk_widget_destroy( f_chooser);
                }
        }

        if ( ui_init() ) {
                pop_ok_message( NULL, "UI failed to initialise", "Your install is broken.");
                return 2;
        }

        try {
                auto F = sigfile::CEDFFile (
                        fname,
                        sigfile::CSource::no_ancillary_files |
                        sigfile::CEDFFile::no_field_consistency_check,
			nullptr);

                channel_no = 0;
                Fp = &F;

                edf_data_to_widgets( F);
                HTmpi = channels_tmp.begin();
                current_channel_data_to_widgets();

                sensitize_channel_nav_buttons();
                if ( gtk_dialog_run( wMain) == -5 ) {
                        // something edited but no Next/Prev pressed to trigger this, so do it now
                        widgets_to_current_channel_data();
                        widgets_to_edf_data( F);
                }
        } catch (invalid_argument ex) {
                pop_ok_message( NULL, "Heh", "%s", ex.what());
        }

        ui_fini();

        return 0;
}





void
edf_data_to_widgets( const sigfile::CEDFFile& F)
{
        gtk_label_set_markup( lLabel, (string ("<b>File:</b> <i>") + F.filename() + "</i>").c_str());
        gtk_entry_set_text( e[PatientID],     agh::str::trim( string (F.header.patient_id,     80)) . c_str());
        gtk_entry_set_text( e[RecordingID],   agh::str::trim( string (F.header.recording_id,   80)) . c_str());
        gtk_entry_set_text( e[RecordingDate], agh::str::trim( string (F.header.recording_date,  8)) . c_str());
        gtk_entry_set_text( e[RecordingTime], agh::str::trim( string (F.header.recording_time,  8)) . c_str());
        gtk_entry_set_text( e[Reserved],      agh::str::trim( string (F.header.reserved,       44)) . c_str());

        for ( auto &h : F.channels ) {
                channels_tmp.emplace_back(
                        agh::str::trim( string (h.header.label, 16)),
                        agh::str::trim( string (h.header.physical_dim, 8)),
                        agh::str::trim( string (h.header.physical_min, 8)),
                        agh::str::trim( string (h.header.physical_max, 8)),
                        agh::str::trim( string (h.header.digital_min,  8)),
                        agh::str::trim( string (h.header.digital_max,  8)),
                        agh::str::trim( string (h.header.transducer_type, 80)),
                        agh::str::trim( string (h.header.filtering_info, 80)),
                        agh::str::trim( string (h.header.samples_per_record, 8)),
                        agh::str::trim( string (h.header.reserved, 32)));
        }
}



void
widgets_to_edf_data( sigfile::CEDFFile& F)
{
        memcpy( F.header.patient_id,     agh::str::pad( gtk_entry_get_text( e[PatientID]),     80).c_str(), 80);
        memcpy( F.header.recording_id,   agh::str::pad( gtk_entry_get_text( e[RecordingID]),   80).c_str(), 80);
        memcpy( F.header.recording_date, agh::str::pad( gtk_entry_get_text( e[RecordingDate]),  8).c_str(),  8);
        memcpy( F.header.recording_time, agh::str::pad( gtk_entry_get_text( e[RecordingTime]),  8).c_str(),  8);
        memcpy( F.header.reserved,       agh::str::pad( gtk_entry_get_text( e[Reserved]),      44).c_str(), 44);

        auto H = channels_tmp.begin();
        for ( auto& h : F.channels ) {
                using agh::str::pad;
                memcpy( h.header.label,              pad( H->Label,           16).c_str(), 16);
                memcpy( h.header.physical_dim,       pad( H->PhysicalDim,      8).c_str(),  8);
                memcpy( h.header.physical_min,       pad( H->PhysicalMin,      8).c_str(),  8);
                memcpy( h.header.physical_max,       pad( H->PhysicalMax,      8).c_str(),  8);
                memcpy( h.header.digital_min,        pad( H->DigitalMin,       8).c_str(),  8);
                memcpy( h.header.digital_max,        pad( H->DigitalMax,       8).c_str(),  8);
                memcpy( h.header.transducer_type,    pad( H->TransducerType,  80).c_str(), 80);
                memcpy( h.header.filtering_info,     pad( H->FilteringInfo,   80).c_str(), 80);
                memcpy( h.header.samples_per_record, pad( H->SamplesPerRecord, 8).c_str(),  8);
                memcpy( h.header.reserved,           pad( H->Reserved,        32).c_str(), 32);
                ++H;
        }
}





void
current_channel_data_to_widgets()
{
        GString *tmp = g_string_new("");
        size_t i = 0;
        for ( auto& H : channels_tmp ) {
                gchar *escaped = g_markup_escape_text( agh::str::trim( H.Label).c_str(), -1);
                if ( i++ == channel_no )
                        g_string_append_printf( tmp, "  <b>%s</b>  ", escaped);
                else
                        g_string_append_printf( tmp, "  %s  ", escaped);
                g_free( escaped);
        }
        gtk_label_set_markup( lChannelsNum, tmp->str);
        g_string_free( tmp, TRUE);
        using agh::str::trim;
        gtk_entry_set_text( e[ChannelLabel],            trim( HTmpi->Label           ).c_str());
        gtk_entry_set_text( e[ChannelPhysicalDim],      trim( HTmpi->PhysicalDim     ).c_str());
        gtk_entry_set_text( e[ChannelPhysicalMin],      trim( HTmpi->PhysicalMin     ).c_str());
        gtk_entry_set_text( e[ChannelPhysicalMax],      trim( HTmpi->PhysicalMax     ).c_str());
        gtk_entry_set_text( e[ChannelDigitalMin],       trim( HTmpi->DigitalMin      ).c_str());
        gtk_entry_set_text( e[ChannelDigitalMax],       trim( HTmpi->DigitalMax      ).c_str());
        gtk_entry_set_text( e[ChannelTransducerType],   trim( HTmpi->TransducerType  ).c_str());
        gtk_entry_set_text( e[ChannelFilteringInfo],    trim( HTmpi->FilteringInfo   ).c_str());
        gtk_entry_set_text( e[ChannelSamplesPerRecord], trim( HTmpi->SamplesPerRecord).c_str());
        gtk_entry_set_text( e[ChannelReserved],         trim( HTmpi->Reserved        ).c_str());
}

void
widgets_to_current_channel_data()
{
        HTmpi->Label            = gtk_entry_get_text( e[ChannelLabel]);
        HTmpi->PhysicalDim      = gtk_entry_get_text( e[ChannelPhysicalDim]);
        HTmpi->PhysicalMin      = gtk_entry_get_text( e[ChannelPhysicalMin]);
        HTmpi->PhysicalMax      = gtk_entry_get_text( e[ChannelPhysicalMax]);
        HTmpi->DigitalMin       = gtk_entry_get_text( e[ChannelDigitalMin]);
        HTmpi->DigitalMax       = gtk_entry_get_text( e[ChannelDigitalMax]);
        HTmpi->TransducerType   = gtk_entry_get_text( e[ChannelTransducerType]);
        HTmpi->FilteringInfo    = gtk_entry_get_text( e[ChannelFilteringInfo]);
        HTmpi->SamplesPerRecord = gtk_entry_get_text( e[ChannelSamplesPerRecord]);
        HTmpi->Reserved         = gtk_entry_get_text( e[ChannelReserved]);
}


bool
validate_all_widgets()
{
        const char *str, *p;
        struct tm ts;
        str = gtk_entry_get_text( e[RecordingDate]);
        p = strptime( str, "%d.%m.%y", &ts);
        if ( strlen(str) != 8 || p == NULL || *p != '\0' )
                return false;
        str = gtk_entry_get_text( e[RecordingTime]);
        p = strptime( str, "%H.%M.%S", &ts);
        if ( strlen(str) != 8 || p == NULL || *p != '\0' )
                return false;

        char *tail;
        double p_min, p_max;
        int d_min, d_max;
        if ( (p_min = strtod( gtk_entry_get_text( e[ChannelPhysicalMin]), &tail), *tail != '\0') )
                return false;
        if ( (p_max = strtod( gtk_entry_get_text( e[ChannelPhysicalMax]), &tail), *tail != '\0') )
                return false;
        if ( (d_min = strtoul( gtk_entry_get_text( e[ChannelDigitalMin]), &tail, 10), *tail != '\0') )
                return false;
        if ( (d_max = strtoul( gtk_entry_get_text( e[ChannelDigitalMax]), &tail, 10), *tail != '\0') )
                return false;
        if ( p_min >= p_max || d_min >= d_max )
                return false;

        return true;
}





extern "C" void
bNext_clicked_cb( GtkButton*, gpointer)
{
        if ( next(HTmpi) != channels_tmp.end() ) {
                widgets_to_current_channel_data();
                ++HTmpi;
                ++channel_no;
                current_channel_data_to_widgets();
        }
        sensitize_channel_nav_buttons();
}

extern "C" void
bPrevious_clicked_cb( GtkButton*, gpointer)
{
        if ( HTmpi != channels_tmp.begin() ) {
                widgets_to_current_channel_data();
                --HTmpi;
                --channel_no;
                current_channel_data_to_widgets();
        }
        sensitize_channel_nav_buttons();
}


extern "C" void
inserted_text_cb( GtkEntryBuffer *,
                  guint           ,
                  gchar          *,
                  guint           ,
                  gpointer        )
{
        gtk_widget_set_sensitive( (GtkWidget*)bWrite, validate_all_widgets());
}
extern "C" void
deleted_text_cb( GtkEntryBuffer *,
                 guint           ,
                 guint           ,
                 gpointer        )
{
        gtk_widget_set_sensitive( (GtkWidget*)bWrite, validate_all_widgets());
}

void
sensitize_channel_nav_buttons()
{
        gtk_widget_set_sensitive( (GtkWidget*)bNext, not (next(HTmpi) == channels_tmp.end()));
        gtk_widget_set_sensitive( (GtkWidget*)bPrevious, not (HTmpi == channels_tmp.begin()));
}


int
ui_init()
{
        GResource
                *gresource
                = g_resource_load(
                        PACKAGE_DATADIR "/" PACKAGE "/" AGH_UI_GRESOURCE_FILE,
                        NULL);
        if ( !gresource ) {
                fprintf( stderr, "Bad or missing " PACKAGE_DATADIR "/" PACKAGE "/" AGH_UI_GRESOURCE_FILE);
                return -1;
        }
        g_resources_register( gresource);

      // load glade
        builder = gtk_builder_new();
        if ( !gtk_builder_add_from_resource( builder, "/org/gtk/aghermann/edfhed.glade", NULL) ) {
                pop_ok_message( NULL, "Failed to load UI description file", "Your install is broken");
                return -1;
        }
        gtk_builder_connect_signals( builder, NULL);

#define AGH_GBGETTRY(A)                                   \
        do { if (!(A = (GtkEntry*)(gtk_builder_get_object( builder, #A)))) \
		     throw runtime_error ("Missing widget " #A);	\
	} while (0);

        AGH_GBGETOBJ (wMain);
	AGH_GBGETOBJ (lLabel);
             // begad, glade indeed has these widgets named like so: "e[PatientID]"!
	AGH_GBGETTRY (e[PatientID]);
	AGH_GBGETTRY (e[RecordingID]);
	AGH_GBGETTRY (e[RecordingDate]);
	AGH_GBGETTRY (e[RecordingTime]);
	AGH_GBGETTRY (e[Reserved]);
	AGH_GBGETTRY (e[ChannelLabel]);
	AGH_GBGETTRY (e[ChannelPhysicalDim]);
	AGH_GBGETTRY (e[ChannelPhysicalMin]);
	AGH_GBGETTRY (e[ChannelPhysicalMax]);
	AGH_GBGETTRY (e[ChannelDigitalMin]);
	AGH_GBGETTRY (e[ChannelDigitalMax]);
	AGH_GBGETTRY (e[ChannelTransducerType]);
	AGH_GBGETTRY (e[ChannelFilteringInfo]);
	AGH_GBGETTRY (e[ChannelReserved]);
	AGH_GBGETTRY (e[ChannelSamplesPerRecord]);
	AGH_GBGETOBJ (lChannelsNum);
	AGH_GBGETOBJ (bNext);
	AGH_GBGETOBJ (bPrevious);
	AGH_GBGETOBJ (bWrite);

        for ( int i = 0; i < _n_entries; ++i ) {
#pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
                g_signal_connect( gtk_entry_get_buffer( e[i]),
                                  "deleted-text", (GCallback)deleted_text_cb,
                                  (gpointer)i);
                g_signal_connect( gtk_entry_get_buffer( e[i]),
                                  "inserted-text", (GCallback)inserted_text_cb,
                                  (gpointer)i);
#pragma GCC diagnostic warning "-Wint-to-pointer-cast"
        }

        return 0;
}


void
ui_fini()
{
        // gtk_widget_destroy
        g_object_unref( builder);
}


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