#include <gtk/gtksignal.h>
#include <gdk/gdkevents.h>
#include <gdk/gdkkeysyms.h>

#include "mmyth_uicommon.h"
#include "mmyth_epg_grid_widget.h"

#include "gmyth_util.h" 
#include "gmyth_epg.h"

#define PIXELS_HOUR 105
#define PROGRAM_SEPARATION 2

enum {
  SELECTION_UPDATED_SIGNAL,
  LAST_SIGNAL
};

struct _MMythEpgGridWidgetPrivate {
    /* private widget components */
    GtkWidget *epg_channels_vbox;
    GtkWidget *epg_programs_vbox;
 
    GHashTable *service_model_hash;
    
    /* guidegrid attributes */
    gboolean show_favorites;
    gint current_start_channel_id;
    
    time_t current_start_time;
    time_t current_end_time;

    guint selected_channel_index;
    
    /* GList of ProgramInfo for each Channel */
    GList * program_list[MAX_DISPLAY_CHANS];
    GList * channel_list;
    
    GMythEPG *mmyth_epg;
    
    gint DISPLAY_CHANS;
};

static void mmyth_epg_grid_widget_class_init          (MMythEpgGridWidgetClass *klass);
static void mmyth_epg_grid_widget_init                (MMythEpgGridWidget *object);
static void mmyth_epg_grid_widget_private_init        (MMythEpgGridWidgetPrivate *private);
static void mmyth_epg_grid_widget_mount_services      (MMythEpgGridWidget *object, 
                                                       int start_time, int end_time);
static void mmyth_epg_grid_widget_mount_header        (MMythEpgGridWidget *object);
static void mmyth_epg_grid_widget_clicked             (GtkWidget* widget, 
                                                       GdkEventExpose *event, 
                                                       gpointer data);
static GtkWidget *create_event_box_lbl                (gchar *str, int width, 
                                                       const GdkColor *bg_color, 
                                                       const GdkColor *fg_color);

static void mmyth_epg_grid_widget_fill_programinfos(MMythEpgGridWidgetPrivate *private);
static void mmyth_epg_grid_widget_fill_program_row_infos(
                    MMythEpgGridWidgetPrivate *private, 
                    unsigned int chanNum, unsigned int row);

static gint mmyth_epg_grid_widget_signals[LAST_SIGNAL] = { 0 };

G_DEFINE_TYPE(MMythEpgGridWidget, mmyth_epg_grid_widget, GTK_TYPE_EVENT_BOX)
    
static void
mmyth_epg_grid_widget_class_init (MMythEpgGridWidgetClass *klass)
{
  g_type_class_add_private (klass, sizeof (MMythEpgGridWidgetPrivate));
  
  mmyth_epg_grid_widget_signals[SELECTION_UPDATED_SIGNAL] = g_signal_new (
                     "selection_updated",
					 G_TYPE_FROM_CLASS(klass), 
					 G_SIGNAL_RUN_FIRST,
					 0,
                     NULL,
                     NULL,
					 g_cclosure_marshal_VOID__POINTER, 
                     G_TYPE_NONE, 
                     1,
                     G_TYPE_POINTER);
}

static void 
mmyth_epg_grid_widget_private_init (MMythEpgGridWidgetPrivate *private)
{
    time_t cur_time;
    
    g_return_if_fail(private != NULL);     

    private->epg_channels_vbox   = NULL;
    private->epg_programs_vbox   = NULL;
    private->service_model_hash  = NULL;

    private->show_favorites = FALSE;
    private->current_start_channel_id = -1;

    /* Selected the first diplayable channel initially */
    private->selected_channel_index = 0;
        
    /* TODO fix the current start/end time */
    private->current_start_time = time(&cur_time);
    private->current_end_time = time(&cur_time) + 10800;   
    
    private->DISPLAY_CHANS = MAX_DISPLAY_CHANS;
    
    // TODO: Close the epg and unref it in dispose call
    private->mmyth_epg = gmyth_epg_new ();
    if (!gmyth_epg_connect (private->mmyth_epg)) {
    	g_warning ("[%s] Could not connect mysql handler to db", __FUNCTION__);
    	g_object_unref (private->mmyth_epg);
    	private->mmyth_epg = NULL;
    }
}

static void
mmyth_epg_grid_widget_init (MMythEpgGridWidget *mmyth_epg_grid_widget)
{
    MMythEpgGridWidgetPrivate *private = 
        MMYTH_EPG_GRID_WIDGET_GET_PRIVATE(mmyth_epg_grid_widget);
    
    /* init private fields */
    mmyth_epg_grid_widget_private_init(private);

    mmyth_epg_grid_widget->epg_view_model      = NULL;
    mmyth_epg_grid_widget->selected_grid_item  = NULL;

    GtkWidget *epg_event_box = GTK_WIDGET(mmyth_epg_grid_widget);
    gtk_widget_modify_bg(epg_event_box, GTK_STATE_NORMAL, &main_bg_color);
    gtk_widget_set_size_request (epg_event_box, 0, 125);
    
    GtkWidget *epg_main_hbox = gtk_hbox_new (FALSE, 10);
    gtk_container_set_border_width(GTK_CONTAINER (epg_main_hbox), 10);
    
    gtk_container_add (GTK_CONTAINER (epg_event_box),
                       epg_main_hbox);
		        
    /* channels vbox */
    GtkWidget *epg_channels_vbox = gtk_vbox_new (FALSE, 3); 
    private->epg_channels_vbox = epg_channels_vbox; 

    /* programs vbox */
    GtkWidget *epg_programs_vbox = gtk_vbox_new (FALSE, 3);
    private->epg_programs_vbox = epg_programs_vbox;
    
    /* packing start */
    gtk_box_pack_start (GTK_BOX (epg_main_hbox),
	                epg_channels_vbox, FALSE, FALSE, 0);
    gtk_box_pack_start (GTK_BOX (epg_main_hbox),
                        epg_programs_vbox, FALSE, FALSE, 0);

    /* table header (first line) */  
    mmyth_epg_grid_widget_mount_header(mmyth_epg_grid_widget);                 
    
    /* service programs */ 
    /* mount service programs with current time */
    mmyth_epg_grid_widget_mount_services(mmyth_epg_grid_widget, 
                                         private->current_start_time,
                                         private->current_end_time);                                           
}

GtkWidget*
mmyth_epg_grid_widget_new ()
{
  return GTK_WIDGET ( gtk_type_new (mmyth_epg_grid_widget_get_type ()));
}

static void
mmyth_epg_grid_widget_mount_services(MMythEpgGridWidget *mmyth_epg_grid_widget, 
                                     int start_time, int end_time)
{
    GList *proglist;
    GList *channel_list = NULL;
    GMythChannelInfo *channel_info;

    int chanid;
    MMythEpgGridWidgetPrivate *private = 
        MMYTH_EPG_GRID_WIDGET_GET_PRIVATE(mmyth_epg_grid_widget);
	
	// update view_model
	/* FIXME shallow free or recursive? */
	if(mmyth_epg_grid_widget->epg_view_model != NULL) {
		g_list_free(mmyth_epg_grid_widget->epg_view_model);
		mmyth_epg_grid_widget->epg_view_model = NULL;
	}
	
	if(private->service_model_hash != NULL) {
		g_hash_table_destroy(private->service_model_hash);
	}

	private->service_model_hash = g_hash_table_new(NULL, NULL);

    /* fill program infos from db */
    mmyth_epg_grid_widget_fill_programinfos(private);
    
    channel_list = private->channel_list;
 
    /* for each channel get_programs() */
    for (chanid=0; channel_list &&
                   chanid < private->DISPLAY_CHANS; chanid++) {
        proglist = (GList *) private->program_list[chanid];        
    
        channel_info = (GMythChannelInfo *) channel_list->data;
        channel_list = g_list_next(channel_list);        

        /* Service Title*/
        GString *name = NULL;
        if (channel_info->channel_name)
        	name = g_string_new (channel_info->channel_name->str);
		
		GdkColor title_bg_color;
		title_bg_color.red = 5000;
		title_bg_color.green = 9000;
		title_bg_color.blue = 40000;        
		
		GdkColor title_fg_color;
		title_fg_color.red = 60000;
		title_fg_color.green = 60000;
		title_fg_color.blue = 60000;        
		
		GtkWidget *event_box_channel = create_event_box_lbl(
                                                        name->str, 90, 
									                    &title_bg_color, 
									                    &title_fg_color);

        gtk_box_pack_start (GTK_BOX (private->epg_channels_vbox),
		                    event_box_channel, FALSE, FALSE, 0);

  		GtkWidget *epg_line_hbox = gtk_hbox_new (FALSE, 0);      

	    GdkColor bg_color;
	    bg_color.red = 5000;
	    bg_color.green = 30000;
	    bg_color.blue = 60000;        
	
	    GdkColor fg_color;
	    fg_color.red = 60000;
	    fg_color.green = 60000;
	    fg_color.blue = 60000;        
		
		/* Content parsing */
        GList *epg_grid_list = NULL;

        GMythProgramInfo *proginfo;
        int pixel_count = 0;
        for (; proglist; proglist = proglist->next) {
            proginfo = (GMythProgramInfo *) proglist->data;
            
            GString *content_name = proginfo->title;
                    
       	    int initial_time, last_time, duration;

            int schedule_start_time = proginfo->startts;
            int schedule_end_time   = proginfo->endts;

	        initial_time = 
                (schedule_start_time < start_time) ? start_time : schedule_start_time;
            last_time = (schedule_end_time > end_time) ? end_time : schedule_end_time;
	        duration = last_time - initial_time;
		    
            // Verify program time 
            #if 0
			    g_debug ("ServiceID: %d, ScheduleID: %d\n", service->id, schedule->id);
                fprintf (stderr, "program time\nfrom = %d, to = %d\n", 
                         schedule->validFrom, schedule->validTo); 
                
                struct tm loctime;
    
                /* Convert it to local time representation. */
                if (localtime_r((time_t *)&schedule->validFrom, &loctime) == NULL) {
                    g_warning ("localtime_r error in mmyth_epg_grid_widget!\n");
                    return NULL;
                }            
                fprintf (stderr, asctime (&loctime)); 
                
                if (localtime_r((time_t *)&schedule->validTo, &loctime) == NULL) {
                    g_warning ("localtime_r error in mmyth_epg_grid_widget!\n");
                    return NULL;
                } 
                fprintf (stderr, asctime (&loctime)); 
            #endif
    		
    	    /* fprintf(stderr, "duration = %d\n", duration); */
    	    double duration_hour = duration / (double) 3600.0;
    	    /* fprintf(stderr, "duration_hour = %lf\n", duration_hour); */
    		
            int size = PIXELS_HOUR * duration_hour;
                
            /* complete hour */
            /* FIXME: UGLY WRONG HACK TO ALIGN PROGRAM TIME!!!*/
            if(last_time%3600 != 0) {
                size -= PROGRAM_SEPARATION;                
            }
            if(initial_time%3600 != 0) {
                size -= PROGRAM_SEPARATION;                
            }

            pixel_count += size + PROGRAM_SEPARATION;
	        GtkWidget *event_box = create_event_box_lbl(content_name->str, 
							  size, &bg_color, 
							  &fg_color);
							  gtk_widget_add_events(event_box, 
							  GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
            
            /* create EpgGridItem */
            EpgGridItem *epg_grid_item = g_new(EpgGridItem, 1);
            epg_grid_item->proginfo  = proginfo;
            epg_grid_item->event_box = event_box;
            epg_grid_item->object    = mmyth_epg_grid_widget;
            
            epg_grid_list = g_list_prepend(epg_grid_list, (gpointer) epg_grid_item);                                   		                        

     	    gtk_box_pack_start (GTK_BOX (epg_line_hbox),
	                            event_box, FALSE, FALSE, PROGRAM_SEPARATION);
	                                 
   	        g_signal_connect (G_OBJECT (event_box), "button-press-event",
		                      G_CALLBACK (mmyth_epg_grid_widget_clicked), 
                              (gpointer*) epg_grid_list);
        }
#if 0
        printf("chaind = %d!!!!" chanid);fflush(stdout);
#endif        

        if(!epg_grid_list) {
            /* No programs for current channel */
            /* FIXME: size HARDCODED */
            GtkWidget *event_box = create_event_box_lbl("No program list available",
                              PIXELS_HOUR * 3, &bg_color,
                              &fg_color);
                              gtk_widget_add_events(event_box,
                              GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);

            /* create EpgGridItem */
            EpgGridItem *epg_grid_item = g_new(EpgGridItem, 1);
            epg_grid_item->proginfo  = NULL;
            epg_grid_item->event_box = event_box;
            epg_grid_item->object    = mmyth_epg_grid_widget;

            epg_grid_list = g_list_prepend(epg_grid_list, (gpointer) epg_grid_item);
            
            gtk_box_pack_start (GTK_BOX (epg_line_hbox),
	                            event_box, FALSE, FALSE, PROGRAM_SEPARATION);            	
	                                 
   	        g_signal_connect (G_OBJECT (event_box), "button-press-event",
		                      G_CALLBACK (mmyth_epg_grid_widget_clicked),
                              (gpointer*) epg_grid_list);
        }

        epg_grid_list = g_list_reverse(epg_grid_list);
        mmyth_epg_grid_widget->epg_view_model = 
              g_list_append(mmyth_epg_grid_widget->epg_view_model, epg_grid_list);
        
	    gtk_box_pack_start (GTK_BOX (private->epg_programs_vbox),
	                        epg_line_hbox, FALSE, FALSE,  0);                    		    		
    }
}

static void
mmyth_epg_grid_widget_mount_header(MMythEpgGridWidget *mmyth_epg_grid_widget)
{	          	    
    MMythEpgGridWidgetPrivate *private = 
        MMYTH_EPG_GRID_WIDGET_GET_PRIVATE(mmyth_epg_grid_widget);
    
    struct tm hour_tm;
    const gchar name_title[] = "Today";    
    GtkWidget * lbl_title = gtk_label_new(name_title);

    gtk_misc_set_alignment (GTK_MISC(lbl_title), 0.0, 0.5);
    
    gtk_box_pack_start (GTK_BOX (private->epg_channels_vbox),
                        lbl_title, FALSE, FALSE, 0);

	/* hours title line */
    GtkWidget *epg_programs_hours_hbox = gtk_hbox_new (TRUE, 0); 

    if (localtime_r((time_t *)&private->current_start_time, &hour_tm) == NULL) {
        g_warning ("localtime_r error in mmyth_epg_grid_widget!\n");
        return NULL;
    }    

    if (hour_tm.tm_min>30) {
        hour_tm.tm_min = 30;
    } else if (hour_tm.tm_min>0) {
        hour_tm.tm_min = 0;
    }
        
    gchar hour1_str[10];
    strftime(hour1_str, 8, "%H:%M", &hour_tm);
    GtkWidget * lbl_hour1 = gtk_label_new(hour1_str);
    gtk_misc_set_alignment (GTK_MISC(lbl_hour1), 0.0, 0.5);    
	
    hour_tm.tm_hour++;
    gchar hour2_str[10];    
    strftime(hour2_str, 8, "%H:%M", &hour_tm);
    GtkWidget * lbl_hour2 = gtk_label_new(hour2_str);
    gtk_misc_set_alignment (GTK_MISC(lbl_hour2), 0.0, 0.5);        

    hour_tm.tm_hour++;
	gchar hour3_str[10];    
    strftime(hour3_str, 8, "%H:%M", &hour_tm);
    GtkWidget * lbl_hour3 = gtk_label_new(hour3_str);
    gtk_misc_set_alignment (GTK_MISC(lbl_hour3), 0.0, 0.5);        
    
    gtk_box_pack_start (GTK_BOX (epg_programs_hours_hbox),
                        lbl_hour1, TRUE, TRUE, 0);                    
    gtk_box_pack_start (GTK_BOX (epg_programs_hours_hbox),
                        lbl_hour2, TRUE, TRUE, 0);                    
    gtk_box_pack_start (GTK_BOX (epg_programs_hours_hbox),
                        lbl_hour3, TRUE, TRUE, 0); 
 
    gtk_box_pack_start (GTK_BOX (private->epg_programs_vbox),
                        epg_programs_hours_hbox, FALSE, FALSE, 0);                        
}

/******************************************************************************
 *              INTERNAL CALLBACKS FOR STATE CHANGE                           *
 *****************************************************************************/
static void 
mmyth_epg_grid_widget_deselect_service(MMythEpgGridWidget *mmyth_epg_grid_widget)
{
	EpgGridItem *epg_grid_item;
	
  	/* deselect*/
    if(mmyth_epg_grid_widget->selected_grid_item != NULL) {
    	epg_grid_item = 
            (EpgGridItem*) mmyth_epg_grid_widget->selected_grid_item->data;
    	gtk_widget_set_state(GTK_WIDGET(epg_grid_item->event_box), GTK_STATE_NORMAL);
    }            
}

static void 
mmyth_epg_grid_widget_clicked (GtkWidget* widget, 
                               GdkEventExpose *event, gpointer data)
{           
    g_return_if_fail(data != NULL);

    GList *epg_grid_item_list = (GList *) data;
    EpgGridItem *epg_grid_item = (EpgGridItem *) epg_grid_item_list->data;

    /* update the selected service */
    mmyth_epg_grid_widget_update_service( epg_grid_item->object, (GList*) data );       
}

void
mmyth_epg_grid_widget_update_service(MMythEpgGridWidget * object,
                                     GList *selected_grid_list)
{
    g_return_if_fail(object != NULL);
    g_return_if_fail(selected_grid_list != NULL);
		
	EpgGridItem *epg_grid_item = (EpgGridItem *) selected_grid_list->data;

    mmyth_epg_grid_widget_deselect_service(epg_grid_item->object);

    /* updating current selected schedule_item and schedule_list*/
    object->selected_grid_item = selected_grid_list;
    
    /* set state of the event box */
    gtk_widget_set_state(GTK_WIDGET(epg_grid_item->event_box), GTK_STATE_SELECTED);
    /* emit update signal for listeners */
    g_signal_emit(object, 
                  mmyth_epg_grid_widget_signals[SELECTION_UPDATED_SIGNAL],
                  0,
                  (gpointer) epg_grid_item);
}

static GtkWidget * 
create_event_box_lbl(gchar *str, int width, const GdkColor *bg_color, 
                     const GdkColor *fg_color) 
{
    GtkWidget *event_box = gtk_event_box_new();
	GtkWidget *lbl = gtk_label_new(str);
    gtk_label_set_ellipsize(GTK_LABEL(lbl), PANGO_ELLIPSIZE_END);

    gtk_widget_modify_bg(event_box, GTK_STATE_NORMAL, bg_color);    
    gtk_widget_modify_fg(lbl, GTK_STATE_NORMAL, fg_color);    
    
    /* selected colors are const*/
    GdkColor selected_bg_color;
    selected_bg_color.red = 100;
    selected_bg_color.green = 40000;
    selected_bg_color.blue = 100;        

    GdkColor selected_fg_color;
    selected_fg_color.red = 100;
    selected_fg_color.green = 100;
    selected_fg_color.blue = 100;        
            
    gtk_widget_modify_bg(event_box, GTK_STATE_SELECTED, &selected_bg_color);    
    gtk_widget_modify_fg(lbl, GTK_STATE_SELECTED, &selected_fg_color);    
    
    gtk_misc_set_alignment (GTK_MISC(lbl), 0.0, 0.5);    
	gtk_container_add (GTK_CONTAINER (event_box),
                       lbl);
	gtk_widget_set_size_request(event_box, width, -1);
	                       
    return event_box;
}

/******************************************************************************
 *                            METHODS                                         *
 *****************************************************************************/

/* Callback for hardware keys */
gboolean
mmyth_epg_grid_widget_key_press (MMythEpgGridWidget * object, 
                                 GtkWidget * widget, GdkEventKey * event)
{
    MMythEpgGridWidgetPrivate *private = 
        MMYTH_EPG_GRID_WIDGET_GET_PRIVATE(object);
  
    EpgGridItem *epg_grid_item;
    GList *tmp;
    
    /* List of selected_grid_item */
    GList *selected_view_model;
    
    gint channel_index;
    
	if(object->selected_grid_item == NULL) {
        g_warning ("No program selected");
	    return FALSE;
	} 
    
    epg_grid_item = (EpgGridItem*) object->selected_grid_item->data;    
    
    channel_index = private->selected_channel_index;
    
    switch (event->keyval) {
        case GDK_Up:        
            selected_view_model = g_list_nth( object->epg_view_model, channel_index - 1 );
            if(selected_view_model != NULL) {
                private->selected_channel_index = channel_index - 1;
   	            tmp = (GList *) selected_view_model->data;               
                /* TODO: select a better centralized item 
                   currently is picking the 1st or last item */
                if(g_list_next(object->selected_grid_item) == NULL &&
                   g_list_previous(object->selected_grid_item) != NULL) {
                    /* in this case the new selected will be the last */ 
                    tmp = g_list_last(tmp);
                }

	            /* update the selected service */
                mmyth_epg_grid_widget_update_service( object, tmp );
            }    
        return TRUE;         
        case GDK_Down:
            selected_view_model = g_list_nth( object->epg_view_model, channel_index + 1 );            
            if(selected_view_model != NULL) {   	            
                private->selected_channel_index = channel_index + 1;
   	            tmp = (GList *) selected_view_model->data;
                /* TODO: select a better centralized item 
                   currently is picking the 1st or last item */
                if(g_list_next(object->selected_grid_item) == NULL &&
                   g_list_previous(object->selected_grid_item) != NULL) {
                    /* in this case the new selected will be the last */ 
                    tmp = g_list_last(tmp);
                }

	            /* update the selected service */
                mmyth_epg_grid_widget_update_service( object, tmp );
            }
        return TRUE;        
        case GDK_Left:    
            tmp = g_list_previous( object->selected_grid_item );
            if(tmp != NULL) {
	            /* update the selected service */
                mmyth_epg_grid_widget_update_service( object, tmp );
            }
        return TRUE;        
        case GDK_Right:
            tmp = g_list_next( object->selected_grid_item );
            if(tmp != NULL) {
	            /* update the selected service */
                mmyth_epg_grid_widget_update_service( object, tmp );
            }
        return TRUE;        
	    default:
	    return TRUE;
    }
    
    return FALSE;
}

static void 
mmyth_epg_grid_widget_fill_programinfos (MMythEpgGridWidgetPrivate *private)
{
    GList *channels_list = NULL;
    int y;

    if ((private->mmyth_epg != NULL) &&
	   	(gmyth_epg_get_channel_list (private->mmyth_epg, &channels_list) < 0 )) {
			private->channel_list = NULL;
			return;
    }
    
    private->channel_list = channels_list;
    
    for (y = 0; y < private->DISPLAY_CHANS && channels_list; y++) {
        GMythChannelInfo *channel_info = (GMythChannelInfo *) channels_list->data;

        mmyth_epg_grid_widget_fill_program_row_infos(
                            private, channel_info->channel_ID, y);

        channels_list = g_list_next (channels_list);
    }
}

static void 
mmyth_epg_grid_widget_fill_program_row_infos(MMythEpgGridWidgetPrivate *private,
                                             guint chanNum, guint row)
{    
    gint res =  gmyth_epg_get_program_list (private->mmyth_epg,
    					&(private->program_list[row]),
                        chanNum, private->current_start_time, 
                        private->current_end_time);
                        
    if (res < 0) {
    	g_warning ("[%s] Error while retrieving epg programs", __FUNCTION__);
    }
}