// -*- C++ -*-
//
//  This file is part of Siena, a wide-area event notification system.
//  See http://www.cs.colorado.edu/serl/dot/siena.html
//
//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
//  See the file AUTHORS for full details. 
//
//  Copyright (C) 1998-1999 University of Colorado
//
//  This program 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 2
//  of the License, or (at your option) any later version.
//
//  This program 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 this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
//  USA, or send email to serl@cs.colorado.edu.
//
//
// $Id: smon.cc,v 1.7 2002/06/12 00:48:16 carzanig Exp $
//
#include <smonconf.h>

using namespace std;

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>

#include <string.h>
#include <errno.h>

#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <gtk/gtk.h>

#ifdef __cplusplus
extern "C" {
#endif
#include "interface.h"
#include "support.h"
#ifdef __cplusplus
};
#endif

#include "graph.h"
#include "Icons.h"

#include "smon.h"

#define			SMON_CMDTIMOUT_MSEC	1000

GtkDrawingArea *	GraphDrawingarea;
GtkCList *		LogList;
GtkToggleButton *	PlayButton;
GtkSpinButton *		PortNumberSpin;
GtkWidget *		ClosePortButton;
GtkWidget *		OpenPortButton;
GtkStatusbar *		StatusBar;

GtkSpinButton *		ResizeXSpinbutton;
GtkSpinButton *		ResizeYSpinbutton;

GtkSpinButton *		AnimTimeSpin;
GtkSpinButton *		AnimStepsSpin;

GtkText *		ErrorLogText;

gint			StatusBarContext;

unsigned int		LogLength = 0;
unsigned int		LogCursor = 0;

//
// counter of active (in active display) commands.  At the moment, I'm
// using this as a lock.
//
unsigned int		CmdSpin = 0;
//
// pointer to the most recently processed command, or to the command
// that is being processed right now
//
unsigned int		CmdCursor = 0;

int			PortFD = -1;

GtkLabel *		ErrorLabel;
GdkFont *		ErrorFont;
GdkColormap *		ErrorColormap;
GdkColor		ErrorFgColor;
const char *		ErrorFontName = "-adobe-helvetica-medium-r-normal-*-10-*-*-*-p-*-iso8859-1";

GtkWidget *		AboutDialog;
GtkWidget *		OpenPortDialog;
GtkWidget *		ZoomResizeDialog;
GtkWidget *		AnimParamDialog;
GtkWidget *		ErrorMessagesDialog;

GtkFileSelection *	SaveSelection;
GtkFileSelection *	OpenSelection;

GtkWidget *		ConfirmQuitDialog;
GtkWidget *		ErrorDialog;

#include "pixmaps/smon.xpm"

static void set_pixmap_data(GtkPixmap * pix_widget, gchar ** data); 
static bool smon_playing();
static void smon_read_from_port(gpointer data, gint source, 
				GdkInputCondition condition);

extern "C" void smon_open_port(unsigned short pnumber);
static void smon_close_port();

static void smon_process_command();
static void smon_done_processing_command();

#define SMON_LOG_MAX_LEN	200

void smon_log_error(const char * s, int len) {
    static guint lengths[SMON_LOG_MAX_LEN];
    static int curr_pos = 0;
    static int first_pos = 0;

    lengths[curr_pos] = len + 1;
    curr_pos = (curr_pos + 1) % SMON_LOG_MAX_LEN;
    if (curr_pos == first_pos) {
	gtk_text_set_point(ErrorLogText, 0);
	gtk_text_forward_delete(ErrorLogText, lengths[first_pos]);
	first_pos = (first_pos + 1) % SMON_LOG_MAX_LEN;
    }
    
    gtk_text_set_point(ErrorLogText, gtk_text_get_length(ErrorLogText));
    gtk_text_insert(ErrorLogText, ErrorFont, &ErrorFgColor, NULL, 
		    s, len);
    gtk_text_insert(ErrorLogText, ErrorFont, &ErrorFgColor, NULL, 
		    "\n", 1);
    gtk_widget_show(ErrorMessagesDialog); 
}

extern "C" void smon_signal_error(const char * msg) {
    gtk_label_set_text(ErrorLabel, msg);
    gtk_widget_show(ErrorDialog);
}

extern "C" void smon_create_gui() {
    GtkWidget *smon_window;
    GtkWidget *save_selection;
    GtkWidget *open_selection;
    GtkWidget *about_dialog;
    GtkWidget *zoom_resize_window;
    GtkWidget *open_port_dialog;

    smon_window = create_smon_window();
    //
    // graph
    //
    GraphDrawingarea = GTK_DRAWING_AREA(lookup_widget(smon_window, 
						      "graph_drawingarea"));
    //
    // you *must* call graph_init before you do gtk_widget_show
    //
    graph_init(GraphDrawingarea, smon_done_processing_command);
    gtk_widget_show(smon_window);

    SaveSelection = GTK_FILE_SELECTION(create_save_selection());
    OpenSelection = GTK_FILE_SELECTION(create_open_selection());
    AboutDialog = create_about_dialog();

    GtkPixmap * about_pixmap;
    about_pixmap = (GtkPixmap*)lookup_widget(AboutDialog, "about_pixmap");

    set_pixmap_data(about_pixmap, smon_xpm);

    ZoomResizeDialog = create_zoom_resize_window();
    OpenPortDialog = create_open_port_dialog();
    ConfirmQuitDialog = create_quit_dialog();
    ErrorDialog = create_error_dialog();
    AnimParamDialog = create_anim_param_window();
    ErrorMessagesDialog = create_error_log_window();

    LogList = GTK_CLIST(lookup_widget(smon_window, "log_clist"));
    ErrorLabel = GTK_LABEL(lookup_widget(ErrorDialog, "error_label"));
    PlayButton = GTK_TOGGLE_BUTTON(lookup_widget(smon_window, 
						 "controls_play_button"));
    PortNumberSpin = GTK_SPIN_BUTTON(lookup_widget(OpenPortDialog,
						   "open_port_number_spin"));
    ClosePortButton = lookup_widget(smon_window, "close_port_b");
    OpenPortButton = lookup_widget(smon_window, "open_port_b");
    StatusBar = GTK_STATUSBAR(lookup_widget(smon_window, "smon_statusbar"));
    StatusBarContext = gtk_statusbar_get_context_id(StatusBar, "normal");
    ResizeXSpinbutton = GTK_SPIN_BUTTON(lookup_widget(ZoomResizeDialog,
						      "resize_x_spinbutton"));
    ResizeYSpinbutton = GTK_SPIN_BUTTON(lookup_widget(ZoomResizeDialog,
						      "resize_y_spinbutton"));
    AnimTimeSpin = GTK_SPIN_BUTTON(lookup_widget(AnimParamDialog,
						 "anim_time_spinbutton"));
    AnimStepsSpin = GTK_SPIN_BUTTON(lookup_widget(AnimParamDialog,
						 "anim_steps_spinbutton"));

    ErrorLogText = GTK_TEXT(lookup_widget(ErrorMessagesDialog, 
					  "error_log_text"));

    ErrorFont = gdk_font_load(ErrorFontName);
    if (ErrorFont == NULL) {
	if ((ErrorFont = gdk_font_load("fixed")) == NULL) {
	    g_error("couldn't allocate default font");
	}
    }

    ErrorColormap = gtk_widget_get_colormap(ErrorMessagesDialog);
    ErrorFgColor.red = 0x8000;
    ErrorFgColor.green = 0x0000;
    ErrorFgColor.blue = 0x0000;
    if (!gdk_color_alloc(ErrorColormap, &ErrorFgColor)) {
	g_error("couldn't allocate logging foreground color");
    }

    icons_init(GTK_WIDGET(GraphDrawingarea));

    graph_set_logger_function(smon_log_error);
}

static void set_pixmap_data(GtkPixmap * pix_widget, gchar ** data) {
    GdkPixmap * pixmap;
    GdkBitmap * mask;
    GdkColormap * colormap;

    colormap = gtk_widget_get_colormap((GtkWidget *)pix_widget);
    pixmap = gdk_pixmap_colormap_create_from_xpm_d((GdkWindow *)0, 
						   colormap, & mask, 
						   (GdkColor *)0, data);
    gtk_pixmap_set(pix_widget, pixmap, mask);
    gdk_pixmap_unref(pixmap);
    gdk_bitmap_unref(mask);
}

static bool smon_playing() {
    return gtk_toggle_button_get_active(PlayButton);
}

static void smon_read_from_port(gpointer data, gint source, 
				GdkInputCondition condition) {
    if (condition & GDK_INPUT_EXCEPTION) {
	smon_signal_error("Error while reading from port");
	return;
    }
    //
    // it must be a GDK_INPUT_READ condition
    //
    char buf[SMON_MAX_CMDLINE_LEN];
    ssize_t res;

    res = recvfrom(source, buf, SMON_MAX_CMDLINE_LEN, 0, NULL, 0);
    if (res < 0) {
	smon_signal_error("Error while receiving log message");
	return;
    }
    buf[res]='\0';
    char *items[2];
    items[0] = buf;
    items[1] = NULL;

    gtk_clist_append(LogList, items);
    ++LogLength;
    smon_process_command();
}

static gint OpenPortMessageId;

static void smon_close_port() {
    if (PortFD >= 0) {
	gdk_input_remove(PortFD);
	close(PortFD);
	PortFD = -1;
	gtk_widget_set_sensitive(ClosePortButton, 0);
	gtk_widget_set_sensitive(OpenPortButton, 1);
	gtk_statusbar_remove(StatusBar, StatusBarContext, OpenPortMessageId);
    } 
}

extern "C" void smon_open_port(unsigned short pnumber) {
    smon_close_port();

    struct sockaddr_in addr;
    int on = 1; /* for address reuse */
  
    PortFD = socket(PF_INET, SOCK_DGRAM, 0);
    if (PortFD < 0) {
	ostringstream os;
	os << "Couldn't open UDP port " << pnumber<< "\n"
	   << strerror(errno);
	smon_signal_error(os.str().c_str());
	return;
    }
    ostringstream os;
    os << "Tracing log from from port " <<  pnumber;
    OpenPortMessageId = gtk_statusbar_push(StatusBar, StatusBarContext, 
					   os.str().c_str());

    setsockopt(PortFD, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(pnumber);
  
    if (bind(PortFD, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
	ostringstream os;
	os << "Couldn't bind socket for port " << pnumber << "\n"
	   << strerror(errno);
	smon_signal_error(os.str().c_str());
	smon_close_port();
	return;
    }

    gdk_input_add(PortFD, GDK_INPUT_READ, smon_read_from_port, NULL);
    gtk_widget_set_sensitive(OpenPortButton, 0);
    gtk_widget_set_sensitive(ClosePortButton, 1);
}

static gint CommandMessageId;
static void smon_done_processing_command() {
    if (CmdSpin == 0) return;
    --CmdSpin;
    gtk_statusbar_remove(StatusBar, StatusBarContext, CommandMessageId);
    smon_process_command();
}

static void smon_process_command() {
    static char *cmd_delim = "\t ";
    static char command[SMON_MAX_CMDLINE_LEN];
    static char *args[SMON_MAX_ARGS_LEN];

    if (CmdSpin == 0 && smon_playing() && LogCursor < LogLength ) {
	++CmdSpin;
	char * text;
	gtk_clist_get_text(LogList, LogCursor, 0, &text);
	gtk_clist_moveto(LogList, LogCursor, 0, 0.5, 0.5);
	++LogCursor;
	if (LogLength > LogCursor) {
	    gtk_clist_select_row(LogList, LogCursor, 0);    
	} else {
	    gtk_clist_unselect_all(LogList);
	}

	CommandMessageId = gtk_statusbar_push(StatusBar, StatusBarContext, 
					      text);

	strncpy(command, text, SMON_MAX_CMDLINE_LEN);
	command[SMON_MAX_CMDLINE_LEN - 1] = '\0';
	int i = 0;
	args[i] = strtok(command, cmd_delim);
	while(args[i] != NULL && ++i < SMON_MAX_ARGS_LEN)
	    args[i] = strtok(NULL, cmd_delim);

	if (i > 0) {
	    graph_display_command(i, args);
	} else {
	    smon_done_processing_command();
	}
    }
}

//
// callbacks need to have C linkage
//
extern "C" {

void on_error_close_button_clicked(GtkButton *button, gpointer user_data) {
    gtk_widget_hide(ErrorDialog);
}

void on_zoom_resize_b_activate(GtkMenuItem *menuitem, gpointer user_data) {
    gtk_widget_show(ZoomResizeDialog);
}

void on_new_b_activate(GtkMenuItem *menuitem, gpointer user_data) {
    gtk_clist_clear(LogList);
    LogLength = 0;
    LogCursor = 0;
}

void on_open_b_activate(GtkMenuItem *menuitem, gpointer user_data) {
    gtk_widget_show(GTK_WIDGET(OpenSelection));
}

void on_open_port_b_activate(GtkMenuItem *menuitem, gpointer user_data) {
    gtk_widget_show(OpenPortDialog);
}

void on_close_port_b_activate(GtkMenuItem *menuitem, gpointer user_data) {
    smon_close_port();
}

void on_save_b_activate(GtkMenuItem *menuitem, gpointer user_data) {

}

void on_save_as_b_activate(GtkMenuItem *menuitem, gpointer user_data) {
    gtk_widget_show(GTK_WIDGET(SaveSelection));
}

void on_exit_b_activate(GtkMenuItem *menuitem, gpointer user_data) {
    gtk_widget_show(ConfirmQuitDialog);
}

void on_notifications_b_activate(GtkMenuItem *menuitem, gpointer user_data) {

}

void on_subscriptions_b_activate(GtkMenuItem *menuitem, gpointer user_data) {

}

void on_unsubscriptions_b_activate(GtkMenuItem *menuitem, gpointer user_data) {

}

void on_advertisements_b_activate(GtkMenuItem *menuitem, gpointer user_data) {

}

void on_unadvertisements_b_activate(GtkMenuItem *menuitem, 
				    gpointer user_data) {

}

void on_labels_b_activate(GtkMenuItem *menuitem, gpointer user_data) {

}

void on_about_b_activate(GtkMenuItem *menuitem, gpointer user_data) {
    gtk_widget_show(AboutDialog);
}

void on_log_list_realize(GtkWidget *widget, gpointer user_data) {

}

void on_save_ok_button_clicked(GtkButton *button, gpointer user_data) {
    gtk_widget_hide(GTK_WIDGET(SaveSelection));

    const char * fname = gtk_file_selection_get_filename(SaveSelection);

    ofstream f(fname);

    if (!f) {
	string emsg;
	emsg = "Couldn't open output file\n";
	emsg += fname;
	smon_signal_error(emsg.c_str());
    } else {
	char * text;
	for (unsigned int row = 0; row < LogLength; ++row) {
	    gtk_clist_get_text(LogList, row, 0, &text);
	    f << text << endl;
	    if (!f) {
		string emsg;
		emsg = "Error while writing output file\n";
		emsg += fname;
		smon_signal_error(emsg.c_str());
		break;
	    }
	}
    }
}

void on_save_cancel_button_clicked(GtkButton *button, gpointer user_data) {
    gtk_widget_hide(GTK_WIDGET(SaveSelection));
}

void on_open_ok_button_clicked(GtkButton *button, gpointer user_data) {
    gtk_widget_hide(GTK_WIDGET(OpenSelection));

    const char * fname = gtk_file_selection_get_filename(OpenSelection);

    ifstream f(fname);

    if (!f) {
	string emsg;
	emsg = "Couldn't open input file\n";
	emsg += fname;
	smon_signal_error(emsg.c_str());
    } else {
	char buf[SMON_MAX_CMDLINE_LEN];
	char *items[2];
	items[0] = buf;
	items[1] = NULL;

  	gtk_clist_clear(LogList);
	LogLength = 0;
	LogCursor = 0;

	gtk_clist_freeze(LogList);
	while(f.getline(buf, SMON_MAX_CMDLINE_LEN)) {
	    gtk_clist_append(LogList, items);
	    ++LogLength;
	}
	gtk_clist_thaw(LogList);
	if (!f.eof()) {
	    string emsg;
	    emsg = "Error while reading file\n";
	    emsg += fname;
	    smon_signal_error(emsg.c_str());    
	}
    }
}

void on_open_cancel_button_clicked(GtkButton *button, gpointer user_data) {
    gtk_widget_hide(GTK_WIDGET(OpenSelection));
}

void on_about_close_button_clicked(GtkButton *button, gpointer user_data) {
    gtk_widget_hide(AboutDialog);
}

void on_resize_lock_button_toggled(GtkToggleButton *togglebutton, 
				   gpointer user_data) {

}

void on_resize_x_spinbutton_changed(GtkEditable *editable, 
				    gpointer user_data) {

}

void on_resize_y_spinbutton_changed(GtkEditable *editable, 
				    gpointer user_data) {

}

void on_resize_apply_button_clicked(GtkButton *button, gpointer user_data) {
    graph_set_size(gtk_spin_button_get_value_as_int(ResizeXSpinbutton),
		   gtk_spin_button_get_value_as_int(ResizeYSpinbutton));
}

void on_resize_reset_button_clicked(GtkButton *button, gpointer user_data) {
    gint x,y;
    graph_get_size(x, y);
    gtk_spin_button_set_value(ResizeXSpinbutton, x);
    gtk_spin_button_set_value(ResizeYSpinbutton, y);
}

void on_zoom_m10_button_clicked(GtkButton *button, gpointer user_data) {

}

void on_zoom_m5_button_clicked(GtkButton *button, gpointer user_data) {

}

void on_zoom_p5_button_clicked(GtkButton *button, gpointer user_data) {

}

void on_zoom_p10_button_clicked(GtkButton *button, gpointer user_data) {

}

void on_zoom_resize_close_button_clicked(GtkButton *button, 
					 gpointer user_data) {
    gtk_widget_hide(ZoomResizeDialog);
}

void on_open_port_ok_button_clicked(GtkButton *button, gpointer user_data) {
    gtk_widget_hide(OpenPortDialog);
    smon_open_port(gtk_spin_button_get_value_as_int(PortNumberSpin));
}

void on_open_port_cancel_button_clicked(GtkButton *button, 
					gpointer user_data) {
    gtk_widget_hide(OpenPortDialog);
}

void on_quit_quit_button_clicked(GtkButton *button, gpointer user_data) {
    gtk_widget_hide(ConfirmQuitDialog);
    gtk_main_quit();
}

void on_quit_cancel_button_clicked (GtkButton *button, gpointer user_data) {
    gtk_widget_hide(ConfirmQuitDialog);
}

void on_controls_begin_button_clicked(GtkButton *button, gpointer user_data) {
    gtk_clist_select_row(LogList, LogCursor = 0, 0);
    gtk_clist_moveto(LogList, LogCursor, 0, 0.5, 0.5);
}

void on_controls_prev_button_clicked(GtkButton *button, gpointer user_data) {
    if (LogCursor > 0) {
	gtk_clist_select_row(LogList, --LogCursor, 0);
	gtk_clist_moveto(LogList, LogCursor, 0, 0.5, 0.5);
    }
}

void on_controls_succ_button_clicked(GtkButton *button, gpointer user_data) {
    if (LogCursor < LogLength) {
	++LogCursor;
	if (LogLength > LogCursor) {
	    gtk_clist_moveto(LogList, LogCursor, 0, 0.5, 0.5);
	    gtk_clist_select_row(LogList, LogCursor, 0);    
	} else {
	    gtk_clist_unselect_all(LogList);
	}
    }
}

void on_controls_end_button_clicked(GtkButton *button, gpointer user_data) {
    if (LogLength > 0) {
	gtk_clist_select_row(LogList, LogCursor = LogLength - 1, 0);
	gtk_clist_moveto(LogList, LogCursor, 0, 0.5, 0.5);
    }
}

void on_log_clist_select_row(GtkCList * list, gint row, gint column,
			     GdkEvent *event, gpointer data) {
    LogCursor = row;
}

void on_controls_play_button_toggled(GtkToggleButton * button,
				     gpointer user_data) {
    if (gtk_toggle_button_get_active(button))
	smon_process_command();
}

void on_commands_b_activate(GtkMenuItem *menuitem, gpointer user_data) {
}

void on_anim_param_b_activate(GtkMenuItem *menuitem, gpointer user_data) {
    gtk_widget_show(AnimParamDialog);
}

void on_anim_param_apply_clicked(GtkButton *button, gpointer user_data) {
    graph_set_anim_params(gtk_spin_button_get_value_as_int(AnimTimeSpin),
			  gtk_spin_button_get_value_as_int(AnimStepsSpin));
}

void on_anim_param_close_clicked(GtkButton *button, gpointer user_data) {
    gtk_widget_hide(AnimParamDialog);
}

void on_error_messages_b_activate(GtkMenuItem *menuitem, gpointer user_data) {
    gtk_widget_show(ErrorMessagesDialog);
}

void on_error_log_close_button_clicked(GtkButton *button, gpointer user_data) {
    gtk_widget_hide(ErrorMessagesDialog);
}

} /* extern "C" */

