/*  gfocustimer
 *  Copyright (C) 1999 James Cameron, <quozl@us.netrek.org>
 *
 *  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.
*/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

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

#include "callbacks.h"
#include "interface.h"
#include "support.h"
#include "utilities.h"
#include "data.h"
#include "total.h"
#include "entry.h"
#include "connect.h"
#include "charge.h"


extern struct mw mw;


/* root of data list */
GList *observations;

/* observation counter */
int oc;

/* pending observation */
struct od *pending;

/* selected observation */
struct od *selected;

/* time already accumulated into current entry */
time_t already;

/* the list widget */
GtkCList *clist;

/* the observation list window */
GtkWidget *ow;


#ifdef DEBUG
#undef MAXOBSERVATIONS
#define MAXOBSERVATIONS 8
#endif


/* initialise the observations list */
void observation_initialise()
{
  /* create the observation list */
  observations = g_list_alloc();
  observations->data = NULL;

  /* reset the count of observations */
  oc = 0;

  pending = NULL;

  clist = NULL;
  ow = NULL;
}

char *to_event(struct od *od)
{
  switch (od->type) {
  case OD_MANUAL:   return "Manual";
  case OD_FOCUS:    return "Focus";
  case OD_EXTERNAL: return "External";
  default: return "invalid";
  }
}

char *to_detail(struct od *od)
{
  switch (od->type) {
  case OD_MANUAL:   return "";
  case OD_FOCUS:    return od->un.focus.name;
  case OD_EXTERNAL: return od->un.external.name;
  default: return "invalid";
  }
}

#ifdef DEBUG
void observation_dump (struct od *od)
{
  printf ("%08x: type=%s", (int) od, to_event(od));
  printf ("start=%d, ", (int) od->start);
  printf ("value=%s, ", time_to_text (od->value));
  if (od->ed != NULL)
    printf ("ed->name=%s, ", od->ed->name);
  else
    printf ("ed=NULL, ");

  switch (od->type) {
  case OD_FOCUS:
    printf ("focus->name=%s, ", to_detail(od));
    break;
  case OD_EXTERNAL:
    printf ("external->name=%s, ", to_detail(od));
    break;
  }

  printf ("\n");
}
#endif


void observations_list_append (struct od *od)
{
  char *columns[5];
  int row;
  
  columns[0] = ctime (&od->start);
  columns[1] = time_to_text (od->value);
  if (od->ed != NULL)
    columns[2] = od->ed->name;
  else
    columns[2] = "";
  columns[3] = to_event(od);
  columns[4] = to_detail(od);

  row = gtk_clist_append (clist, columns);
  gtk_clist_set_row_data (clist, row, od);
}


void observations_list_append_all ()
{
  GList *list = g_list_first (observations);

  gtk_clist_freeze (clist);
  gtk_clist_clear (clist);

  while (list != NULL) {
    struct od *od = (struct od *)list->data;
    if (od != NULL) observations_list_append (od);
    list = g_list_next (list);
  }
  gtk_clist_thaw (clist);
}


/* remove an observation from the list */
void observation_remove (struct od *od)
{
  int row;

  /* if the list is displayed */
  if (ow != NULL) {
    /* find the observation in the list */
    row = gtk_clist_find_row_from_data (clist, od);
    /* if it is there */
    if (row != -1) {
      /* is this the selected row? unselect it! */
      if (od == selected) gtk_clist_unselect_row (clist, row, 0);
      /* remove it */
      gtk_clist_remove (clist, row);
    }
  }

  switch (od->type) {
  case OD_MANUAL:
    break;
  case OD_FOCUS:
    g_free (od->un.focus.name);
    break;
  case OD_EXTERNAL:
    g_free (od->un.external.name);
    break;
  }
  g_list_remove (observations, od);
  g_free (od);
  oc--;
}


/* remove all observations that reference an entry being deleted */
void observation_purge (struct ed *ed)
{
  GList *list = g_list_first (observations);

  if (ow != NULL) gtk_clist_freeze (clist);

  while (list != NULL) {
    /* obtain a pointer to the observation at this point in the list */
    struct od *od = (struct od *)list->data;

    /* go to next list member before possibly removing this one */
    list = g_list_next (list);

    /* remove this one if it matches the entry given */
    if (od != NULL)
      if (od->ed == ed) observation_remove (od);
  }

  if (ow != NULL) gtk_clist_thaw (clist);
}


/* append an observation to the list */
void observation_append (struct od *od)
{
  GList *list;
  struct od *last;

#ifdef DEBUG
  observation_dump (od);
#endif

  if (ow != NULL) {
    gtk_clist_freeze (clist);
  }

  /* merge this new observation with latest one if they are the same */
  list = g_list_last (observations);
  last = (struct od *)list->data;
  if (last != NULL) {
    if (last->type == od->type && last->ed == od->ed) {
      int same = 0;

      switch (od->type) {
      case OD_MANUAL:
	same++;
	break;

      case OD_FOCUS:
	if (last->un.focus.window == od->un.focus.window &&
	    ! strcmp (last->un.focus.name, od->un.focus.name) &&
	    last->un.focus.wd == od->un.focus.wd) same++;
	break;

      case OD_EXTERNAL:
	if (! strcmp (last->un.external.name, od->un.external.name)) same++;
	break;
      }

      if (same) {
	last->value += od->value;
	g_free (od);

	/* if the window is displayed ... */
	if (ow != NULL) {
	  /* find the observation in the list */
	  int row = gtk_clist_find_row_from_data (clist, last);
	  /* if it is there, update the value */
	  if (row != -1) {
	    gtk_clist_set_text (clist, row, 1, time_to_text (last->value));
	  }
	  gtk_clist_thaw (clist);
	}
	return;
      }
    }
  }

  /* append to the list */
  od->node = list_append (observations, od);

  /* update the displayed list */
  if (ow != NULL) {
    observations_list_append (od);
  }

  /* increment the observations counter and perhaps remove oldest */
  if (++oc > MAXOBSERVATIONS) {
    list = g_list_first (observations);
    if (list->data == NULL) list = g_list_next (list);
    observation_remove ((struct od *)list->data);
  }

  if (ow != NULL) {
    gtk_clist_thaw (clist);
  }
}


/* end an observation */
void observation_end (time_t end)
{
  if (!pending) return;

  pending->value = end - pending->start;
  observation_append (pending);

  /* accumulate the elapsed time */
  if (pending->ed != NULL) {
    time_t elapsed = pending->value - already;
    pending->ed->time += elapsed;
    total_adjust (elapsed);
    entry_update (pending->ed->row, pending->ed);
  }

#ifdef LOG
  {
    char *stamp = log_stamp(pending->start);
    char *value = time_to_text(pending->value);
    char *event = to_event(pending);
    char *detail = to_detail(pending);
    char *name = (pending->ed != NULL) ? pending->ed->name : "";
    char *total = (pending->ed != NULL) ? time_to_text(pending->ed->time) : g_strdup("");

    printf("%s\tevent\t%s\t%s\t%s\t%s\t%s\n", stamp, value, event, detail,
	   name, total);
    g_free(stamp);
    g_free(value);
    g_free(total);
  }
#endif

  pending = NULL;
}


/* start a manual observation */
void observation_start_manual (time_t start, struct ed *ed)
{
  if (pending) return;

  pending = g_new(struct od, 1);
  pending->type = OD_MANUAL;
  pending->start = start;
  pending->ed = ed;
  /* ??? some day someone will delete an entry, 
     so we need a way to clear this pointer */
  already = 0;
}


/* start a focus observation */
void observation_start_focus (time_t start, struct ed *ed, 
			       Window window, char *name, struct wd *wd)
{
  if (pending) return;

  pending = g_new(struct od, 1);
  pending->type = OD_FOCUS;
  pending->start = start;
  pending->ed = ed;
  pending->un.focus.window = window;
  pending->un.focus.name = g_strdup (name);
  pending->un.focus.wd = wd;
  already = 0;
}


/* append an external observation to the list */
void observation_start_external (time_t start, struct ed *ed, 
				  char *name)
{
  if (pending) return;

  pending = g_new(struct od, 1);
  pending->type = OD_EXTERNAL;
  pending->start = start;
  pending->ed = ed;
  pending->un.external.name = g_strdup (name);
  already = 0;
}


void observation_update (time_t now)
{
  time_t elapsed;

  if (!pending) return;
  if (!pending->ed) return;

  elapsed = now - pending->start;
  pending->ed->time += elapsed - already;
  total_adjust (elapsed - already);
  entry_update (pending->ed->row, pending->ed);
  already = elapsed;
}



gboolean
on_observations_delete_event           (GtkWidget       *widget,
                                        GdkEvent        *event,
                                        gpointer         user_data)
{
  gtk_widget_set_sensitive (lookup_widget(mw.widget, "Log"), 1);
  gtk_widget_hide(ow);
  return TRUE;
}


void
on_observations_close_activate         (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{
  gtk_widget_set_sensitive (lookup_widget(mw.widget, "Log"), 1);
  gtk_widget_hide(ow);
}


void
on_observations_list_show              (GtkWidget       *widget,
                                        gpointer         user_data)
{
}


void
on_observations_close_clicked          (GtkButton       *button,
                                        gpointer         user_data)
{
  gtk_widget_set_sensitive (lookup_widget(mw.widget, "Log"), 1);
  gtk_widget_hide(ow);
}


void
on_observations_activate               (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{
  gtk_widget_set_sensitive (lookup_widget(mw.widget, "Log"), 0);

  if (ow != NULL) {
    gtk_widget_show(ow);
    return;
  }

  ow = create_observations();
  clist = GTK_CLIST(lookup_widget(ow, "observations_list"));

  gtk_clist_set_column_auto_resize (clist, 0, TRUE);
  gtk_clist_set_column_auto_resize (clist, 1, TRUE);
  gtk_clist_set_column_auto_resize (clist, 2, TRUE);
  gtk_clist_set_column_auto_resize (clist, 3, TRUE);
  gtk_clist_set_column_auto_resize (clist, 4, TRUE);
  observations_list_append_all ();
  gtk_widget_set_sensitive (lookup_widget(ow, "observations_remove"), 0);
  gtk_widget_set_sensitive (lookup_widget(ow, "observations_reassign"), 0);
  gtk_widget_show(ow);
}


void
on_observations_list_select_row        (GtkCList        *clist,
                                        gint             row,
                                        gint             column,
                                        GdkEvent        *event,
                                        gpointer         user_data)
{
  selected = gtk_clist_get_row_data (clist, row);
  gtk_widget_set_sensitive (lookup_widget(ow, "observations_remove"), 1);
  gtk_widget_set_sensitive (lookup_widget(ow, "observations_reassign"), 1);
}


void
on_observations_list_unselect_row      (GtkCList        *clist,
                                        gint             row,
                                        gint             column,
                                        GdkEvent        *event,
                                        gpointer         user_data)
{
  selected = NULL;
  gtk_widget_set_sensitive (lookup_widget(ow, "observations_remove"), 0);
  gtk_widget_set_sensitive (lookup_widget(ow, "observations_reassign"), 0);
}


void
on_observations_remove_clicked         (GtkButton       *button,
                                        gpointer         user_data)
{
  if (selected == NULL) return;

  if (selected->ed != NULL) {
    selected->ed->time -= selected->value;
    total_adjust (-selected->value);
    entry_update (selected->ed->row, selected->ed);
  }

  observation_remove (selected);
}


void
on_observations_reassign_clicked       (GtkButton       *button,
                                        gpointer         user_data)
{
  GList *next;

  if (selected == NULL) return;
  if (selected->ed == NULL) return;

  /* remove the value from the entry it has */
  selected->ed->time -= selected->value;
  entry_update (selected->ed->row, selected->ed);

  /* get the next (or the first) entry */
  next = g_list_next (selected->ed->node);
  if (next == NULL) {
    next = g_list_first (selected->ed->node);
  }

  if (next->data == NULL) {
    next = g_list_next (next);
  }
  
  selected->ed = (struct ed *) next->data;
  
  /* add the value into the newly reassigned entry */
  selected->ed->time += selected->value;
  entry_update (selected->ed->row, selected->ed);

  /* update the cell */
  {
    int row = gtk_clist_find_row_from_data (clist, selected);
    gtk_clist_set_text (clist, row, 2, selected->ed->name);
  }
}
