/*==========================================================================*\
 |  This file is part of setiherder (http://www.sackheads.org/setiherder/)  |
 |  which was written by Brian L. Naylor <brian+seti@sackheads.org> and is  |
 |  under the GNU General Public License.  You should have recieved a copy  |
 |  of the GPL with this source code.  If not, we'd like to know about it.  |
\*--------------------------------------------------------------------------*/

#include <gtk/gtk.h> 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>

#include "shtypes.h"
#include "shwin.h"
#include "shproc.h"
#include "shclients.h"
#include "shutil.h"
#include "shrc.h"


/*------------------------------------------------------------------------*\
 | Globals                                                                |
\*------------------------------------------------------------------------*/
int        clist_selected;        /* currently-selected clist row         */

GtkWidget *clist,                 /* widgets that need to be available    */
          *clist_popup_menu,      /* to post-initialization routines      */
          *log_window;
         
/* these come from shmain.c                                               */
extern int clist_host_width,      /* some widget sizings that are         */
           clist_account_width,   /* user-configurable                    */
           clist_state_width,
           clist_units_width,
           clist_complete_width,
           main_window_width,
           main_window_height;

typedef struct add_info {
  GtkWidget *hostname;
  GtkWidget *seticmd;
  GtkWidget *delay;
  GtkWidget *autores;
  GtkWidget *dialog;
  int        new;
} add_info_t;

/*------------------------------------------------------------------------*\
 | Callbacks                                                              |
\*------------------------------------------------------------------------*/
void delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
  cleanup();
  gtk_main_quit(); 
}

void exit_callback(GtkWidget *widget, gpointer data)
{
  cleanup();
  gtk_main_quit(); 
}

void expanded_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
  sh_client_t *sc = (sh_client_t *)data;
  
  gtk_widget_destroy(widget);
  sc->expanded = NULL;
}

void mass_startstop_callback(GtkWidget *widget, gpointer data)
{
  int          i, ss, rc;
  sh_client_t *sc;
  
  ss = (int)data;
  
  if(ss) /* start */
  {
    for(i=0; i<=sh_client_idx; i++)
    {
      if((sc = sh_client_get(i)) != NULL && sc->pid <= 0)
      {
        sc->user_stopped = 0;
        if((rc = sh_client_start(sc)) != SH_SUCCESS)
        {
          dbg(DBG_ERROR, "Client %d (%s) failed to start.  Check your "
                         "command line or host setup.\n", 
                         i, sh_client_get(i)->hostname);
          shwin_status_change(i);
          continue;
        }
        sh_client_get(clist_selected)->state = SH_STATE_WORKING;
        shwin_status_change(i);
      }
    } 
  }
  else   /* stop  */
  {
    for(i=0; i<=sh_client_idx; i++)
    {
      if((sc = sh_client_get(i)) != NULL)
      {
        if(sc->pid > 0)
        {
          sc->user_stopped = 1;
          sh_client_stop(sc);
        }
      }
    }
  }
}

void expand_button_callback(GtkWidget *widget, gpointer data)
{
  GtkWidget             *dialog,
                        *vbox,
                        *hbox1,
                        *sep,
                        *tmplabel,
                        *progbar;
  sh_client_t           *client;                  
  char                   tmp[80];
  sh_expanded_widgets_t *exp;
  
  if((client = sh_client_get((int)data)) == NULL)
  {
    dbg(DBG_ERROR, 
        "expand_button_callback() got request for invalid client (%d)\n",
        (int)data);
    return;
  }
  
  if((exp = (sh_expanded_widgets_t *)malloc(sizeof(sh_expanded_widgets_t)))
      == NULL)
  {
    dbg(DBG_ERROR, 
        "expand_button_callback() was unable to allocate %d bytes.\n",
        sizeof(sh_expanded_widgets_t));
    return;
  }

  dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  exp->toplevel = dialog;
  strcpy(tmp, "setiherder client: ");
  strncat(tmp, client->hostname, 59);
  gtk_window_set_title(GTK_WINDOW(dialog), tmp);  
  gtk_signal_connect(GTK_OBJECT(dialog), "delete_event",
                     GTK_SIGNAL_FUNC(expanded_delete_event),
                     (gpointer) client);
  gtk_container_set_border_width(GTK_CONTAINER(dialog), 5);
  
  vbox = gtk_vbox_new(FALSE, 6);
  gtk_container_add(GTK_CONTAINER(dialog), vbox);
  
  hbox1 = gtk_table_new(11, 6, 0);
  gtk_container_add(GTK_CONTAINER(vbox), hbox1);
  
  /*
   * start labels
   */
  
  /* hostname */
  tmplabel = gtk_label_new("Hostname:");
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 0, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   0,  /* left      */
                   1,  /* right     */
                   0,  /* top       */
                   1,  /* bottom    */
                   GTK_FILL,
                   GTK_FILL,
                   2,  /* x padding */
                   1); /* y padding */
  gtk_widget_show(tmplabel);
  
  tmplabel = gtk_label_new(client->hostname);
  exp->hostname = tmplabel;
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 0, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   1, 
                   2, 
                   0,
                   1,
                   GTK_FILL,
                   GTK_FILL,
                   5,
                   1);
  gtk_widget_show(tmplabel);
  
  /* pad */
  tmplabel = gtk_label_new(" ");
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   2,
                   3,
                   0,
                   1,
                   GTK_FILL,
                   GTK_FILL,
                   2,
                   2);
  gtk_widget_show(tmplabel);
  
  /* Source */
  tmplabel = gtk_label_new("Source:");
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 0, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   3,
                   4,
                   0,
                   1,
                   GTK_FILL,
                   GTK_FILL,
                   2,
                   1);
  gtk_widget_show(tmplabel);
  
  tmplabel = gtk_label_new(client->source);
  exp->source = tmplabel;
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 0, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   4,
                   5,
                   0,
                   1,
                   GTK_FILL,
                   GTK_FILL,
                   5,
                   1);
  gtk_widget_show(tmplabel);
  
  /* platform */
  tmplabel = gtk_label_new("Platform:");
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 0, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   0,
                   1,
                   1,
                   2,
                   GTK_FILL,
                   GTK_FILL,
                   2,
                   1);
  gtk_widget_show(tmplabel);
  
  tmplabel = gtk_label_new(client->platform);
  exp->platform = tmplabel;
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 0, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   1,
                   2,
                   1,
                   2,
                   GTK_FILL,
                   GTK_FILL,
                   5,
                   1);
  gtk_widget_show(tmplabel);
  
  /* Date Recorded */
  tmplabel = gtk_label_new("Date Recorded:");
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 0, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   3,
                   4,
                   1,
                   2,
                   GTK_FILL,
                   GTK_FILL,
                   2,
                   1);
  gtk_widget_show(tmplabel);
  
  tmplabel = gtk_label_new(client->recdate);
  exp->recdate = tmplabel;
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 0, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   4,
                   5,
                   1,
                   2,
                   GTK_FILL,
                   GTK_FILL,
                   5,
                   1);
  gtk_widget_show(tmplabel);

  /* Version */
  tmplabel = gtk_label_new("Version:");
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 0, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   0,
                   1,
                   2,
                   3,
                   GTK_FILL,
                   GTK_FILL,
                   2,
                   1);
  gtk_widget_show(tmplabel);
  
  tmplabel = gtk_label_new(client->version);
  exp->version = tmplabel;
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 0, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   1,
                   2,
                   2,
                   3,
                   GTK_FILL,
                   GTK_FILL,
                   5,
                   1);
  gtk_widget_show(tmplabel);
  
  /* coordinates */
  tmplabel = gtk_label_new("Coordinates:");
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 0, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   3,
                   4,
                   2,
                   3,
                   GTK_FILL,
                   GTK_FILL,
                   2,
                   1);
  gtk_widget_show(tmplabel);
  
  tmplabel = gtk_label_new(client->coords);
  exp->coords = tmplabel;
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 0, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   4,
                   5,
                   2,
                   3,
                   GTK_FILL,
                   GTK_FILL,
                   5,
                   1);
  gtk_widget_show(tmplabel);
  
  /* vertical pad */
  sep = gtk_hseparator_new();
  gtk_table_attach(GTK_TABLE(hbox1), sep,
                   0,
                   6,
                   3,
                   4,
                   GTK_FILL,
                   GTK_FILL,
                   5,
                   8);
  gtk_widget_show(sep);
          
  /* Task */
  tmplabel = gtk_label_new("Task:");
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 1, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   0,
                   2,
                   4,
                   5,
                   GTK_FILL,
                   GTK_FILL,
                   2,
                   1);
  gtk_widget_show(tmplabel);
  
  tmplabel = gtk_label_new(NULL);
  exp->task = tmplabel;
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 0, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   3,
                   5,
                   4,
                   5,
                   GTK_FILL,
                   GTK_FILL,
                   5,
                   1);
  gtk_widget_show(tmplabel);
  
  /* Doppler shift rate */
  tmplabel = gtk_label_new("Doppler shift rate:");
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 1, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   0,
                   2,
                   5,
                   6,
                   GTK_FILL,
                   GTK_FILL,
                   2,
                   1);
  gtk_widget_show(tmplabel);
 
  sprintf(tmp, "%f", client->dop_shift_rt);
  tmplabel = gtk_label_new(tmp);
  exp->dop_shift_rt = tmplabel;
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 0, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   3,
                   5,
                   5,
                   6,
                   GTK_FILL,
                   GTK_FILL,
                   5,
                   1);
  gtk_widget_show(tmplabel);
  
  /* last shift */
  tmplabel = gtk_label_new("Last shift:");
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 1, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   0,
                   2,
                   6,
                   7,
                   GTK_FILL,
                   GTK_FILL,
                   2,
                   1);
  gtk_widget_show(tmplabel);
 
  sprintf(tmp, "%f Hz/s", client->last_shift); 
  tmplabel = gtk_label_new(tmp);
  exp->last_shift = tmplabel;
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 0, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   3,
                   5,
                   6,
                   7,
                   GTK_FILL,
                   GTK_FILL,
                   5,
                   1);
  gtk_widget_show(tmplabel);
  
  /* FFT length */
  tmplabel = gtk_label_new("FFT length:");
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 1, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   0,
                   2,
                   7,
                   8,
                   GTK_FILL,
                   GTK_FILL,
                   2,
                   1);
  gtk_widget_show(tmplabel);
  
  sprintf(tmp, "%d", client->fft_len);
  tmplabel = gtk_label_new(tmp);
  exp->fft_len = tmplabel;
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 0, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   3,
                   5,
                   7,
                   8,
                   GTK_FILL,
                   GTK_FILL,
                   5,
                   1);
  gtk_widget_show(tmplabel);
  
  /* Base frequency */
  tmplabel = gtk_label_new("Base frequency:");
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 1, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   0,
                   2,
                   8,
                   9,
                   GTK_FILL,
                   GTK_FILL,
                   2,
                   1);
  gtk_widget_show(tmplabel);
  
  tmplabel = gtk_label_new(client->basefreq);
  exp->basefreq = tmplabel;
  gtk_misc_set_alignment(GTK_MISC(tmplabel), 0, 0);
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   3,
                   5,
                   8,
                   9,
                   GTK_FILL,
                   GTK_FILL,
                   5,
                   1);
  gtk_widget_show(tmplabel);

  /* vertical pad */
  tmplabel = gtk_label_new(" ");
  gtk_table_attach(GTK_TABLE(hbox1), tmplabel,
                   0,
                   1,
                   9,
                   10,
                   GTK_FILL,
                   GTK_FILL,
                   0,
                   0);
  gtk_widget_show(tmplabel);
  
  /*
   * progress bar 
   */
  progbar = gtk_progress_bar_new();
  exp->progress = progbar;
  gtk_container_add(GTK_CONTAINER(vbox), progbar);
  gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(progbar), 
                                   GTK_PROGRESS_LEFT_TO_RIGHT);
  gtk_progress_bar_set_bar_style(GTK_PROGRESS_BAR(progbar), 
                                 GTK_PROGRESS_CONTINUOUS);
  gtk_progress_set_format_string(GTK_PROGRESS(progbar), "%p%% complete");
  gtk_progress_set_show_text(GTK_PROGRESS(progbar), TRUE);
  gtk_progress_set_text_alignment(GTK_PROGRESS(progbar), 0.5, 0.5);
  gtk_progress_bar_update(GTK_PROGRESS_BAR(progbar), 
                                           client->completed / 100.0);
  gtk_widget_show(progbar);
  
  /*
   * show dialog 
   */
  gtk_widget_show(hbox1);
  gtk_widget_show(vbox);
  gtk_widget_show(dialog);
  
  /*
   * save pointer to widgets 
   */
  client->expanded = exp;
}

void add_button_callback(GtkWidget *widget, gpointer data)
{
  GtkWidget         *dialog,
                    *vbox,
                    *canvas,
                    *label,
                    *entry,
                    *check,
                    *bbox,
                    *button;
  static add_info_t  addinfo;            
  char              *in_hostname,
                    *in_seticmd,
                     in_delay[10],
                    *title;
  int                in_autores;
  sh_client_t       *sc;
  int                edit; /* flag */ 
       
  /*
   * New client or Edit client?
   */
  edit = (int)data;
  if(edit == 0)         /* new client  */
  {
    in_hostname    = SH_DEFAULT_HOSTNAME; 
    in_seticmd     = SH_DEFAULT_SETICMD;
#ifdef HAVE_SNPRINTF
    snprintf(in_delay, 9, "%d", SH_DEFAULT_DELAY);
#else
    sprintf(in_delay, "%d", SH_DEFAULT_DELAY);
#endif    
    in_autores     = SH_DEFAULT_AUTORES;
    title          = "Add Client";
    addinfo.new    = 1;
  }
  else                  /* edit client */
  {
    sc = sh_client_get(clist_selected);
    in_hostname    = sc->hostname;
    in_seticmd     = sc->seticmd;
#ifdef HAVE_SNPRINTF    
    snprintf(in_delay, 9, "%d", sc->delay);
#else 
    sprintf(in_delay, "%d", sc->delay);   
#endif    
    in_autores     = sc->autores;
    title          = "Edit Client";
    addinfo.new    = 0;
  }
  
  dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  addinfo.dialog = dialog;
  gtk_window_set_title(GTK_WINDOW(dialog), title);
  gtk_container_set_border_width(GTK_CONTAINER(dialog), 5);
  
  vbox = gtk_vbox_new(FALSE, 6);
  gtk_container_add(GTK_CONTAINER(dialog), vbox);
 
  canvas = gtk_table_new(7, 24, 0);
  gtk_container_add(GTK_CONTAINER(vbox), canvas);

  /* hostname entry field */  
  label = gtk_label_new("Hostname:");
  gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
  gtk_table_attach(GTK_TABLE(canvas), label,
                   0,  /* left      */
                   4,  /* right     */
                   0,  /* top       */
                   1,  /* bottom    */
                   GTK_FILL,  /* x options */
                   GTK_FILL,  /* y options */
                   2,  /* x padding */
                   2); /* y padding */
  gtk_widget_show(label);
  
  entry = gtk_entry_new_with_max_length(255);
  addinfo.hostname = entry;
  gtk_entry_set_text(GTK_ENTRY(entry), in_hostname);
  /* set one label size to make 4-6 a little bigger */
  gtk_widget_set_usize(GTK_WIDGET(entry), 250, 0);
  gtk_table_attach(GTK_TABLE(canvas), entry,
                   4,
                   23,
                   0,
                   1,
                   GTK_FILL,
                   GTK_FILL,
                   2,
                   2);
  gtk_widget_show(entry);

  /* command entry field */
  label = gtk_label_new("Seti@Home command:");
  gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
  gtk_table_attach(GTK_TABLE(canvas), label,
                   0,
                   4,
                   1,
                   2,
                   GTK_FILL,
                   GTK_FILL,
                   2,
                   2);
  gtk_widget_show(label);

  entry = gtk_entry_new_with_max_length(255);
  addinfo.seticmd = entry;
  gtk_entry_set_text(GTK_ENTRY(entry), in_seticmd);
  gtk_table_attach(GTK_TABLE(canvas), entry,
                   4,
                   23,
                   1,
                   2,
                   GTK_FILL,
                   GTK_FILL,
                   2,
                   2);
  gtk_widget_show(entry);

  /* delay entrybox */
  label = gtk_label_new("Delay between restarts:");
  gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
  gtk_widget_set_usize(GTK_WIDGET(entry), 50, 0);
  gtk_table_attach(GTK_TABLE(canvas), label,
                   0, 
                   4, 
                   2,
                   3,
                   GTK_FILL,
                   GTK_FILL,
                   2,
                   2);
  gtk_widget_show(label);

  entry = gtk_entry_new_with_max_length(5);
  addinfo.delay = entry;
  gtk_entry_set_text(GTK_ENTRY(entry), in_delay);
  gtk_widget_set_usize(GTK_WIDGET(entry), 80, 0);
  gtk_table_attach(GTK_TABLE(canvas), entry,
                   4,
                   6,
                   2,
                   3,
                   GTK_FILL,
                   GTK_FILL,
                   2,
                   2);
  gtk_widget_show(entry);
  
  /* check button */  
  check = gtk_check_button_new_with_label("Restart upon crash?");
  addinfo.autores = check;
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), in_autores);
  gtk_table_attach(GTK_TABLE(canvas), check, 
                   0,
                   6,
                   3,
                   4,
                   GTK_FILL,
                   GTK_FILL,
                   1,
                   1);
  gtk_widget_show(check);

  /* action buttons */
  bbox = gtk_hbox_new(TRUE, 4);
  gtk_table_attach(GTK_TABLE(canvas), bbox, 
                   0, 
                  23,
                   5,
                   6,
                   GTK_FILL,
                   GTK_FILL,
                   4,
                   4);
  gtk_widget_show(bbox);
  
  button = gtk_button_new_with_label(" OK ");
  gtk_signal_connect(GTK_OBJECT(button), "clicked", 
                     GTK_SIGNAL_FUNC(add_ok_callback),
                     (gpointer)&addinfo);
  gtk_box_pack_start(GTK_BOX(bbox), button, TRUE, TRUE, 10);
  gtk_widget_show(button);

  button = gtk_button_new_with_label(" Cancel ");
  gtk_signal_connect(GTK_OBJECT(button), "clicked",
                     GTK_SIGNAL_FUNC(add_cancel_callback), 
                     (gpointer)dialog);
  gtk_box_pack_start(GTK_BOX(bbox), button, TRUE, TRUE, 10);
  gtk_widget_show(button);
  
  gtk_widget_show(canvas);
  gtk_widget_show(vbox);
  gtk_widget_show(dialog);
  
}

void popup_handler(GtkWidget *widget, gpointer data)
{
  int option = (int) data;
  int rc;
  sh_client_t *sc;
  
  sc = sh_client_get(clist_selected);
      
  switch(option) {
    case SH_GUI_MENU_EDIT:
      add_button_callback(widget, GINT_TO_POINTER(TRUE));
      break;
    case SH_GUI_MENU_EXPAND: 
      if(sc->expanded == NULL) {
        expand_button_callback(widget, GINT_TO_POINTER(clist_selected));
      }
      else {
        dbg(DBG_WARN, "%s is already expanded.\n", sc->hostname);
      }
      break;
    case SH_GUI_MENU_START:
      if( (sc->state == SH_STATE_STOPPED) ||
          (sc->state == SH_STATE_CRASHED) )
      {
        sc->user_stopped = 0;

        /* spawn child */
        if(sh_client_get(clist_selected)->seticmd == NULL) 
        {
          dbg(DBG_ERROR, "popup_handler(spawn): No command was set! (rc=%d)\n", 
              SH_ERR_NOCMD);
          return;
        }
        if((rc = sh_client_start(sh_client_get(clist_selected)))
              != SH_SUCCESS)
        {
          dbg(DBG_ERROR, "Client %d (%s) failed to start.  Check your "
                         "command or host setup.\n",
                         clist_selected, sc->hostname);
          shwin_status_change(clist_selected);
          return;
        }
      
        sh_client_get(clist_selected)->state = SH_STATE_WORKING;
        shwin_status_change(clist_selected);
      }
      else
      {
        dbg(DBG_INFO, "Client %d (%s) is already started.\n",
            clist_selected, 
            sc->hostname);
      }
      break;
    case SH_GUI_MENU_STOP:
      if(sc->state != SH_STATE_STOPPED) {
        sc->user_stopped = 1;
        if(sc->state == SH_STATE_CRASHED) {
          sc->state = SH_STATE_STOPPED;
        }
        else
        {
          /* actually need to kill the client */
          if((rc = sh_client_stop(sh_client_get(clist_selected)))
             != SH_SUCCESS)
          {
            sh_client_get(clist_selected)->state = SH_STATE_HUNG;
          }
          else{
            sh_client_get(clist_selected)->state = SH_STATE_STOPPED;
          }
        }
        shwin_status_change(clist_selected);
      }
      else {
        dbg(DBG_INFO, "Client %d (%s) is already stopped.\n",
            clist_selected, sc->hostname);
      }
      break;
    case SH_GUI_MENU_REMOVE:
      /* XXX should ask for confirmation here..  need yes/no popup XXX */
      if(sh_client_del(clist_selected) == SH_SUCCESS)
      {
        gtk_clist_remove(GTK_CLIST(clist), clist_selected);
        shrc_save();
      }
      break;
    default:
      dbg(DBG_DIAG, "Eek!  Invalid menu choice!\n");
      break;
  };
}

void clist_select_row_callback(GtkCList *widget, gint row, gint column,
                               GdkEvent *event, gpointer data)
{
  clist_selected = row;

  if(event->button.button != 1) {
    gtk_menu_popup(GTK_MENU(clist_popup_menu), NULL, NULL, NULL, 
                   NULL, event->button.button, event->button.time);
  }
}

void clist_click_callback(GtkWidget *widget, 
                          GdkEventButton *bevent,
                          gpointer data)
{
  gint row, column, res;
  
  res = gtk_clist_get_selection_info(GTK_CLIST(clist),
                                     bevent->x,
                                     bevent->y,
                                     &row,
                                     &column);
  
  /* sanity check */
  if(row < 0 || row > sh_client_idx)
  {
    return;
  }
  
  clist_selected = row;

  if(bevent->button != 1) 
  {
    gtk_menu_popup(GTK_MENU(clist_popup_menu), NULL, NULL, NULL, 
                   NULL, bevent->button, bevent->time);
  }

  gtk_clist_select_row(GTK_CLIST(clist), row, column);
  
  gtk_signal_emit_stop_by_name(GTK_OBJECT(clist),
                               "button_press_event");
}

void input_callback(gpointer data, gint source, GdkInputCondition cnd)
{
  char  buf[SH_MAX_BUF+1];
  int   client, n;
  /*
   * map file descriptor to client 
   */
  memcpy((void *)&client, data, sizeof(int));
  dbg(DBG_DUMP, "Reading data from client %d\n", client); 
  
  /*
   * read data 
   */
  if((n = read(source, buf, SH_MAX_BUF)) < 1)
  {
    if(n < 0) {
      dbg(DBG_ERROR, "read() failed with rc=%d\n", errno);
      return;
    }
  }
  
  parse_buffer(buf,n,client);
}

void add_ok_callback(GtkWidget *widget, gpointer data)
{
  add_info_t *info = (add_info_t *)data;
  int idx;
  sh_rc rc;
 
  if(info->new == 1)
  {
    /* create a new client entry */
    if((rc = sh_client_new(&idx)) != SH_SUCCESS)
    {
      dbg(DBG_ERROR, "Couldn't create new client entry: rc=%d\n", rc);
      return;
    }
  }
  else 
  {
    /* point to correct client entry */
    idx = clist_selected;
  }
  
  /* scromp data from widgets */
  sh_client_set_hostname(idx, gtk_entry_get_text(GTK_ENTRY(info->hostname)));
  sh_client_set_seticmd (idx, gtk_entry_get_text(GTK_ENTRY(info->seticmd)));
  sh_client_set_delay(idx, atoi(gtk_entry_get_text(GTK_ENTRY(info->delay))));
  sh_client_set_autores(idx, GTK_TOGGLE_BUTTON(info->autores)->active);
 
  /* make sure everything's kosher (must use strlen here; if(NULL) !work) */
  if( (strlen(sh_client_get(idx)->hostname) == 0) ||
      (strlen(sh_client_get(idx)->seticmd ) == 0) )
  {
    dbg(DBG_ERROR, 
        "You must enter both 'hostname' and 'Seti@Home command'!\n");
    sh_client_del(idx);
    return;
  }

  if(info->new == 1)
  { 
    shwin_client_add(sh_client_get(idx));
  }
  else
  {
    shwin_status_change(idx);
  }
  gtk_widget_destroy(GTK_WIDGET(info->dialog));
  
  /*
   * try to save rc file here..
   */
  shrc_save();
}

gint idle_check(gpointer data)
{
  return TRUE;  /* required to remain in timeout list */
}

void add_cancel_callback(GtkWidget *widget, gpointer data)
{
  gtk_widget_destroy((GTK_WIDGET(data)));
}

/*------------------------------------------------------------------------*\
 | Functions                                                              |
\*------------------------------------------------------------------------*/
sh_rc shwin_init(void)
{
  GtkWidget   *window,
              *button,
              *vbox,
              *hbox,
              *frame,
              *vscroll,
              *menuitem;
  char        *titles[5];
  int          i;

  clist_selected       = -1; 
 
  /* 
   * toplevel window 
   */
  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window),
                       "setiherder");
  gtk_window_set_default_size(GTK_WINDOW(window),
                              main_window_width,
                              main_window_height);
  
  gtk_signal_connect(GTK_OBJECT(window), "delete_event",
                     GTK_SIGNAL_FUNC(delete_event), NULL);

  /* set window border width */  
  gtk_container_border_width(GTK_CONTAINER(window), 10);
  
  /*
   * main container 
   */
  vbox = gtk_vbox_new(FALSE, 6);
  gtk_container_add(GTK_CONTAINER(window), vbox);
  
  /* 
   * client list and frame 
   */
  frame = gtk_frame_new("Clients");
  gtk_container_add(GTK_CONTAINER(vbox), frame);
  titles[0] = "Host";
  titles[1] = "Account";
  titles[2] = "State";
  titles[3] = "Units";
  titles[4] = "Complete";
  vscroll = gtk_scrolled_window_new(NULL, NULL);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(vscroll),
                                 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  clist = gtk_clist_new_with_titles(5,titles);
  /* the following line effectively sets the horizontal width of 
   * the entire window */
  gtk_widget_set_usize(GTK_WIDGET(clist), 500, 200);
  gtk_clist_set_selection_mode(GTK_CLIST(clist), GTK_SELECTION_SINGLE);
  gtk_clist_column_titles_passive(GTK_CLIST(clist));
  gtk_clist_set_shadow_type(GTK_CLIST(clist), GTK_SHADOW_IN);
  gtk_clist_set_column_justification(GTK_CLIST(clist), 0,
                                     GTK_JUSTIFY_LEFT);
  /* set column widths */
  gtk_clist_set_column_width(GTK_CLIST(clist), 0, clist_host_width);
  gtk_clist_set_column_width(GTK_CLIST(clist), 1, clist_account_width);
  gtk_clist_set_column_width(GTK_CLIST(clist), 2, clist_state_width);
  gtk_clist_set_column_width(GTK_CLIST(clist), 3, clist_units_width);
  gtk_clist_set_column_width(GTK_CLIST(clist), 4, clist_complete_width);
  gtk_container_add(GTK_CONTAINER(vscroll), clist);
  gtk_container_add(GTK_CONTAINER(frame), vscroll);
  /* connect resize signal */
  /* XXX ... XXX */
  gtk_widget_show(clist);
  gtk_widget_show(vscroll);
  gtk_widget_show(frame);
  
  /*
   * Log window
   */
  frame = gtk_frame_new("Log");
  gtk_container_add(GTK_CONTAINER(vbox), frame);
  hbox = gtk_table_new(2,2,FALSE);
  gtk_container_add(GTK_CONTAINER(frame), hbox);
  log_window = gtk_text_new(NULL, NULL);
  vscroll = gtk_vscrollbar_new(GTK_TEXT(log_window)->vadj);
  gtk_text_set_editable(GTK_TEXT(log_window), FALSE);
  gtk_table_attach(GTK_TABLE(hbox), log_window, 0, 1, 0, 1,
                   GTK_EXPAND | GTK_SHRINK | GTK_FILL,
                   GTK_EXPAND | GTK_SHRINK | GTK_FILL,
                   0, 0);
  gtk_table_attach(GTK_TABLE(hbox), vscroll, 1, 2, 0, 1,
                   GTK_FILL, 
                   GTK_EXPAND | GTK_SHRINK | GTK_FILL,
                   0, 0);
  gtk_widget_set_usize(log_window, 0, 70);
  gtk_widget_show(log_window);
  gtk_widget_show(vscroll);
  gtk_widget_show(hbox);
  gtk_widget_show(frame);
  gtk_widget_realize(log_window);

  /*
   * buttons
   */
  hbox = gtk_hbox_new(TRUE, 4);
  gtk_container_add(GTK_CONTAINER(vbox), hbox);
  button = gtk_button_new_with_label(" Add client ");
  gtk_signal_connect(GTK_OBJECT(button), "clicked", 
                     GTK_SIGNAL_FUNC(add_button_callback), NULL);
  gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 5);
  gtk_widget_show(button);

  button = gtk_button_new_with_label(" Start all ");
  gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 5);
  gtk_signal_connect(GTK_OBJECT(button), "clicked",
                     GTK_SIGNAL_FUNC(mass_startstop_callback), 
                     GINT_TO_POINTER(TRUE));
  gtk_widget_show(button);

  button = gtk_button_new_with_label(" Stop all ");
  gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 5);
  gtk_signal_connect(GTK_OBJECT(button), "clicked",
                     GTK_SIGNAL_FUNC(mass_startstop_callback), 
                     GINT_TO_POINTER(FALSE));
  gtk_widget_show(button);

  button = gtk_button_new_with_label(" Exit ");
  gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 5);
  gtk_signal_connect(GTK_OBJECT(button), "clicked", 
                     GTK_SIGNAL_FUNC(exit_callback), NULL);
  gtk_widget_show(button);

  gtk_widget_show(hbox);
  
  /* 
   * create popup menu 
   */
  clist_popup_menu = gtk_menu_new();

  menuitem = gtk_menu_item_new_with_label("Edit...");
  gtk_menu_append(GTK_MENU(clist_popup_menu), menuitem);
  gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
                     GTK_SIGNAL_FUNC(popup_handler),
                     GINT_TO_POINTER(SH_GUI_MENU_EDIT));
  gtk_widget_show(menuitem);
  
  menuitem = gtk_menu_item_new_with_label("Expand...");
  gtk_menu_append(GTK_MENU(clist_popup_menu), menuitem);
  gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
                     GTK_SIGNAL_FUNC(popup_handler),
                     GINT_TO_POINTER(SH_GUI_MENU_EXPAND));
  gtk_widget_show(menuitem);
  
  menuitem = gtk_menu_item_new_with_label("Start");
  gtk_menu_append(GTK_MENU(clist_popup_menu), menuitem);
  gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
                     GTK_SIGNAL_FUNC(popup_handler),
                     GINT_TO_POINTER(SH_GUI_MENU_START));
  gtk_widget_show(menuitem);

  menuitem = gtk_menu_item_new_with_label("Stop");
  gtk_menu_append(GTK_MENU(clist_popup_menu), menuitem);
  gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
                     GTK_SIGNAL_FUNC(popup_handler),
                     GINT_TO_POINTER(SH_GUI_MENU_STOP));
  gtk_widget_show(menuitem);

  menuitem = gtk_menu_item_new_with_label("Remove");
  gtk_menu_append(GTK_MENU(clist_popup_menu), menuitem);
  gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
                     GTK_SIGNAL_FUNC(popup_handler),
                     GINT_TO_POINTER(SH_GUI_MENU_REMOVE));
  gtk_widget_show(menuitem);
    
  /* force the clist to select items with buttons 2&3 as well, so we
   * can obtain the selection info when spawning a popup 
   */
  gtk_clist_set_button_actions(GTK_CLIST(clist), 2, GTK_BUTTON_SELECTS);
  gtk_clist_set_button_actions(GTK_CLIST(clist), 3, GTK_BUTTON_SELECTS);

#ifdef OTHERWAY 
  /* watch for row-selection signal; we'll spawn a popup from there */
  gtk_signal_connect(GTK_OBJECT(clist), "select_row",
                     GTK_SIGNAL_FUNC(clist_select_row_callback),
                     NULL);
  /* we want right-click to operate on "de-selects" as well. */
  gtk_signal_connect(GTK_OBJECT(clist), "unselect_row",
                     GTK_SIGNAL_FUNC(clist_select_row_callback),
                     NULL);
#else
  /* catch button-press event in clist, so we can pop up a menu */
  gtk_signal_connect(GTK_OBJECT(clist), "button_press_event",
                     GTK_SIGNAL_FUNC(clist_click_callback),
                     NULL);
#endif  

  /*
   * done with widgets; show main window 
   */
  gtk_widget_show(vbox);
  gtk_widget_show(window);
 
  /*
   * Iterate through list of clients (if there is one) and add
   * them to the clist.
   */
  for(i=0; i<=sh_client_idx; i++)
  {
    shwin_client_add(sh_client_get(i));
  }
  
  return 0;
}

sh_rc shwin_client_add(sh_client_t *newclient)
{
  char *columns[5], units[10], percent[10];

#ifdef HAVE_SNPRINTF  
  snprintf(units,   9, "%d",    newclient->units_done);
  snprintf(percent, 9, "%2.2f%%", newclient->completed);
#else
  sprintf(units, "%d", newclient->units_done);
  sprintf(percent, "%2.2f%%", newclient->completed);
#endif  
  
  columns[0] = newclient->hostname;
  columns[1] = newclient->account;
  columns[2] = sh_state_list[newclient->state];
  columns[3] = units;
  columns[4] = percent;
  
  gtk_clist_append(GTK_CLIST(clist), columns);
  return 0;
}

void add_text_to_log(int level, char *text)
{
  GdkColormap *cmap;
  GdkColor color;
  guint pos;
  
  if(text == NULL)
  {
    printf("Null text sent to log window.\n");
    return;
  }
 
  /* get system colormap and allocate a color */
  cmap = gdk_colormap_get_system();
  color.red = color.green = color.blue = 0;
  switch(level) {
    case DBG_INFO:
      break;
    case DBG_CUTE: 
      color.blue  = 0xffff;
      break;
    case DBG_WARN:
      color.blue  = 0xaaaa;
      color.green = 0x3333;
      break;
    case DBG_ERROR:
      color.red   = 0xcccc;
      color.green = 0x7777;
      break;
    case DBG_FATAL:
      color.red   = 0xffff;      
      break;
    default:
      color.green = 0xaaaa;
      break;
  };
  
  if(!gdk_color_alloc(cmap, &color)) {
    printf("log window couldn't allocate color for dbg level %d\n", level);
  }
  
  /* don't really need to freeze the window for this small amount of text */
  gtk_text_insert(GTK_TEXT(log_window), NULL, &color, NULL, text, -1);
  pos = gtk_text_get_length(GTK_TEXT(log_window));
  gtk_text_set_point(GTK_TEXT(log_window), pos);
}

sh_rc shwin_details_change(int client)
{
  sh_client_t           *sc;
  sh_expanded_widgets_t *sw;
  char                   tmp[80];

  if((sc = sh_client_get(client)) == NULL)
    return SH_ERR_GENERIC;
  
  if((sw = sc->expanded) == NULL)
    return SH_ERR_NOT_EXPANDED;

  dbg(DBG_DIAG, "Updating details for client %d (exp=0x%8x)\n",
      client, sw);
  
  /* update members of client->expanded and refresh window */
  gtk_label_set_text(GTK_LABEL(sw->hostname    ), sc->hostname);
  gtk_label_set_text(GTK_LABEL(sw->platform    ), sc->platform);
  gtk_label_set_text(GTK_LABEL(sw->version     ), sc->version );
  gtk_label_set_text(GTK_LABEL(sw->source      ), sc->source  );
  gtk_label_set_text(GTK_LABEL(sw->recdate     ), sc->recdate );
  gtk_label_set_text(GTK_LABEL(sw->task        ), sc->task    );
  sprintf(tmp, "%f", sc->dop_shift_rt);
  gtk_label_set_text(GTK_LABEL(sw->dop_shift_rt), tmp         );
  sprintf(tmp, "%f Hz/s", sc->last_shift);
  gtk_label_set_text(GTK_LABEL(sw->last_shift  ), tmp         );
  sprintf(tmp, "%d", sc->fft_len);
  gtk_label_set_text(GTK_LABEL(sw->fft_len     ), tmp         );
  gtk_label_set_text(GTK_LABEL(sw->basefreq    ), sc->basefreq);
  gtk_progress_bar_update(GTK_PROGRESS_BAR(sw->progress), sc->completed/100.0);
  
  return SH_SUCCESS;
}


/*
 * little private bail function for parse_line()
 */
void parse_bail(char *bailstr)
{
  printf("input: got weird string: `%s'\n", bailstr);
  printf("If this came from the actual seti@home client, please ");
  printf("send it to\n"); 
  printf("setiherder@sackheads.org with a description of the ");
  printf("circumstances\n"); 
  printf("that led to it.  Thanks.\n\n");
  return;
}
  
sh_rc parse_line(char *line, int client)
{
  char *ptr=NULL, buf[256];
  dbg(DBG_DIAG, "got(%d): %s\n", client, line); 

  /* now that we're running on a pty, we get \r's as well.  Remove them. */
  ptr = index(line, '\r');
  if(ptr != NULL) {
    ptr[0] = '\0';
    ptr = NULL;
  }
   
  /* 
   * This basically sucks.  We have to parse the text output of the
   * seti client, using whatever clues we can to determine what a
   * particular message is meant to convey.  The fact that the 
   * seti@home coders are probably not counting on anyone doing this
   * means that 1) they're not too worried about their messages being 
   * syntactically distinct, and 2) they feel perfectly free to change
   * wordings at will.  Oh well, we'll adapt.
   */
 
  if(!strlen(line))
    return SH_SUCCESS;
  
  /*-----------------------------------------------------*\
   | string: "SETI@home client"                          |
   | action: ignore                                      |
  \*-----------------------------------------------------*/
  if((ptr = strstr(line, "SETI@home client")))
  {
    /* try and make extra-sure this is the right string..  (they've
     * changed it from version to version, and I'm just matching the
     * common bits.  If we've got some other string, let go..
     */
    if( (ptr != line) && (line[strlen(line)-1] == '.'))
    {
      parse_bail(line);
      return SH_ERR_GENERIC;
    }
    return SH_SUCCESS;
  }

  /*-----------------------------------------------------*\
   | string: Platform: <platform>                        |
   | action: scromp <platform> and save it away          |
  \*-----------------------------------------------------*/
  if((ptr = strstr(line, "Platform: ")))
  {
    if(ptr != line) 
    {
      parse_bail(line);
      return SH_ERR_GENERIC;
    }
    ptr += strlen("Platform: ");
    sh_client_set_platform(client, ptr);
    return SH_SUCCESS;
  }
  
  /*-----------------------------------------------------*\
   | string: Version: <version>                          |
   | action: scromp <version> and save it away           |
  \*-----------------------------------------------------*/
  if((ptr = strstr(line, "Version: ")))
  {
    if(ptr != line)
    {
      parse_bail(line);
      return SH_ERR_GENERIC;
    }
    ptr += strlen("Version: ");
    sh_client_set_version(client, ptr);
    return SH_SUCCESS;
  }
  
  /*-----------------------------------------------------*\
   | string: "Sending result - connecting to server"     |
   | action: change state to reflect this client task    |
   |         this also means we've processed a work unit.|
   |         increment that count.                       |
  \*-----------------------------------------------------*/
  if(!strcmp(line, "Sending result - connecting to server."))
  {
    sh_client_get(client)->state = SH_STATE_TRANSFERRING;
    shwin_status_change(client);
    sh_client_set_task(client, line);
    return SH_SUCCESS;
  }
  
  /*-----------------------------------------------------*\
   | string: "All data sent"                             |
   | action: change state to reflect this client task    |
  \*-----------------------------------------------------*/
  if(!strcmp(line, "All data sent."))
  {
    sh_client_get(client)->state = SH_STATE_WORKING;
    shwin_status_change(client);
    return SH_SUCCESS;
  }
  
  /*-----------------------------------------------------*\
   | string: "Sending data: "                            |
   | action: ignore (there's only one of them, usually)  |
  \*-----------------------------------------------------*/
  if((ptr = strstr(line, "Sending data: ")))
  {
    if(ptr != line)
    {
      parse_bail(line);
      return SH_ERR_GENERIC;
    }
    return SH_SUCCESS;
  }

  /*-----------------------------------------------------*\
   | string: "Done with work unit."                      |
   | action: change state to reflect this client task    |
  \*-----------------------------------------------------*/
  if(!strcmp(line, "Done with work unit."))
  {
    sh_client_set_completed(client, 100.00);
    sh_client_increment_work(client);
    return SH_SUCCESS;
  }
 
  /*-----------------------------------------------------*\
   | string: "Logged in as <domain> (<email>)"           |
   | action: scromp <email> and save away                |
  \*-----------------------------------------------------*/
  if((ptr = strstr(line, "Logged in as ")))
  {
    if(ptr != line)
    {
      parse_bail(line);
      return SH_ERR_GENERIC;
    }
    ptr  = index(line, '(');
    if(ptr == NULL) {
      parse_bail(line);
      return SH_ERR_GENERIC;
    }
    ptr++;
    if(strlen(ptr) > 256) {
      dbg(DBG_ERROR, "Erk.  Account line too long.\n");
      return SH_ERR_GENERIC;
    }
    strncpy(buf, ptr, strlen(ptr) - 1);
    buf[strlen(ptr)-1] = '\0'; /* the above doesn't null-terminate */
    sh_client_set_account(client, buf);
    return SH_SUCCESS;
  }
  
  /*-----------------------------------------------------*\
   | string: "Number of work units processed: <num>"     |
   | action: scromp <num> and save away                  |
  \*-----------------------------------------------------*/
  if((ptr = strstr(line, "Number of work units processed: ")))
  {
    extern int total_complete; /* currently a global in shmain.c */
    
    if(ptr != line)
    {
      parse_bail(line);
      return SH_ERR_GENERIC;
    }
    ptr += strlen("Number of work units processed: ");
    total_complete = atoi(ptr);
    return SH_SUCCESS;
  } 
  
  /*-----------------------------------------------------*\
   | string: "Total CPU time: <float>"                   |
   | action: scromp <float> and save away                |
  \*-----------------------------------------------------*/
  if((ptr = strstr(line, "Total CPU time: ")))
  {
    extern int   total_complete, session_complete;
    extern float total_cpu;
    
    if(ptr != line)
    {
      parse_bail(line);
      return SH_ERR_GENERIC;
    }
    ptr += strlen("Total CPU time: ");
    total_cpu = atof(ptr);
    
    /* this is the last bit of status we get upon login, so print */
    dbg(DBG_INFO, "%s total units: %d, total cpu: %.2f hours\n",
        sh_client_get(client)->account, 
        total_complete,
        total_cpu);
    dbg(DBG_INFO, "Total units processed in this setiherder session: %d\n",
        session_complete);
    
    return SH_SUCCESS;
  }
 
  /*-----------------------------------------------------*\
   | string: "Found data file:"                          |
   | action: ignore                                      |
  \*-----------------------------------------------------*/
  if((ptr = strstr(line, "Found data file")))
  {
    if(ptr != line)
    {
      parse_bail(line);
      return SH_ERR_GENERIC;
    }
    return SH_SUCCESS;
  }
  
  /*-----------------------------------------------------*\
   | string: "Scanning checkpoint file."                 |
   | action: ignore                                      |
  \*-----------------------------------------------------*/
  if(!strcmp(line, "Scanning checkpoint file."))
  {
    return SH_SUCCESS;
  }
  
  /*-----------------------------------------------------*\
   | string: "Getting data - connecting to server."      |
   | action: change state                                |
  \*-----------------------------------------------------*/
  if(!strcmp(line, "Getting data - connecting to server."))
  {
    sh_client_get(client)->state = SH_STATE_TRANSFERRING;
    shwin_status_change(client);
    return SH_SUCCESS;
  }
  
  /*-----------------------------------------------------*\
   | string: "Receiving data: <numK>"                    |
   | action: pluck out <numK> and update gui             |
  \*-----------------------------------------------------*/
  if((ptr = strstr(line, "Receiving data: ")))
  {
    float percent, k, chunk; 

    if(ptr != line)
    {
      parse_bail(line);
      return SH_ERR_GENERIC;
    }
    ptr  += strlen("Receiving data: ");
    k     = (float) atoi(ptr);
    chunk = (float) CHUNKSIZE;
    percent = (k/chunk);
    sh_client_set_completed(client, percent*100);
    sh_client_set_task(client,"Receiving data");
    return SH_SUCCESS;
  }
  
  /*-----------------------------------------------------*\
   | string: "All data received."                        |
   | action: change state                                |
  \*-----------------------------------------------------*/
  if(!strcmp(line, "All data received."))
  {
    sh_client_set_completed(client, 0.00);
    sh_client_get(client)->state = SH_STATE_WORKING;
    shwin_status_change(client);
    return SH_SUCCESS;
  }
  
  /*-----------------------------------------------------*\
   | string: "Scanning data file"                        |
   | action: ignore                                      |
  \*-----------------------------------------------------*/
  if(!strcmp(line, "Scanning data file"))
  {
    return SH_SUCCESS;
  }
  
  /*-----------------------------------------------------*\
   | string: "Scanning result header file."              |
   | action: ignore                                      |
  \*-----------------------------------------------------*/
  if(!strcmp(line, "Scanning result header file."))
  {
    return SH_SUCCESS;
  }
  
  /*-----------------------------------------------------*\
   | string: "Data Info:"                                |
   | action: ignore                                      |
  \*-----------------------------------------------------*/
  if(!strcmp(line, "Data Info:"))
  {
    return SH_SUCCESS;
  }
  
  /*-----------------------------------------------------*\
   | string: "Sky coordinates: <string>"                 |
   | action: pluck out <string> and save                 |
  \*-----------------------------------------------------*/
  if((ptr = strstr(line, "Sky coordinates: ")))
  {
    if(ptr != line)
    {
      parse_bail(line);
      return SH_ERR_GENERIC;
    }
    ptr += strlen("Sky coordinates: ");
    sh_client_set_coords(client, ptr);
    return SH_SUCCESS;
  }
  
  /*-----------------------------------------------------*\
   | string: "Recorded on: <string>"                     |
   | action: pluck out <string> and save                 |
  \*-----------------------------------------------------*/
  if((ptr = strstr(line, "Recorded on: ")))
  {
    if(ptr != line)
    {
      parse_bail(line);
      return SH_ERR_GENERIC;
    }
    ptr += strlen("Recorded on: ");
    sh_client_set_recdate(client, ptr);
    return SH_SUCCESS;
  }

  /*-----------------------------------------------------*\
   | string: "Source: <string>"                          |
   | action: pluck out <string> and save                 |
  \*-----------------------------------------------------*/
  if((ptr = strstr(line, "Source: ")))
  {
    if(ptr != line)
    {
      parse_bail(line);
      return SH_SUCCESS;
    }
    ptr += strlen("Source: ");
    sh_client_set_source(client, ptr);
    return SH_SUCCESS;
  }
    
  /*-----------------------------------------------------*\
   | string: "Base Frequencey: <string>"                 |
   | action: pluck out <string> and save                 |
  \*-----------------------------------------------------*/
  if((ptr = strstr(line, "Base Frequency: ")))
  {
    if(ptr != line)
    {
      parse_bail(line);
      return SH_ERR_GENERIC;
    }
    ptr += strlen("Base Frequency: ");
    sh_client_set_basefreq(client, ptr);
    return SH_SUCCESS;
  }
  
  /*-----------------------------------------------------*\
   | string: "Doing baseline smoothing."                 |
   | action: ignore (for now)                            |
  \*-----------------------------------------------------*/
  if(!strcmp(line, "Doing baseline smoothing."))
  {
    sh_client_set_task(client, line);
    return SH_SUCCESS;
  }  
  
  /*------------------------------------------------------------------*\
   | string: "Starting work for Doppler shift rate <x>, FFT length y" |
   | action: ignore (for now)                                         |
  \*------------------------------------------------------------------*/
  if((ptr = strstr(line, "Starting work for Doppler shift rate ")))
  {
    if(ptr != line)
    {
      parse_bail(line);
      return SH_ERR_GENERIC;
    }
    ptr = strstr(line, "rate ");
    ptr += strlen("rate ");
    sh_client_set_dop_shift_rt(client, atof(ptr));
    ptr = strstr(line, "length ");
    ptr += strlen("length ");
    sh_client_set_fft_len(client, atoi(ptr));
    sh_client_set_task(client, "Working");
    return SH_SUCCESS;
  }
 
  /*------------------------------------------------------*\
   | string: "Doppler shifting data by <num> Hz/s"        |
   | action: ignore (for now)                             |
  \*------------------------------------------------------*/
  if((ptr = strstr(line, "Doppler shifting data by ")))
  {
    if(ptr != line)
    {
      parse_bail(line);
      return SH_ERR_GENERIC;
    }
    ptr = strstr(line, "by ");
    ptr += strlen("by ");
    sh_client_set_last_shift(client, atof(ptr));
    return SH_SUCCESS;
  }

  /*------------------------------------------------------*\
   | string: "Starting Gaussian curve-fitting"            |
   | action: ignore (for now)                             |
  \*------------------------------------------------------*/
  if((ptr = strstr(line, "Starting Gaussian curve-fitting")))
  {
    if(ptr != line)
    {
      parse_bail(line);
      return SH_ERR_GENERIC;
    }
    sh_client_set_task(client, line);
    return SH_SUCCESS;
  }
  
  /*------------------------------------------------------*\
   | string: "x.yyyyyy$ done"                             |
   | action: update status line                           |
  \*------------------------------------------------------*/
  if((ptr = strstr(line, " done")))
  {
    sh_client_set_completed(client, atof(line));
    return SH_SUCCESS;
  }
 
  /*------------------------------------------------------*\
   | string: "Unexpected end of data from server"         |
   | action: update status line and log                   |
  \*------------------------------------------------------*/
  if(!strcmp(line, "Unexpected end of data from server"))
  {
    dbg(DBG_WARN, "SETI@home client reports: %s\n", line);
    return SH_SUCCESS;
  }
 
  /*------------------------------------------------------*\
   | string: "Server is down; will retry in an hour."     |
   | action: update status line and log                   |
  \*------------------------------------------------------*/
  if(!strcmp(line, "Server is down; will retry in an hour."))
  {
    dbg(DBG_WARN, "SETI@home client reports: %s\n", line);
    sh_client_get(client)->state = SH_STATE_WAITING;
    shwin_status_change(client);
    return SH_SUCCESS;
  }

  /*------------------------------------------------------*\
   | string: "Another instance of SETI..."                |
   | action: update status line and log                   |
  \*------------------------------------------------------*/
  if(!strcmp(line, 
         "Another instance of SETI@home is running in this directory"))
  {
    dbg(DBG_WARN, "SETI@home client reports: %s\n", line);
    dbg(DBG_WARN, "The client may not have exited properly last time..\n");
    return SH_SUCCESS;
  }
  
  /*------------------------------------------------------*\
   | string: "SETI@home is sponsored"                     |
   | action: grumble about why the hell they had to put   |
   |         all this crap in the client and not hide it  |
   |         behind a --about switch or something. :-/    |
  \*------------------------------------------------------*/
  if((ptr = strstr(line, "SETI@home is sponsored")))
  {
    return SH_SUCCESS;
  }

  /*------------------------------------------------------*\
   | string: "If you'd like to contribute"                |
   | action: grumble more                                 |
  \*------------------------------------------------------*/
  if((ptr = strstr(line, "If you'd like to contribute")))
  {
    return SH_SUCCESS;
  }
  
  /*------------------------------------------------------*\
   | string: "please visit the SETI@home"                 |
   | action: grumble more                                 |
  \*------------------------------------------------------*/
  if((ptr = strstr(line, "please visit the SETI@home")))
  {
    return SH_SUCCESS;
  }
   
  /*------------------------------------------------------*\
   | string: "http://setiathome.ssl.berkeley.edu"         |
   | action: grumble more                                 |
  \*------------------------------------------------------*/
  if((ptr = strstr(line, "http://setiathome.ssl.berkeley.edu")))
  {
    return SH_SUCCESS;
  }
   
  /*------------------------------------------------------*\
   | string: "The project is also sponsored"              |
   | action: grumble more                                 |
  \*------------------------------------------------------*/
  if((ptr = strstr(line, "The project is also sponsored")))
  {
    return SH_SUCCESS;
  }
   
  /*------------------------------------------------------*\
   | string: "the University of California, Sun"          |
   | action: grumble more                                 |
  \*------------------------------------------------------*/
  if((ptr = strstr(line, "the University of California, Sun")))
  {
    return SH_SUCCESS;
  }
  
  /*------------------------------------------------------*\
   | string: "Fujifilm Computer Products, Informix"       |
   | action: grumble more                                 |
  \*------------------------------------------------------*/
  if((ptr = strstr(line, "Fujifilm Computer Products, Informix")))
  {
    return SH_SUCCESS;
  } 
  
  /*------------------------------------------------------*\
   | string: "The Santa Cruz Operation"                   |
   | action: grumble more                                 |
  \*------------------------------------------------------*/
  if((ptr = strstr(line, "The Santa Cruz Operation")))
  {
    return SH_SUCCESS;
  }
    
  /*------------------------------------------------------*\
   | string: "and the SETI Institute."                    |
   | action: grumble more                                 |
  \*------------------------------------------------------*/
  if((ptr = strstr(line, "and the SETI Institute.")))
  {
    return SH_SUCCESS;
  }
  
  /*------------------------------------------------------*\
   | string: "SETI@home was developed"                    |
   | action: grumble more                                 |
  \*------------------------------------------------------*/
  if((ptr = strstr(line, "SETI@home was developed")))
  {
    return SH_SUCCESS;
  }
  
  /*------------------------------------------------------*\
   | string: "Fenton"                                     |
   | action: grumble more                                 |
  \*------------------------------------------------------*/
  if((ptr = strstr(line, "Fenton")))
  {
    return SH_SUCCESS;
  }
  
  /*------------------------------------------------------*\
   | string: "Peter Leiser"                               |
   | action: grumble more                                 |
  \*------------------------------------------------------*/
  if((ptr = strstr(line, "Peter Leiser")))
  {
    return SH_SUCCESS;
  }

  /*------------------------------------------------------*\
   | string: "Werthimer"                                  |
   | action: grumble more                                 |
  \*------------------------------------------------------*/
  if((ptr = strstr(line, "Werthimer")))
  {
    return SH_SUCCESS;
  }
  
  /*------------------------------------------------------*\
   | string: "New software version - deleting"            |
   | action: ignore                                       |
  \*------------------------------------------------------*/
  if((ptr = strstr(line, "New software version - deleting")))
  {
    return SH_SUCCESS;
  }
  
  /* unknown message at this point. */
  parse_bail(line);
          
  return 0;  
}

sh_rc parse_buffer(char *buf, int n, int client)
{
  char  line[SH_MAX_LINE+1],
       *cur;
  int   i;
 
  /*
   * split incoming data out into lines and process them
   * individually..
   */
  
  cur = buf;

  for(i=0; i<n; i++) 
  {
    if(buf[i] == '\n')
    {
      if(cur == buf+i)
        cur++;           /* skip blank newlines */
      else
      {
        buf[i] = '\0';
        strncpy(line, cur, SH_MAX_LINE);
        cur = buf+i+1;
        buf[i] = '\n';
        parse_line(line, client);
      }
    }
  }

  if(cur != buf+n)   /* there's some leftover */
  {
    dbg(DBG_DIAG, "Eek!  I was hoping this wouldn't happen.\n");
    return 1;
  }
  return 0;  
}

sh_rc shwin_status_change(int idx)
{
  char tmp[20];

  if(sh_client_get(idx) == NULL)
    return SH_ERR_GENERIC;

  if(clist == NULL)
    return SH_SUCCESS;  /* gui not yet up */
  
  /* hostname (sure, why not?) */
  if(sh_client_get(idx)->hostname != NULL)
  {
    gtk_clist_set_text(GTK_CLIST(clist),
              idx,
              0,
              sh_client_get(idx)->hostname);
  }
  
  /* account */
  if(sh_client_get(idx)->account != NULL)
  {
    gtk_clist_set_text(GTK_CLIST(clist),
                       idx,
                       1,
                       sh_client_get(idx)->account);
  }

  /* state */
  gtk_clist_set_text(GTK_CLIST(clist),
                     idx,
                     2,
                     sh_state_list[sh_client_get(idx)->state]);
  
  /* units done */
#ifdef HAVE_SNPRINTF  
  snprintf(tmp, 19, "%d", sh_client_get(idx)->units_done);
#else
  sprintf(tmp, "%d", sh_client_get(idx)->units_done);
#endif  
  gtk_clist_set_text(GTK_CLIST(clist),
                     idx,
                     3,
                     tmp);
  
  /* percent */
#ifdef HAVE_SNPRINTF  
  snprintf(tmp, 19, "%.2f%%", sh_client_get(idx)->completed);
#else
  sprintf(tmp, "%.2f%%", sh_client_get(idx)->completed);
#endif  
  gtk_clist_set_text(GTK_CLIST(clist),
                     idx, 
                     4,
                     tmp);
  
  return SH_SUCCESS;
}

sh_rc shwin_increment_session_work()
{
  extern int session_complete;
  session_complete++;
  return SH_SUCCESS;
}


