/**
   @file

   Robot-Side application with GUI (simulator) to control the robot.
*/

#include <time.h>
#include <math.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <gtk/gtk.h>
#include <glib.h>
#include <unistd.h>
#include <signal.h>
#include <gdk/gdk.h>

#include "../lib/libcomm.h"
#include "guicommon.h"

// Pointer to the Main-Application-Object. Is necessary if a function
// of the GUI is called from a not-gui-object.
GtkWidget *gr_application;

// Pointer to the DrawinArea-Object. Is necessary if a function
// of the DrawingArea is called from a not-gui-object.
GtkWidget *da_global;

// Stores the absolut position of the robot. 
int robot_x = 50, robot_y = 0;

// Configuration for LineMonitor. Stores the Soft- and Hard-Timeout
// and wait-times.
int soft_msec;
int hard_msec;
int wait_msec;

// Indicates the the "system" is shutting down. Do not launch further
// command to the GUI
int shut_down;

// Stores the portnumber on which the server operates. serverport:
// Datalink (local/bind); serverport + 1: Linemonitor (remote/connect).
int serverport;



/**

    @param widget (GtkWidget *) pointer to affected object (for
    example: window).

    @param event (GdkEventM *) pointer to structure which
    describes the event.

    @param data (gpointer) pointer to additional data from gtk.


*/
static gbooleandelete_event(GtkWidget *widget,
			    GdkEvent  *event,
			    gpointer   data) {
  interface_stop(); // ??
  fprintf(stderr, "Shut down...\n");
  gtk_main_quit();
  return FALSE;
}


/** This function will be called be gtk when the window gets a closing
    command, it makes sure that the interface is down and the program
    is finished.

    @param widget (GtkWidget *) pointer to affected object (for
    example: window).

    @param data (gpointer) pointer to additional data from gtk.
 */
void gr_delete_event(GtkWidget *widget, gpointer data) {
  interface_stop();
  shut_down = 1;
  fprintf(stderr, "Shut down...\n");
  gtk_main_quit();
  gdk_window_process_all_updates();
}


/** This function is called if a signal (line C-c, terminate)
    occurs. See in signal() -calls in main() for detals.

    @param sig (int) number of orrured signal
*/
static void finish(int sig) {
  gr_delete_event(gr_application, NULL);
}


/** This function is called by gtk if the drawing area (da) needs to
    be redrawn. For example if it becomes visible.

    @param widget (GtkWidget *) pointer to affected object (in this
    case the drawing area).

    @param data (gpointer) pointer to additional data from gtk.
*/
void gr_da_expose_event(GtkWidget *widget, gpointer data) {
  gui_common_drawrobot(widget, &robot_x, &robot_y, -1, -1, 0);
}



/** This function is called if a button is pressed

    @param widget (GtkWidget *) pointer to affected object (button).

    @param event (GdkEventMotion *) pointer to structure which
    describes the event.
*/

void gr_button_press_event(GtkWidget *widget, gpointer data) {
  if(strcmp("Emergency Stop", (char *)data) == 0) {
    gr_delete_event(gr_application, NULL);    
    g_print("Emergency Stop Button pressed\n");
  }
}


/**
   Function which is called from block_call() if a
   message/datablock has received. See API of block_call().
*/
gr_block_call_do_test(int fd, int id, unsigned int type, char *buf,
                   unsigned int size, int term) {
  struct ROBOT_POSITION *robot_position;

  // do nothing, if the system is shutting down
  if (shut_down) {
    return;
  }

  if ((sizeof(struct ROBOT_POSITION) == size) && (type == 1)) {
    // load new position of the robot from the received package
    robot_position = (struct ROBOT_POSITION *) buf;
    robot_x = robot_position -> x;
    robot_y = robot_position -> y;
    free(robot_position);

    // drive robot to the new coordinates
    interface_driveto(robot_x, robot_y);

    // make sure, that thre is no conflict with the
    // gdk-main-loop. This is a locking-mechanism which privents
    // "Xlib: unexpected async reply"s
    gdk_threads_enter();

    // re-draw the robot with the new coordinates
    gui_common_drawrobot(da_global, &robot_x, &robot_y, -1, -1, 0);

    // make sure, that all changed item are really plotted n the screen
    gdk_window_process_all_updates();

    // unlock gdk-main-loop
    gdk_threads_leave();

    // some of the other tries to force a re-draw of the screen -- all
    // useless!
    /*    while(gtk_events_pending() && !shut_down) {
	    gtk_main_iteration();
          } */
    //    gdk_flush();
    //    da_global->queue_draw();
    //    gtk_widget_draw(da_global, da_global);
    /*    gtk_widget_queue_draw(da_global);
	  gtk_widget_queue_draw(gr_application);*/
    //    gtk_widget_queue_clear(da_global->window);
    //    da_global -> queue_draw();
    //    gtk_widget_draw(da_global, NULL);
    /*    gtk_signal_emit_by_name(da_global, "expose_event", 
	  NULL, 1);*/
    //    gtk_signal_emit_by_name(GTK_OBJECT(da_global), "changed");
    //    gtk_signal_emit_by_name(GTK_OBJECT(da_global), "expose_event");
    //    gtk_widget_show_all(da_global);
    //    gdk_window_hide(da_global->window);
    //    gdk_window_show(da_global->window);
    //    gdk_window_get_update_area(da_global->window);
    //    gtk_widget_queue_draw(da_global);
  } else {
    if (type != 1) {
      fprintf(stderr, "** WARNING: Unknown type of datablock received!\n");
    } else {
      fprintf(stderr, "** WARNING: Datablock with improper size received!\n");
    }
  }
}


/** Exception function for the linemonitor(), print exception code and
    its meaning on the screen and take further action if
    necessary. See API of linemonitor(). */
linemonitor_exception(char *server, int port, int type) {
  fprintf(stderr, "linemonitor_exception(%s, %d, %d): ",
	  server, port, type);
  switch(type) {
  case 0:
    fprintf(stderr, "Connecion Fault\n");
    gr_delete_event(gr_application, NULL);
    break;
  case 1:
    fprintf(stderr, "Soft Real Time Exception\n");
    break;
  case 2:
    fprintf(stderr, "HARD Real Time Exception\n");
    gr_delete_event(gr_application, NULL);
    break;
  case 3:
    fprintf(stderr, "Transmission Fault\n");
    gr_delete_event(gr_application, NULL);
    break;
  case 4:
    fprintf(stderr, "Emergency Stop\n");
    gr_delete_event(gr_application, NULL);
    break;
  } /* switch() */
}



/**
   Function which ist called from block_call() if a the connection
   terminates: shut down system. See block_call() API.
*/
gr_block_call_term_test(int fd, int id) {
  gr_delete_event(gr_application, NULL);
}



/**
   Function which is called from socket_accept() if someone has
   connected. See socket_accept() API.
*/
gr_socket_accept_do(int fd, int id, char *pip,
                      struct sockaddr_in their_addr, int term) {

  // starting line-monitor
  char *pard = inet_ntoa(their_addr.sin_addr);
  linemonitor(pard, serverport + 1,
	      soft_msec, hard_msec, wait_msec,
	      linemonitor_exception);
  

  // waiting for incomming data in an other thread
  block_call(fd, id, false,
             (void *) gr_block_call_do_test,
             (void *) gr_block_call_term_test);
}






int main(int argc, char *argv[]) {
  // file discriptor for the local bind.
  int sock;

  // GTK-Objecte. This is necessary to create the buttons on the
  // screen and connect them to some actions
  GtkWidget *button1, *button2, *button3, *button4;
  // Sorting into tables.
  GtkWidget *table, *table2;

  // system is not shutting down now... (it shutting up ;-)
  shut_down = 0;

  // Init the gtk (GUI toolkit) and the gdk (threads) system
  g_thread_init(NULL);
  gdk_threads_init();
  gtk_init(&argc, &argv);

  // Connect the finish() -function to some signals which can cause a
  // system shut down.
  signal(SIGHUP, finish);  // 01  /  hangup     - close Window
  signal(SIGINT, finish);  // 02  /  Interrupt  - ^C - C-c
  signal(SIGQUIT, finish); // 03  /  Quit
  signal(SIGTERM, finish); // 15  /  Terminierung -- kill
  signal(SIGALRM, finish); // 14  /  Alarm

  // load and examine parameters
  if (argc == 6) {
    serverport = atoi(argv[1]);
    soft_msec = atoi(argv[2]);
    hard_msec = atoi(argv[3]);
    wait_msec = atoi(argv[4]);
    interface_init(2);
  } else {
    if (argc == 5) {
      serverport = atoi(argv[1]);
      soft_msec = atoi(argv[2]);
      hard_msec = atoi(argv[3]);
      wait_msec = atoi(argv[4]);
      interface_init(0);
    } else {
      fprintf(stderr, "%s port-to-bind soft_msec hard_msec wait_msec\n", argv[0]);
      //      exit(0);
    }
  }

  // bind local port and launch socket_accept() to wait for connection
  if ((sock = socket_bind(serverport, 10)) < 0) {
    error("bind()");
  } else {
    socket_accept(sock, 0, (void *) gr_socket_accept_do);
  }

  // create button(s)
  button1  = gtk_button_new_with_label("Emergency Stop");
  /*  button2  = gtk_button_new_with_label("Button 2");
  button3  = gtk_button_new_with_label("Button 3");
  button4  = gtk_button_new_with_label("Button 4"); */

  // connect the buttons to the function gr_button_press_event(). If
  // the botton is pressed, this function will be colled.
  gtk_signal_connect(GTK_OBJECT(button1), "clicked",
		     GTK_SIGNAL_FUNC(gr_button_press_event),
		     "Emergency Stop" );
  /*  gtk_signal_connect(GTK_OBJECT(button2), "clicked",
		     GTK_SIGNAL_FUNC(gr_button_press_event),
		     "Button 2");
  gtk_signal_connect(GTK_OBJECT(button3), "clicked",
		     GTK_SIGNAL_FUNC(gr_button_press_event),
		     "Button 3" );
  gtk_signal_connect(GTK_OBJECT(button4), "clicked",
		     GTK_SIGNAL_FUNC(gr_button_press_event),
		     "Button 4"); */

  // create tables to sort buttons and drawindarea in it
  table = gtk_table_new(2,2,FALSE);
  table2 = gtk_table_new(2,2,FALSE);

  // create new application (wiindow)
  gr_application = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  // set window title
  gtk_window_set_title(GTK_WINDOW (gr_application), "Robot");

  // connect the function gr_delete_event() with the event of a
  // window-close.
  g_signal_connect(G_OBJECT (gr_application), "delete_event",
		   G_CALLBACK (gr_delete_event), NULL);

  // display window
  gtk_widget_show(gr_application);
  
  // create drawing area
  da_global = gtk_drawing_area_new();

  // set which events in the drawing area causing a expose-event
  gtk_widget_set_events(da_global, GDK_EXPOSURE_MASK
			 | GDK_LEAVE_NOTIFY_MASK
			 | GDK_BUTTON_PRESS_MASK
			 | GDK_POINTER_MOTION_MASK
			 | GDK_POINTER_MOTION_HINT_MASK);
  
  // set a minimum size of drawing area
  gtk_widget_set_size_request(da_global, 100, 100);

  // connect the function gr_delete_event() with the event of a
  // window-close.
  gtk_signal_connect(GTK_OBJECT(gr_application), "delete_event",
		     GTK_SIGNAL_FUNC(gr_delete_event), NULL);

  // connect the function gr_da_expose_event() with the expose-event
  // of the drawing area.
  gtk_signal_connect(GTK_OBJECT(da_global), "expose_event",
		     GTK_SIGNAL_FUNC(gr_da_expose_event), NULL);

  // fill the buttons in the table
  gtk_table_attach_defaults(GTK_TABLE(table2), button1, 0,1, 0,1);
  /*  gtk_table_attach_defaults(GTK_TABLE(table2), button2, 0,1, 1,2);
  gtk_table_attach_defaults(GTK_TABLE(table2), button3, 1,2, 0,1);
  gtk_table_attach_defaults(GTK_TABLE(table2), button4, 1,2, 1,2); */

  // fill the drawing area and the table into another table
  gtk_table_attach_defaults(GTK_TABLE(table), da_global, 0,1, 0,1);
  gtk_table_attach_defaults(GTK_TABLE(table), table2, 0,1, 1,2);

  // fill this table into the windos
  gtk_container_add(GTK_CONTAINER(gr_application),table);

  // display it  
  gtk_widget_show_all(gr_application);

  // call gtk-main-loop
  gtk_main();

  return 0;
}


