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

/*
 * guppi-price-series-core-impl.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-price-series-core.h"

#include <libgnome/gnome-defs.h>
#include <libgnome/gnome-config.h>
#include <libgnome/gnome-i18n.h>

#include <guppi-convenient.h>
#include <guppi-memory.h>

typedef struct _PriceDay PriceDay;
struct _PriceDay {
  GDate dt;
  guint code;
  double op, hi, lo, cl, vo, oi;
};

static GtkObjectClass *parent_class = NULL;

static void
guppi_price_series_core_finalize (GtkObject *obj)
{
  GuppiPriceSeriesCore *core = GUPPI_PRICE_SERIES_CORE (obj);
  PriceDay **data;
  gint i;

  data = (PriceDay **)guppi_garray_data (core->garray);
  for (i=0; i<core->size; ++i) {
    guppi_free (data[i]);
    data[i] = NULL;
  }

  guppi_unref0 (core->garray);

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

/***************************************************************************/

static PriceDay*
get_priceday (GuppiPriceSeriesCore *core, const GDate *date,
	      gboolean create_if_missing)
{
  gint i;
  PriceDay *pd;
  PriceDay **data;

  g_return_val_if_fail (core && GUPPI_IS_PRICE_SERIES_CORE (core), NULL);
  g_return_val_if_fail (date && g_date_valid ((GDate *) date), NULL);

  if (core->size == 0) {
    if (!create_if_missing)
      return NULL;
    pd = guppi_new0 (PriceDay, 1);
    pd->dt = *date;
    core->start_date = *date;
    core->end_date = *date;
    guppi_garray_set_size (core->garray, 186);
    core->size = 1;
    data = (PriceDay **)guppi_garray_data (core->garray);
    data[0] = pd;
    return pd;
  }

  i =  (gint)g_date_julian ((GDate *) date)
    - (gint)g_date_julian (&core->start_date);

  if (0 <= i && i < core->size) {
    data = (PriceDay **) guppi_garray_data (core->garray);
    if (create_if_missing && data[i] == NULL) {
      pd = guppi_new0 (PriceDay, 1);
      pd->dt = *date;
      data[i] = pd;
    }

    /* Check that everything is still in sync. */
    g_assert (data[i] == NULL
	      || (g_date_compare (&(data[i]->dt), (GDate *) date) == 0));

    return data[i];
  }

  if (i < 0) {
    gint new_size, j;

    if (!create_if_missing)
      return NULL;

    new_size = core->size - i;

    if (guppi_garray_size (core->garray) < new_size)
      guppi_garray_set_size (core->garray, new_size);

    data = (PriceDay **) guppi_garray_data (core->garray);

    for (j = core->size-1; j >= 0; --j)
      data[j-i] = data[j];
    for (j=1; j<-i; ++j)
      data[j] = NULL;

    pd = guppi_new0 (PriceDay, 1);
    pd->code = 0;
    pd->dt = *date;

    core->size = new_size;
    core->start_date = *date;
    data[0] = pd;

    return pd;
    
  }

  if (i >= core->size) {
    gint new_size, j;

    if (!create_if_missing)
      return NULL;

    new_size = i+1;
    
    if (guppi_garray_size (core->garray) < new_size)
      guppi_garray_set_size (core->garray, new_size);

    data = (PriceDay **) guppi_garray_data (core->garray);

    for (j = core->size; j < new_size-1; ++j)
      data[j] = NULL;

    pd = guppi_new0 (PriceDay, 1);
    pd->dt = *date;

    core->size = new_size;
    core->end_date = *date;
    data[i] = pd;

    return pd;
  }

  g_assert_not_reached ();
  return NULL;
}

static gint
priceday_offset (guint code)
{
  PriceDay pd;
  gpointer data;
  
  switch (code) {
    
  case PRICE_OPEN:
    data = &pd.op;
    break;

  case PRICE_HIGH:
    data = &pd.hi;
    break;

  case PRICE_LOW:
    data = &pd.lo;
    break;

  case PRICE_CLOSE:
    data = &pd.cl;
    break;

  case PRICE_VOLUME:
    data = &pd.vo;
    break;

  case PRICE_OPEN_INTEREST:
    data = &pd.oi;
    break;

  default:
    data = NULL;
    g_assert_not_reached ();
  }

  return (guchar *)data - (guchar *)&pd;
}

static GuppiData *
v_data_copy (GuppiData *impl)
{
  g_assert_not_reached ();
  return NULL;
}

static gint
v_data_size_in_bytes (GuppiData *impl)
{
  GuppiPriceSeriesCore *core = GUPPI_PRICE_SERIES_CORE (impl);
  gint x = 0;

  x += sizeof (GuppiPriceSeriesCore);
  x += core->size * sizeof (PriceDay);
  x += guppi_garray_size (core->garray) * sizeof (PriceDay *);

  return x;
}

static void
v_dateind_bounds (GuppiDateIndexed *impl, GDate *start, GDate *end)
{
  GuppiPriceSeriesCore *core = GUPPI_PRICE_SERIES_CORE (impl);

  if (start)
    *start = core->start_date;

  if (end)
    *end = core->end_date;
}

static gboolean
v_dateind_valid (GuppiDateIndexed *impl, const GDate *date)
{
  GuppiPriceSeriesCore *core = GUPPI_PRICE_SERIES_CORE (impl);
  PriceDay *pd;

  pd = get_priceday (core, date, FALSE);

  return pd && pd->code;
}

/*
static gboolean
v_dateind_step (GuppiDateIndexed *impl, const GDate *orig,
		gint delta, GDate *modified)
{

}
*/

static gint
v_dateind_size (GuppiDateIndexed *impl)
{
  GuppiPriceSeriesCore *core = GUPPI_PRICE_SERIES_CORE (impl);
  PriceDay **data;
  gint count = 0;
  gint i;

  data = (PriceDay **) guppi_garray_data (core->garray);

  for (i=0; i < core->size; ++i)
    if (data[i] != NULL && data[i]->code)
      ++count;

  return count;
}

static guint
v_ps_valid (GuppiPriceSeries *impl, const GDate *date)
{
  GuppiPriceSeriesCore *core = GUPPI_PRICE_SERIES_CORE (impl);
  PriceDay *pd;

  pd = get_priceday (core, date, FALSE);

  return pd ? pd->code : 0;
}

static double
v_ps_get (GuppiPriceSeries *impl, guint code, const GDate *date)
{
  GuppiPriceSeriesCore *core = GUPPI_PRICE_SERIES_CORE (impl);
  PriceDay *pd;
  pd = get_priceday (core, date, FALSE);
  
  if (pd == NULL || (pd->code & code) == 0)
    return 0;

  switch (code) {

  case PRICE_OPEN:
    return pd->op;
    
  case PRICE_HIGH:
    return pd->hi;

  case PRICE_LOW:
    return pd->lo;

  case PRICE_CLOSE:
    return pd->cl;

  case PRICE_VOLUME:
    return pd->vo;

  case PRICE_OPEN_INTEREST:
    return pd->oi;

  default:
    return 0;
  };

  g_assert_not_reached();
  return 0;
}

static void
v_ps_set (GuppiPriceSeries *impl, guint code, const GDate *date, double x)
{
  GuppiPriceSeriesCore *core = GUPPI_PRICE_SERIES_CORE (impl);
  PriceDay *pd;
  pd = get_priceday (core, date, TRUE);

  switch (code) {
  case PRICE_OPEN:
    pd->op = x;
    break;

  case PRICE_HIGH:
    pd->hi = x;
    break;

  case PRICE_LOW:
    pd->lo = x;
    break;

  case PRICE_CLOSE:
    pd->cl = x;
    break;

  case PRICE_VOLUME:
    pd->vo = x;
    break;

  case PRICE_OPEN_INTEREST:
    pd->oi = x;
    break;

  default:
    g_assert_not_reached();
  }

  pd->code |= code;
}

static gint
v_ps_get_many (GuppiPriceSeries *impl,
	       const GDate *start_date, gint count,
	       double *timestamp_buffer,
	       double *open_buffer,
	       double *high_buffer,
	       double *low_buffer,
	       double *close_buffer)
{
  GuppiPriceSeriesCore *core = GUPPI_PRICE_SERIES_CORE (impl);
  PriceDay **data;
  PriceDay *day;
  gint sj, i, i1, j;

  sj = g_date_julian (&core->start_date);
  i = (gint)g_date_julian ((GDate *) start_date) - sj;
  i1 = (gint)g_date_julian (&core->end_date) - sj;
  data = (PriceDay **)guppi_garray_data (core->garray);

  j = 0;
  while (count && 0 <= i && i <= i1) {
    if ( (day = data[i]) ) {

      if (timestamp_buffer)
	timestamp_buffer[j] = sj + i;
      if (open_buffer)
	open_buffer[j] = day->op;
      if (high_buffer)
	high_buffer[j] = day->hi;
      if (low_buffer)
	low_buffer[j] = day->lo;
      if (close_buffer)
	close_buffer[j] = day->cl;
      ++j;

      if (count > 0)
	--count;
      else
	++count;
    }

    if (count > 0)
      ++i;
    else
      --i;
  }

  return j;
}


static gint
v_ps_get_range (GuppiPriceSeries *impl,
		const GDate *start_date, const GDate *end_date,
		double *timestamp_buffer,
		double *open_buffer,
		double *high_buffer,
		double *low_buffer,
		double *close_buffer)
{
  GuppiPriceSeriesCore *core = GUPPI_PRICE_SERIES_CORE (impl);
  PriceDay **data;
  PriceDay *day;
  gint sj, i, j = 0, i0, i1;

  sj = g_date_julian (&core->start_date);
  i0 = (gint)g_date_julian ((GDate *) start_date) - sj;
  i1 = (gint)g_date_julian ((GDate *) end_date) - sj;

  data = (PriceDay **)guppi_garray_data (core->garray);

  for (i = i0; i <= i1; ++i) {
    if ( (day = data[i]) ) {
      if (timestamp_buffer)
	timestamp_buffer[j] = sj + i;
      if (open_buffer)
	open_buffer[j] = day->op;
      if (high_buffer)
	high_buffer[j] = day->hi;
      if (low_buffer)
	low_buffer[j] = day->lo;
      if (close_buffer)
	close_buffer[j] = day->cl;
      ++j;
    }
  }
  
  return j;
}

static gboolean
v_ps_get_bounds (GuppiPriceSeries *impl, guint code,
		 const GDate *start_date, const GDate *end_date, 
		 double *min, double *max)
{
  GuppiPriceSeriesCore *core = GUPPI_PRICE_SERIES_CORE (impl);
  PriceDay **data;
  PriceDay *day;
  gint sj, i, i0, i1, offset;
  double x, m = 0, M = -1;
  gboolean first = TRUE;

  sj = g_date_julian (&core->start_date);
  i0 = (gint)g_date_julian ((GDate *) start_date) - sj;
  i1 = (gint)g_date_julian ((GDate *) end_date) - sj;

  data = (PriceDay **)guppi_garray_data (core->garray);

  offset = priceday_offset (code);

  for (i = i0; i <= i1; ++i) {
    if ( (day = data[i]) ) {

      x = *(double *)(((guchar *)day) + offset);
      
      if (first) {
	m = M = x;
	first = FALSE;
      } else {
	if (x < m) m = x;
	if (x > M) M = x;
      }


    }
  }

  if (!first) {
    if (min) *min = m;
    if (max) *max = M;
  }
   
  return !first;
}

/***************************************************************************/

static void
guppi_price_series_core_class_init (GuppiPriceSeriesCoreClass *klass)
{
  GtkObjectClass* object_class = (GtkObjectClass *)klass;
  GuppiDataClass *data_class = GUPPI_DATA_CLASS (klass);
  GuppiDateIndexedClass *dind_class = GUPPI_DATE_INDEXED_CLASS (klass);
  GuppiPriceSeriesClass *ps_class = GUPPI_PRICE_SERIES_CLASS (klass);

  parent_class = gtk_type_class(GUPPI_TYPE_PRICE_SERIES);

  object_class->finalize = guppi_price_series_core_finalize;

  data_class->is_leaf_type = TRUE;
  data_class->copy = v_data_copy;
  data_class->get_size_in_bytes = v_data_size_in_bytes;

  dind_class->bounds = v_dateind_bounds;
  dind_class->valid = v_dateind_valid;
  /* dind_class->step = v_dateind_step; */
  dind_class->size = v_dateind_size;

  ps_class->valid = v_ps_valid;
  ps_class->get = v_ps_get;
  ps_class->set = v_ps_set;
  ps_class->get_many = v_ps_get_many;
  ps_class->get_range = v_ps_get_range;
  ps_class->get_bounds = v_ps_get_bounds;
  
}

static void
guppi_price_series_core_init (GuppiPriceSeriesCore *obj)
{
  g_date_clear (&obj->start_date, 1);
  g_date_clear (&obj->end_date, 1);
  obj->size = 0;
  obj->garray = guppi_garray_new (sizeof (PriceDay *));
}

GtkType
guppi_price_series_core_get_type (void)
{
  static GtkType guppi_price_series_core_type = 0;
  if (!guppi_price_series_core_type) {
    static const GtkTypeInfo guppi_price_series_core_info = {
      "GuppiPriceSeriesCore",
      sizeof(GuppiPriceSeriesCore),
      sizeof(GuppiPriceSeriesCoreClass),
      (GtkClassInitFunc)guppi_price_series_core_class_init,
      (GtkObjectInitFunc)guppi_price_series_core_init,
      NULL, NULL, (GtkClassInitFunc)NULL
    };
    guppi_price_series_core_type = gtk_type_unique (GUPPI_TYPE_PRICE_SERIES, &guppi_price_series_core_info);
  }
  return guppi_price_series_core_type;
}

GuppiPriceSeries *
guppi_price_series_core_new (void)
{
  return GUPPI_PRICE_SERIES (guppi_type_new (guppi_price_series_core_get_type ()));
}


syntax highlighted by Code2HTML, v. 0.9.1