/*  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"
#include "observation.h"


/* main window */
extern struct mw mw;

/* preferences data */
extern struct pd pd;


/* root of entry data list */
GList *entries;

/* handy colours for highlighting */
GdkColor red, green;


/* initialise the entries list */
void entry_initialise()
{
  GdkColormap *colourmap = gdk_colormap_get_system();

  /* allocate a pair of colours */
  red.red = 0xffff;
  red.green = 0;
  red.blue = 0;
  if (!gdk_color_alloc(colourmap, &red)) {
    g_error("couldn't allocate red");
  }                   

  green.red = 0;
  green.green = 0xffff;
  green.blue = 0;
  if (!gdk_color_alloc(colourmap, &green)) {
    g_error("couldn't allocate green");
  }

  /* create the entry list */
  entries = g_list_alloc();
  entries->data = NULL;
}


void entry_highlight_inactive(int row)
{
  gtk_clist_set_background (mw.list, row, NULL);
}


void entry_highlight_active(int row)
{
  gtk_clist_set_background (mw.list, row, &green);
}


void entry_highlight_connect(int row)
{
  gtk_clist_set_background (mw.list, row, &red);
}


/* find an entry with the given name */
struct entry *entry_find (char *name) {
  return (struct entry *) g_list_find_custom (entries, name, 
					      (GCompareFunc) &strcmp);
}


/* update a specific entry in the list */
void entry_update (int row, struct ed *ed)
{
  gchar *new = time_to_text(ed->time);
  gtk_clist_set_text (mw.list, row, 0, new);
  g_free (new);
}


/* update all entries in the list */
void entry_refresh()
{
  GList *list;

  list = g_list_first (entries);
  while (list != NULL) {
    if (list->data != NULL) {
      struct ed *ed = (struct ed *)list->data;
      entry_update (ed->row, ed);
    }
    list = g_list_next (list);
  }
}

/* zero all entries in the list */
void entry_reset()
{
  GList *list;

  list = g_list_first (entries);
  while (list != NULL) {
    if (list->data != NULL) {
      struct ed *ed = (struct ed *)list->data;
      ed->time=0;
    }
    list = g_list_next (list);
  }
}


/* append an entry to the list given a name and time */
struct ed *entry_append_internal (char *name, time_t time)
{
  struct ed *new;
  char *columns[ENTRYLISTCOLUMNS];

  new = g_new(struct ed, 1);
  new->name = name;
  new->time = time;
  new->node = list_append(entries, new);
    
  columns[0] = time_to_text(new->time);
  columns[1] = new->name;
  if (pd.totals) {
    new->row = gtk_clist_insert (mw.list, mw.total.row-1, columns);
    mw.total.row++;
  } else {
    new->row = gtk_clist_append (mw.list, columns);
  }
  total_adjust (new->time);
  g_free (columns[0]);
  gtk_clist_set_row_data (mw.list, new->row, new);

  return new;
}


void entry_append (char *name, time_t time) {
  (void) entry_append_internal (name, time);
}


/* remove an entry */
void entry_remove (struct ed *ed)
{
  GList *next;
  int active = FALSE;

  /* if we are charging to the entry we are deleting */
  if (mw.charging) {
    if (ed == mw.ed) {
      /* turn off charging first */
      gtk_toggle_button_set_active (mw.charging, FALSE);
      active = TRUE; 
    }
  }

  /* remove the value from the total */
  total_adjust (-(ed->time));

  /* take note of the next list item */
  next = g_list_next (ed->node);

  /* invalidate the current item pointers */
  mw.ed = NULL;
  mw.row = -1;

  /* remove the item from the doubly linked list */
  g_list_remove (ed->node, ed->node->data);
 
  /* delete the row from the table */
  gtk_clist_remove (mw.list, ed->row);

  /* delete any event log entries for this task */
  observation_purge (ed);

  /* decrement row number for each node in the list after this one */
  while (next != NULL) {
    struct ed *ed = next->data;
    ed->row--;
    next = g_list_next (next);
  }

  /* free the struct */
  g_free(ed->name);
  g_free(ed);

  /* remember the totals moved too (oetiker@ee.ethz.ch) */
  mw.total.row--;

  /* no need to update windows tree, it is based on entry pointers */

  if (active) 
    gtk_toggle_button_set_active (mw.charging, TRUE);
}


/* whether an fprintf() error has been reported yet, 
   to avoid generating multiple message windows. */
gint entry_save_error;


/* per-entry function - save the entry name to the names file */
void entry_save_name (gpointer data, gpointer user_data)
{
  struct ed *ed = (struct ed *) data;
  FILE *file = (FILE *) user_data;
  char *text;

  if (ed == NULL) return;
  if (fprintf (file, "%s\n", ed->name) > 0) return;
  if (entry_save_error) return;
  entry_save_error++;
  text = g_strdup_printf ("Save names failed,\n"
			  "Could not write to file,\n"
			  "%s.", g_strerror (errno));
  message ("gfocustimer save fprintf() failed", text);
  g_free (text);
}


/* per-entry function - save the entry name and time to the per-day file */
void entry_save_data (gpointer data, gpointer user_data)
{
  struct ed *ed = (struct ed *) data;
  FILE *file = (FILE *) user_data;
  char *text;

  if (ed == NULL) return;
  if (fprintf (file, "%ld\t\"%s\"\n", ed->time, ed->name) > 0) return;
  if (entry_save_error) return;
  entry_save_error++;
  text = g_strdup_printf ("Save data failed,\n"
			  "Could not write to file,\n"
			  "%s.", g_strerror (errno));
  message ("gfocustimer save fprintf() failed", text);
  g_free (text);
}


/* open a save file, reporting any errors */
FILE *entry_save_open (char *prefix, char *suffix)
{
  char *name;
  FILE *file;

  name = g_strdup_printf ("%s/%s", prefix, suffix);
  file = fopen (name, "w");
  /* ??? 0.2: fix protection on ~/.gfocustimer created by 0.1 */
  if (file == NULL && errno == EACCES) {
    chmod (prefix, 0700);
    file = fopen (name, "w");
  }
  if (file == NULL) {
    char *text = g_strdup_printf ("Save failed,\n"
				  "Could not open for write %s,\n"
				  "%s.", name, g_strerror (errno));
    message ("gfocustimer save fopen() failed", text);
    g_free (text);
    g_free (name);
    return NULL;
  }
  g_free (name);
  return file;
}


/* save the entry database */
void entry_save()
{
  char *prefix;
  char *suffix;
  FILE *file;

  /* find the directory */
  prefix = file_prefix();

  /* save the entry names */
  suffix = FILESUFFIXNAMES;
  file = entry_save_open (prefix, suffix);
  if (file == NULL) {
    g_free (prefix);
    return;
  }
  entry_save_error = 0;
  g_list_foreach (entries, entry_save_name, file);
  fclose (file);

  /* create file name from date */
  suffix = file_suffix();

  /* save the entry data */
  file = entry_save_open (prefix, suffix);
  if (file == NULL) {
    g_free (suffix);
    g_free (prefix);
    return;
  }
  entry_save_error = 0;
  g_list_foreach (entries, entry_save_data, file);
  fclose (file);

  g_free (suffix);
  g_free (prefix);
}


/* load the entry database from file */
void entry_load()
{
  char *prefix;
  char *suffix;
  char *name;
  FILE *file;
  enum {
    STYLE_PER_DAY,
    STYLE_NAMES
  } style;

  /* force column zero to automatically resize to contents */
  gtk_clist_set_column_auto_resize (mw.list, 0, TRUE);

  /* attempt to read from today's per-day file */
  style = STYLE_PER_DAY;
  prefix = file_prefix();
  suffix = file_suffix();
  name = g_strdup_printf ("%s/%s", prefix, suffix);
  file = fopen (name, "r");
  if (file == NULL) {

    /* per-day file could not be opened */
    if (errno != ENOENT) {

      /* and it wasn't merely not there, report it */
      char *text = g_strdup_printf ("Load failed,\n"
				    "Could not open for read %s,\n"
				    "%s (%d).", name, g_strerror (errno), 
				    errno);
      message ("gfocustimer load fopen() failed", text);
      g_free (text);
      g_free (name);
      g_free (suffix);
      g_free (prefix);
      return;
    }

    /* clean up after per-day file attempt */
    g_free (name);
    g_free (suffix);

    /* try the names file */
    style = STYLE_NAMES;
    suffix = g_strdup(FILESUFFIXNAMES);
    name = g_strdup_printf ("%s/%s", prefix, suffix);
    file = fopen (name, "r");
    if (file == NULL) {

      /* names file could not be opened */
      if (errno != ENOENT) {

	/* and it wasn't merely not there, report it */
	char *text = g_strdup_printf ("Load failed,\n"
				      "Could not open for read %s,\n"
				      "%s (%d).", name, g_strerror (errno), 
				      errno);
	message ("gfocustimer load fopen() failed", text);
	g_free (text);
	g_free (name);
	g_free (suffix);
	g_free (prefix);
	return;
      }

      /* no load file, add default */
      entry_append (g_strdup("task one"), 0);
      entry_append (g_strdup("task two"), 0);
      entry_append (g_strdup("task three"), 0);
      g_free (name);
      g_free (suffix);
      g_free (prefix);
      return;
    } /* file != NULL, successful open of names file */
  } /* file != NULL, successful open of per-day file */

  /* read file records */
  while (1) {
    char *line, *time, *name;
    char buffer[MAXLINELENGTH];
    
    /* read a line from file */
    line = fgets (buffer, MAXLINELENGTH, file);
    if (line == NULL) break;

    /* skip any comment lines */
    if (line[0] == '#') continue;
    line[strlen(line)-1] = '\0';

    /* split fields from line according to style */
    if (style == STYLE_PER_DAY) {
      time = strtok (line, "\t");
      if (time == NULL) continue;
      
      name = strtok (NULL, "\"");
      if (name == NULL) continue;
    } else {
      time = "0";
      name = line;
    }

    /* create the entry that was read in */
    entry_append (g_strdup(name), atoi(time));
  }

  fclose (file);
  g_free (name);
  g_free (suffix);
  g_free (prefix);
  return;
}

/*
 * Entry Window							********
 */


void entry_create()
{
  struct ew *ew = g_new(struct ew, 1);
  ew->widget = create_entry ();
  ew->creating = 1;
  gtk_widget_show (ew->widget);
  gtk_object_set_data (GTK_OBJECT (ew->widget), POINTER, ew);
}


void entry_edit()
{
  struct ew *ew = g_new(struct ew, 1);
  GtkEntry *name, *time;

  if (mw.ed == NULL) return;

  ew->widget = create_entry ();
  ew->creating = 0;
  ew->row = mw.row;
  ew->ed = mw.ed;
  ew->time = mw.ed->time;
  gtk_widget_show (ew->widget);
  gtk_object_set_data (GTK_OBJECT (ew->widget), POINTER, ew);
  
  /* find the widget pointers for the entry fields */
  name = GTK_ENTRY(lookup (ew->widget, "name"));
  time = GTK_ENTRY(lookup (ew->widget, "time"));

  /* set the entry fields to the selected entry data */
  gtk_entry_set_text (name, mw.ed->name);
  gtk_entry_set_text (time, time_to_text(mw.ed->time));
}


void entry_apply (struct ew *ew)
{
  GtkEntry *widget;
  char *name, *time;
  
  /* get the data off the window */
  widget = GTK_ENTRY (lookup (ew->widget, "name"));
  if (widget == NULL) return;
  name = g_strdup (gtk_entry_get_text (widget));
  
  widget = GTK_ENTRY (lookup (ew->widget, "time"));
  if (widget == NULL) return;
  time = g_strdup (gtk_entry_get_text (widget));
  
  /* creating or modifying? */
  if (ew->creating) {

    /* creating an entry, pass the buck */
    entry_append (name, text_to_time(time));
    g_free (time);

  } else {
    time_t added;

    /* modifying an entry */
    g_free(ew->ed->name);
    ew->ed->name = name;

    added = text_to_time(time) - ew->time;
    total_adjust (added);
    ew->ed->time += added;

    gtk_clist_set_text (mw.list, ew->ed->row, 0, time_to_text (ew->ed->time));
    gtk_clist_set_text (mw.list, ew->ed->row, 1, name);
    g_free (time);
  }
}


void entry_cancel (struct ew *ew)
{
  gtk_widget_destroy (ew->widget);
  g_free (ew);
}


void
on_entry_apply_clicked                 (GtkButton       *button,
                                        gpointer         user_data)
{
  struct ew *ew = entry_of_widget(GTK_WIDGET(button));
  entry_apply (ew);
}


void
on_entry_cancel_clicked                (GtkButton       *button,
                                        gpointer         user_data)
{
  struct ew *ew = entry_of_widget(GTK_WIDGET(button));
  entry_cancel (ew);
}


void
on_entry_ok_clicked                    (GtkButton       *button,
                                        gpointer         user_data)
{
  struct ew *ew = entry_of_widget(GTK_WIDGET(button));
  entry_apply (ew);
  entry_cancel (ew);
}


gboolean
on_entry_delete_event                  (GtkWidget       *widget,
                                        GdkEvent        *event,
                                        gpointer         user_data)
{
  struct ew *ew = entry_of_widget(widget);
  g_free (ew);
  return FALSE;
}


void
on_delete_ok_clicked                   (GtkButton       *button,
                                        gpointer         user_data)
{
  entry_remove((struct ed *)user_data);
  gtk_widget_destroy (gtk_widget_get_toplevel (GTK_WIDGET(button)));
}


void entry_delete()
{
  char *message;

  if (mw.ed == NULL) return;

  /* cancel a pending connection */
  connect_deschedule();
  entry_highlight_active (mw.row);

  /* queue a query for the user */
  message = g_strdup_printf ("\nDelete entry \"%s\"?\n", mw.ed->name);
  question ("gfocustimer delete entry",
	    message,
	    on_delete_ok_clicked,
	    mw.ed);
  g_free (message);
}


/* called when the user selects a row in the entry list */
void
on_list_select_row                     (GtkCList        *clist,
                                        gint             row,
                                        gint             column,
                                        GdkEvent        *event,
                                        gpointer         user_data)
{
  time_t now = time (NULL);

  /* ignore any attempt to select the total lines */
  if (pd.totals) {
    if (row > mw.total.row-2) {
      gtk_clist_unselect_row (mw.list, row, 0);
      return;
    }
  }

  /* enable edit and delete now that an entry is selected */
  gtk_widget_set_sensitive (GTK_WIDGET(mw.edit), 1);
  gtk_widget_set_sensitive (GTK_WIDGET(mw.delete), 1);

  /* undo the selection by the user, to avoid the blue highlight */
  gtk_clist_unselect_row (mw.list, row, 0);

  /* finish the previous observation period */
  if (mw.charge) observation_end (now);

  /* ignore this select if we are already on this row */
  if (mw.row != row) {

    /* turn off the highlight of the previous selected row */
    if (mw.ed != NULL) {
      entry_highlight_inactive (mw.row);
    }
    
    /* store pointers to the currently selected entry */
    mw.row = row;
    mw.ed = (struct ed *) gtk_clist_get_row_data (mw.list, mw.row);
  }

  /* if we are in focus mode, take note of the connecting sequence */
  if (gtk_toggle_button_get_active(mw.focus)) {
    entry_highlight_connect (mw.row);
    
    /* cancel the focus watch timeout */
    focus_deschedule();
    
    /* cancel and reschedule the connect timeout */
    connect_deschedule();
    connect_schedule();

    /* the period of the connection is still a manual observation */
    if (mw.charge)
      observation_start_manual (now, mw.ed);

  } else {
    /* set the row to green */
    entry_highlight_active (mw.row);

    /* commence the observation on the new entry */
    if (mw.charge)
      observation_start_manual (now, mw.ed);
  }
}


gboolean
on_list_button_press_event             (GtkWidget       *widget,
                                        GdkEventButton  *event,
                                        gpointer         user_data)
{
  if (GTK_IS_CLIST (widget) &&
      (event->type == GDK_2BUTTON_PRESS)) {
    on_Edit__activate (NULL, NULL);
  }
  
  return FALSE;
}


gboolean
on_list_button_release_event           (GtkWidget       *widget,
                                        GdkEventButton  *event,
                                        gpointer         user_data)
{
  return FALSE;
}


void
on_entry_name_activate                 (GtkEditable     *editable,
                                        gpointer         user_data)
{
  struct ew *ew = entry_of_widget(GTK_WIDGET(editable));
  entry_apply (ew);
  entry_cancel (ew);
}


void
on_entry_time_activate                 (GtkEditable     *editable,
                                        gpointer         user_data)
{
  struct ew *ew = entry_of_widget(GTK_WIDGET(editable));
  entry_apply (ew);
  entry_cancel (ew);
}

