/* This is -*- C -*- */
/* vim: set sw=2: */

/*
 * guppi-date-indexed.c
 *
 * Copyright (C) 2000 EMC Capital Management, Inc.
 * Copyright (C) 2001 The Free Software Foundation
 *
 * Developed by Jon Trowbridge <trow@gnu.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
 */

#include <config.h>
#include "guppi-date-indexed.h"

#include <stdlib.h>
#include <string.h>
#include <guppi-convenient.h>
#include <guppi-memory.h>
#include <guppi-i18n.h>

typedef struct _GuppiDateIndexedPrivate GuppiDateIndexedPrivate;
struct _GuppiDateIndexedPrivate {

  gboolean have_bounds;
  GDate start_date, end_date;

  gboolean have_size;
  gint size;

};

#define priv(x) ((GuppiDateIndexedPrivate *)((x)->opaque_internals))

static GtkObjectClass * parent_class = NULL;

static void
guppi_date_indexed_finalize (GtkObject *obj)
{
  GuppiDateIndexed *ind = GUPPI_DATE_INDEXED (obj);
  GuppiDateIndexedPrivate *p = priv (ind);

  p->have_bounds = FALSE;
  p->have_size = FALSE;

  guppi_free (ind->opaque_internals);
  ind->opaque_internals = NULL;

  if (parent_class->finalize)
    parent_class->finalize (obj);
}

static gchar *
get_size_info (GuppiData *d)
{
  GuppiDateIndexed *ind = GUPPI_DATE_INDEXED (d);
  const GDate *dt1, *dt2;
  gchar dt1buf[32];
  gchar dt2buf[32];

  if (guppi_date_indexed_empty (ind))
    return guppi_strdup (_("empty"));

  dt1 = guppi_date_indexed_start (ind);
  dt2 = guppi_date_indexed_end (ind);

  if (dt1 && dt2 && g_date_valid ((GDate *)dt1) && g_date_valid ((GDate *)dt2)) {
    g_date_strftime (dt1buf, 32, "%x", (GDate *) dt1);
    g_date_strftime (dt2buf, 32, "%x", (GDate *) dt2);
    return guppi_strdup_printf (_("%s to %s"), dt1buf, dt2buf);
  }
   
  return guppi_strdup (_("invalid"));
}

static void
changed (GuppiData *data)
{
  GuppiDateIndexedPrivate *p = priv (GUPPI_DATE_INDEXED (data));

  p->have_bounds = p->have_size = FALSE;
  
  if (GUPPI_DATA_CLASS (parent_class)->changed)
    GUPPI_DATA_CLASS (parent_class)->changed (data);
}

static void
export_xml (GuppiData *data, GuppiXMLDocument *doc, xmlNodePtr content_node)
{
  GuppiDateIndexed *ind = GUPPI_DATE_INDEXED (data);
  GuppiDateIndexedClass *klass;
  GDate dt;
  gchar buf[64];
  xmlNodePtr days_node;

  klass = GUPPI_DATE_INDEXED_CLASS (GTK_OBJECT (data)->klass);

  if (! klass->export_xml_element) {
    xmlAddChild (content_node,  xmlNewComment ("XML element format undefined."));
    return;
  }

  days_node = guppi_xml_new_node (doc, "Days");
  xmlAddChild (content_node, days_node);

  dt = *guppi_date_indexed_start (ind);
  while (guppi_date_indexed_in_bounds (ind, &dt)) {

    xmlNodePtr node = klass->export_xml_element (ind, &dt, doc);
    g_snprintf (buf, 64, "%d-%d-%d",
		g_date_year ((GDate *)&dt), g_date_month ((GDate *)&dt), g_date_day ((GDate *)&dt));
    xmlNewProp (node, "date", buf);
    if (node)
      xmlAddChild (days_node, node);
      
    guppi_date_indexed_incr (ind, &dt);
  }
}

static gboolean
import_xml (GuppiData *data, GuppiXMLDocument *doc, xmlNodePtr node)
{
  GuppiDateIndexed *ind = GUPPI_DATE_INDEXED (data);
  GuppiDateIndexedClass *klass;
  GDate dt;
  gchar *buf;
  gint y, m, d;
    
  klass = GUPPI_DATE_INDEXED_CLASS (GTK_OBJECT (data)->klass);

  if (! klass->import_xml_element) {
    g_warning ("XML element reader undefined.");
    return FALSE;
  }

  if (! strcmp (node->name, "Days")) {

    node = node->xmlChildrenNode;
    while (node) {
    
      buf = xmlGetProp (node, "date");
      if (buf && sscanf (buf, "%d-%d-%d", &y, &m, &d) == 3) {
	g_date_set_dmy (&dt, d, m, y);
	klass->import_xml_element (ind, &dt, doc, node);
	xmlFree (buf);
      }
      node = node->next;
    }

    return TRUE;
  }

  if (GUPPI_DATA_CLASS (parent_class)->import_xml)
    return GUPPI_DATA_CLASS (parent_class)->import_xml (data, doc, node);
  else
    return FALSE;
}

static void
guppi_date_indexed_class_init (GuppiDateIndexedClass *klass)
{
  GtkObjectClass *object_class = (GtkObjectClass *)klass;
  GuppiDataClass *data_class = GUPPI_DATA_CLASS (klass);

  parent_class = gtk_type_class(GUPPI_TYPE_DATA);

  object_class->finalize = guppi_date_indexed_finalize;

  data_class->get_size_info = get_size_info;

  data_class->changed    = changed;
  data_class->export_xml = export_xml;
  data_class->import_xml = import_xml;
}

static void
guppi_date_indexed_init (GuppiDateIndexed *obj)
{
  GuppiDateIndexedPrivate *p;

  p = guppi_new0 (GuppiDateIndexedPrivate, 1);
  obj->opaque_internals = p;
}

GtkType
guppi_date_indexed_get_type (void)
{
  static GtkType guppi_date_indexed_type = 0;
  if (!guppi_date_indexed_type) {
    static const GtkTypeInfo guppi_date_indexed_info = {
      "GuppiDateIndexed",
      sizeof(GuppiDateIndexed),
      sizeof(GuppiDateIndexedClass),
      (GtkClassInitFunc)guppi_date_indexed_class_init,
      (GtkObjectInitFunc)guppi_date_indexed_init,
      NULL, NULL, (GtkClassInitFunc)NULL
    };
    guppi_date_indexed_type = gtk_type_unique (GUPPI_TYPE_DATA,
					       &guppi_date_indexed_info);
  }
  return guppi_date_indexed_type;
}

static void
get_bounds (GuppiDateIndexed *ind)
{
  GuppiDateIndexedPrivate *p;
  GuppiDateIndexedClass *klass;
  
  p = priv (ind);

  klass = GUPPI_DATE_INDEXED_CLASS (GTK_OBJECT (ind)->klass);

  g_assert (klass->bounds);

  klass->bounds (ind, &p->start_date, &p->end_date);

  p->have_bounds = TRUE;
}


const GDate *
guppi_date_indexed_start (GuppiDateIndexed *ind)
{
  GuppiDateIndexedPrivate *p;

  g_return_val_if_fail (GUPPI_IS_DATE_INDEXED (ind), NULL);

  p = priv (ind);

  if (! p->have_bounds)
    get_bounds (ind);
  
  return &p->start_date;
}

const GDate *
guppi_date_indexed_end (GuppiDateIndexed *ind)
{
  GuppiDateIndexedPrivate *p;

  g_return_val_if_fail (GUPPI_IS_DATE_INDEXED (ind), NULL);

  p = priv (ind);

  if (! p->have_bounds)
    get_bounds (ind);

  return &p->end_date;
}

#define IN_LOWER_BOUND(p, dt) (g_date_compare (&(p)->start_date, (GDate *)(dt)) <= 0)
#define IN_UPPER_BOUND(p, dt) (g_date_compare (&(p)->end_date, (GDate *)(dt)) >= 0)

#define IN_BOUNDS(p, dt) (IN_LOWER_BOUND (p, dt) && IN_UPPER_BOUND (p, dt))
#define OUT_OF_BOUNDS(p, dt) (!(IN_BOUNDS (p, dt)))

gboolean
guppi_date_indexed_in_bounds (GuppiDateIndexed *ind, const GDate *dt)
{
  GuppiDateIndexedPrivate *p;

  g_return_val_if_fail (GUPPI_IS_DATE_INDEXED (ind), FALSE);
  g_return_val_if_fail (dt && g_date_valid ((GDate *) dt), FALSE);

  p = priv (ind);

  if (! p->have_bounds)
    get_bounds (ind);

  return IN_BOUNDS (p, dt);
}

void
guppi_date_indexed_clamp (GuppiDateIndexed *ind, GDate *dt)
{
  GuppiDateIndexedPrivate *p;

  g_return_if_fail (GUPPI_IS_DATE_INDEXED (ind));
  g_return_if_fail (dt && g_date_valid (dt));

  p = priv (ind);

  if (! p->have_bounds)
    get_bounds (ind);

  if (g_date_lt (dt, &p->start_date))
    *dt = p->start_date;
  else if (g_date_lt (&p->end_date, dt))
    *dt = p->end_date;
}

gboolean
guppi_date_indexed_valid (GuppiDateIndexed *ind, const GDate *dt)
{
  GuppiDateIndexedPrivate *p;
  GuppiDateIndexedClass *klass;

  g_return_val_if_fail (GUPPI_IS_DATE_INDEXED (ind), FALSE);
  g_return_val_if_fail (dt && g_date_valid ((GDate *) dt), FALSE);

  p = priv (ind);

  if (! p->have_bounds)
    get_bounds (ind);

  if (OUT_OF_BOUNDS (p, dt))
    return FALSE;

  klass = GUPPI_DATE_INDEXED_CLASS (GTK_OBJECT (ind)->klass);

  g_assert (klass->valid);

  return klass->valid (ind, dt);
}

gboolean
guppi_date_indexed_step (GuppiDateIndexed *ind, GDate *dt, gint delta)
{
  GuppiDateIndexedPrivate *p;
  GDate min_stepped_date;

  GuppiDateIndexedClass *klass;

  g_return_val_if_fail (GUPPI_IS_DATE_INDEXED (ind), FALSE);
  g_return_val_if_fail (dt != NULL && g_date_valid (dt), FALSE);
  
  if (delta == 0)
    return TRUE;

  p = priv (ind);
  if (! p->have_bounds)
    get_bounds (ind);

  /* Make sure we aren't starting out of bounds. */
  if (OUT_OF_BOUNDS (p, dt))
    return FALSE;

  /* Make sure that we aren't guaranteed to end up out of bounds,
     no matter when happens when we step. */
  min_stepped_date = *dt;
  g_date_add_days (&min_stepped_date, delta);
  if (OUT_OF_BOUNDS (p, &min_stepped_date)) {
    *dt = min_stepped_date;
    return FALSE;
  }
      
  klass = GUPPI_DATE_INDEXED_CLASS (GTK_OBJECT(ind)->klass);

  if (klass->step) {
    GDate base_dt = *dt;
    
    return klass->step (ind, &base_dt, delta, dt);

  } else {

    /* Walk through the dates, decrementing delta every time we pass over
       a valid date. */

    g_assert (klass->valid);

    while (delta) {

      if (delta > 0) {
	g_date_add_days (dt, 1);
	if (! IN_UPPER_BOUND (p, dt))
	  return FALSE;
      } else {
	g_date_subtract_days (dt, 1);
	if (! IN_LOWER_BOUND (p, dt))
	  return FALSE;
      }

      if (klass->valid (ind, dt)) 
	delta += (delta > 0) ? -1 : +1;
    }
  }

  return TRUE;
}

gboolean
guppi_date_indexed_incr (GuppiDateIndexed *ind, GDate *dt)
{
  return guppi_date_indexed_step (ind, dt, 1);
}

gboolean
guppi_date_indexed_decr (GuppiDateIndexed *ind, GDate *dt)
{
  return guppi_date_indexed_step (ind, dt, -1);
}

gint
guppi_date_indexed_size (GuppiDateIndexed *ind)
{
  GuppiDateIndexedPrivate *p;

  g_return_val_if_fail (GUPPI_IS_DATE_INDEXED (ind), -1);

  if (guppi_date_indexed_empty (ind))
    return 0;

  p = priv (ind);

  if (! p->have_size) {
    GuppiDateIndexedClass *klass;

    klass = GUPPI_DATE_INDEXED_CLASS (GTK_OBJECT (ind)->klass);

    if (klass->size) {

      p->size = klass->size (ind);

    } else {

      /* We do this the slow way: we walk across our dataset,
	 counting up valid days. */

      GDate dt;

      g_assert (klass->valid);

      if (! p->have_bounds)
	get_bounds (ind);

      dt = p->start_date;

      p->size = 0;

      if (g_date_valid (&dt)) {
	while (IN_UPPER_BOUND (p, &dt)) {
	
	  if (klass->valid (ind, &dt))
	    ++p->size;

	  g_date_add_days (&dt, 1);
	  
	}
      }
    }
      
    p->have_size = TRUE;

  }

  return p->size;
}

gboolean
guppi_date_indexed_empty (GuppiDateIndexed *ind)
{
  gboolean sv, ev;

  g_return_val_if_fail (GUPPI_IS_DATE_INDEXED (ind), TRUE);

  sv = g_date_valid ((GDate *) guppi_date_indexed_start (ind));
  ev = g_date_valid ((GDate *) guppi_date_indexed_end (ind));

  if (sv && ev)
    return FALSE;
  if ((!sv) && (!ev))
    return TRUE;

  g_assert_not_reached ();

  return FALSE;
}

gboolean
guppi_date_indexed_nonempty (GuppiDateIndexed *ind)
{
  g_return_val_if_fail (GUPPI_IS_DATE_INDEXED (ind), FALSE);

  return !guppi_date_indexed_empty (ind);
}

void
guppi_date_indexed_bounds_hint (GuppiDateIndexed *ind, const GDate *start, const GDate *end)
{
  GuppiDateIndexedClass *klass;
  
  g_return_if_fail (GUPPI_IS_DATE_INDEXED (ind));
  g_return_if_fail (start && g_date_valid ((GDate *)start));
  g_return_if_fail (end && g_date_valid ((GDate *)end));

  if (g_date_compare ((GDate *)start, (GDate *)end) > 0)
    return;

  klass = GUPPI_DATE_INDEXED_CLASS (GTK_OBJECT (ind)->klass);

  if (klass->bounds_hint) 
    klass->bounds_hint (ind, start, end);
 
}




syntax highlighted by Code2HTML, v. 0.9.1