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

/*
 * guppi-seq-categorical.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-seq-categorical.h"

/* #include <gnome.h> */
#include <gtk/gtksignal.h>

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

#include <guppi-convenient.h>


typedef struct _GuppiSeqCategoricalPrivate GuppiSeqCategoricalPrivate;
struct _GuppiSeqCategoricalPrivate {
  GuppiCategory   *category;
  GuppiSeqInteger *seq;

  gboolean auto_add;

  GHashTable *freq_table;
};

static GtkObjectClass *parent_class = NULL;

static void
guppi_seq_categorical_finalize (GtkObject *obj)
{
  GuppiSeqCategorical *seq = GUPPI_SEQ_CATEGORICAL (obj);

  guppi_unref0 (seq->priv->category);

  if (seq->priv->freq_table)
    g_hash_table_foreach (seq->priv->freq_table, guppi_free_hash_val, NULL);
  g_hash_table_destroy (seq->priv->freq_table);
  seq->priv->freq_table = NULL;

  guppi_free0 (seq->priv);

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

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

/* GuppiSeqInteger virtual functions */

static void
range (GuppiSeqInteger *si, gint *min, gint *max)
{
  GuppiSeqInteger *proxy = GUPPI_SEQ_CATEGORICAL (si)->priv->seq;
  GuppiSeqIntegerClass *klass = GUPPI_SEQ_INTEGER_CLASS (GTK_OBJECT (proxy)->klass);

  klass->range (proxy, min, max);
}

static gint
frequency (GuppiSeqInteger *si, gint x)
{
  GuppiSeqInteger *proxy = GUPPI_SEQ_CATEGORICAL (si)->priv->seq;
  GuppiSeqIntegerClass *klass = GUPPI_SEQ_INTEGER_CLASS (GTK_OBJECT (proxy)->klass);

  return klass->frequency (proxy, x);
}

static gint
get (GuppiSeqInteger *si, gint i)
{
  GuppiSeqInteger *proxy = GUPPI_SEQ_CATEGORICAL (si)->priv->seq;
  GuppiSeqIntegerClass *klass = GUPPI_SEQ_INTEGER_CLASS (GTK_OBJECT (proxy)->klass);

  return klass->get (proxy, i);
}

static void
set (GuppiSeqInteger *si, gint i, gint val)
{
  GuppiSeqInteger *proxy = GUPPI_SEQ_CATEGORICAL (si)->priv->seq;
  GuppiSeqIntegerClass *klass = GUPPI_SEQ_INTEGER_CLASS (GTK_OBJECT (proxy)->klass);

  klass->set (proxy, i, val);
}

static void
insert (GuppiSeqInteger *si, gint i, const gint *ptr, gsize N)
{
  GuppiSeqInteger *proxy = GUPPI_SEQ_CATEGORICAL (si)->priv->seq;
  GuppiSeqIntegerClass *klass = GUPPI_SEQ_INTEGER_CLASS (GTK_OBJECT (proxy)->klass);

  klass->insert (proxy, i, ptr, N);
}

/* GuppiSeq virtual functions */

static void
size_hint (GuppiSeq *seq, gsize N)
{
  GuppiSeq *proxy = GUPPI_SEQ (GUPPI_SEQ_CATEGORICAL (seq)->priv->seq);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (proxy)->klass);

  klass->size_hint (proxy, N);
}

static void
get_bounds (GuppiSeq *seq, gint *min, gint *max)
{
  GuppiSeq *proxy = GUPPI_SEQ (GUPPI_SEQ_CATEGORICAL (seq)->priv->seq);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (proxy)->klass);

  klass->get_bounds (proxy, min, max);
}

static void
shift_indices (GuppiSeq *seq, gint delta)
{
  GuppiSeq *proxy = GUPPI_SEQ (GUPPI_SEQ_CATEGORICAL (seq)->priv->seq);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (proxy)->klass);

  klass->shift_indices (proxy, delta);
}

static void
insert_generic (GuppiSeq *seq, gint i, gsize N)
{
  GuppiSeqInteger *proxy = GUPPI_SEQ_INTEGER (GUPPI_SEQ_CATEGORICAL (seq)->priv->seq);
  GuppiSeqIntegerClass *klass = GUPPI_SEQ_INTEGER_CLASS (GTK_OBJECT (proxy)->klass);
  code_t c;
  gint j;

  c = guppi_category_min_code (GUPPI_SEQ_CATEGORICAL (seq)->priv->category);
  for (j = 0; j < (gint) N; ++j) {
    klass->insert (proxy, i, &c, 1);
  }

  /* We don't want to chain insert_generic */
}

static void
delete_many (GuppiSeq *seq, gint i, gsize N)
{
  GuppiSeq *proxy = GUPPI_SEQ (GUPPI_SEQ_CATEGORICAL (seq)->priv->seq);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (proxy)->klass);

  klass->delete_many (proxy, i, N);
}

static gboolean
has_missing (GuppiSeq *seq)
{
  GuppiSeq *proxy = GUPPI_SEQ (GUPPI_SEQ_CATEGORICAL (seq)->priv->seq);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (proxy)->klass);

  return klass->has_missing (proxy);
}

static gsize
missing_count (GuppiSeq *seq)
{
  GuppiSeq *proxy = GUPPI_SEQ (GUPPI_SEQ_CATEGORICAL (seq)->priv->seq);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (proxy)->klass);

  return klass->missing_count (proxy);
}

static gboolean
missing (GuppiSeq *seq, gint i)
{
  GuppiSeq *proxy = GUPPI_SEQ (GUPPI_SEQ_CATEGORICAL (seq)->priv->seq);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (proxy)->klass);

  return klass->missing (proxy, i);
}

static void
set_missing (GuppiSeq *seq, gint i, gboolean x)
{
  GuppiSeq *proxy = GUPPI_SEQ (GUPPI_SEQ_CATEGORICAL (seq)->priv->seq);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (proxy)->klass);

  klass->set_missing (proxy, i, x);
}

static void
set_range_missing (GuppiSeq *seq, gint i0, gint i1, gboolean x)
{
  GuppiSeq *proxy = GUPPI_SEQ (GUPPI_SEQ_CATEGORICAL (seq)->priv->seq);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (proxy)->klass);

  klass->set_range_missing (proxy, i0, i1, x);
}

static void
set_many_missing (GuppiSeq *seq, gint *ind, gsize N, gboolean x)
{
  GuppiSeq *proxy = GUPPI_SEQ (GUPPI_SEQ_CATEGORICAL (seq)->priv->seq);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (proxy)->klass);

  klass->set_many_missing (proxy, ind, N, x);
}

static void
set_all_missing (GuppiSeq *seq, gboolean x)
{
  GuppiSeq *proxy = GUPPI_SEQ (GUPPI_SEQ_CATEGORICAL (seq)->priv->seq);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (proxy)->klass);

  klass->set_all_missing (proxy, x);
}

static void
insert_missing (GuppiSeq *seq, gint i, gboolean x, gsize N)
{
  GuppiSeq *proxy = GUPPI_SEQ (GUPPI_SEQ_CATEGORICAL (seq)->priv->seq);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (proxy)->klass);

  g_message ("guppi-seq-categorical insert_missing");
  klass->insert_missing (seq, i, x, N);
}

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


/*** Frequency Table Updating **** */

/*
  By doing all of this in signals, we ensure that the tables get updated
  even if we directly manipulate the underlying GuppiSeqInteger object.
  Cool, 'eh?
*/

static void
freq_adjust (GuppiSeqCategorical *seq, gint i0, gint i1, gint adj)
{
  GuppiSeqCategoricalPrivate *p = seq->priv;
  GHashTable *freq = p->freq_table;
  gint i;
  gboolean has_missing;
  code_t last_code = GUPPI_INVALID_CODE;
  gpointer last_val = NULL;

  i0 = MAX (i0, guppi_seq_min_index (GUPPI_SEQ (seq)));
  i1 = MIN (i1, guppi_seq_max_index (GUPPI_SEQ (seq)));

  has_missing = guppi_seq_has_missing (GUPPI_SEQ (seq));

  for (i = i0; i <= i1; ++i) {
    code_t c;
    gpointer val;

    if (!has_missing || guppi_seq_available (GUPPI_SEQ (seq), i)) {

      c = (code_t) guppi_seq_integer_get (GUPPI_SEQ_INTEGER (seq), i);
      if (last_val && (c == last_code)) {
	val = last_val;
      } else {
	gpointer ptr = GUPPI_CATEGORY_CODE_TO_POINTER (c);
	val = g_hash_table_lookup (freq, ptr);

	if (val == NULL) {
	  val = guppi_new0 (gint, 1);
	  g_hash_table_insert (freq, ptr, val);
	}

	last_code = c;
	last_val = val;
      }

      *(gint *) val += adj;
    }
  }
}

static void
set_before_cb (GuppiSeq *seq, gint i0, gint i1, gpointer foo)
{
  freq_adjust (GUPPI_SEQ_CATEGORICAL (seq), i0, i1, -1);
}

static void
set_after_cb (GuppiSeq *seq, gint i0, gint i1, gpointer foo)
{
  freq_adjust (GUPPI_SEQ_CATEGORICAL (seq), i0, i1, 1);
}

static void
delete_before_cb (GuppiSeq *seq, gint i, gsize N, gpointer foo)
{
  freq_adjust (GUPPI_SEQ_CATEGORICAL (seq), i, i - 1 + N, -1);
}

static void
insert_after_cb (GuppiSeq *seq, gint i, gsize N, gpointer foo)
{
  freq_adjust (GUPPI_SEQ_CATEGORICAL (seq), i, i - 1 + N, 1);
}

static void
guppi_seq_categorical_class_init (GuppiSeqCategoricalClass *klass)
{
  GtkObjectClass *object_class = (GtkObjectClass *) klass;
  GuppiDataClass *data_class = GUPPI_DATA_CLASS (klass);
  GuppiSeqIntegerClass *seq_int_class = GUPPI_SEQ_INTEGER_CLASS (klass);
  GuppiSeqClass *seq_class = GUPPI_SEQ_CLASS (klass);

  parent_class = gtk_type_class (GUPPI_TYPE_SEQ_INTEGER);
  
  seq_int_class->range     = range;
  seq_int_class->frequency = frequency;
  seq_int_class->get       = get;
  seq_int_class->set       = set;
  seq_int_class->insert    = insert;

  seq_class->size_hint         = size_hint;
  seq_class->get_bounds        = get_bounds;
  seq_class->shift_indices     = shift_indices;
  seq_class->insert_generic    = insert_generic;
  seq_class->delete_many       = delete_many;
  seq_class->has_missing       = has_missing;
  seq_class->missing_count     = missing_count;
  seq_class->missing           = missing;
  seq_class->set_missing       = set_missing;
  seq_class->set_many_missing  = set_many_missing;
  seq_class->set_range_missing = set_range_missing;
  seq_class->set_all_missing   = set_all_missing;
  seq_class->insert_missing    = insert_missing;

  seq_class->support_missing_values = TRUE;
  data_class->is_leaf_type = TRUE;
  
  object_class->finalize = guppi_seq_categorical_finalize;
}

static void
guppi_seq_categorical_init (GuppiSeqCategorical *obj)
{
  GuppiSeqCategoricalPrivate *p;

  p = obj->priv = guppi_new0 (GuppiSeqCategoricalPrivate, 1);

  /* FIXME: Underlying implementation should be used-definable */
  p->seq = GUPPI_SEQ_INTEGER (guppi_data_new ("GuppiSeqIntegerCore"));

  p->category = NULL;

  p->auto_add = TRUE;

  p->freq_table = g_hash_table_new (NULL, NULL);

  gtk_signal_connect (GTK_OBJECT (obj),
		      "changed_set",
		      GTK_SIGNAL_FUNC (set_before_cb),
		      NULL);

  gtk_signal_connect_after (GTK_OBJECT (obj),
			    "changed_set",
			    GTK_SIGNAL_FUNC (set_after_cb),
			    NULL);

  gtk_signal_connect_after (GTK_OBJECT (obj),
			    "changed_insert",
			    GTK_SIGNAL_FUNC (insert_after_cb),
			    NULL);

  gtk_signal_connect (GTK_OBJECT (obj),
		      "changed_delete",
		      GTK_SIGNAL_FUNC (delete_before_cb),
		      NULL);
}

GtkType guppi_seq_categorical_get_type (void)
{
  static GtkType guppi_seq_categorical_type = 0;
  if (!guppi_seq_categorical_type) {
    static const GtkTypeInfo guppi_seq_categorical_info = {
      "GuppiSeqCategorical",
      sizeof (GuppiSeqCategorical),
      sizeof (GuppiSeqCategoricalClass),
      (GtkClassInitFunc) guppi_seq_categorical_class_init,
      (GtkObjectInitFunc) guppi_seq_categorical_init,
      NULL, NULL, (GtkClassInitFunc) NULL
    };
    guppi_seq_categorical_type = gtk_type_unique (GUPPI_TYPE_SEQ_INTEGER,
						  &guppi_seq_categorical_info);
  }
  return guppi_seq_categorical_type;
}

GuppiCategory *
guppi_seq_categorical_category (GuppiSeqCategorical *seq)
{
  g_return_val_if_fail (seq != NULL && GUPPI_IS_SEQ_CATEGORICAL (seq), NULL);

  return seq->priv->category;
}

void
guppi_seq_categorical_set_category (GuppiSeqCategorical *seq,
				    GuppiCategory *cat)
{
  GuppiSeqCategoricalPrivate *p;

  g_return_if_fail (seq != NULL && GUPPI_IS_SEQ_CATEGORICAL (seq));
  g_return_if_fail (cat != NULL && GUPPI_IS_CATEGORY (cat));

  p = seq->priv;

  guppi_refcounting_assign (p->category, cat);
  p->auto_add = FALSE;
}

gboolean guppi_seq_categorical_auto_add (GuppiSeqCategorical *seq)
{
  g_return_val_if_fail (seq != NULL && GUPPI_IS_SEQ_CATEGORICAL (seq), FALSE);

  return seq->priv->auto_add;
}

void
guppi_seq_categorical_set_auto_add (GuppiSeqCategorical *seq, gboolean x)
{
  g_return_if_fail (seq != NULL && GUPPI_IS_SEQ_CATEGORICAL (seq));

  seq->priv->auto_add = x;
}

const gchar *
guppi_seq_categorical_get (GuppiSeqCategorical *seq, gint i)
{
  code_t c;

  g_return_val_if_fail (seq != NULL && GUPPI_IS_SEQ_CATEGORICAL (seq), NULL);
  g_return_val_if_fail (guppi_seq_in_bounds (GUPPI_SEQ (seq), i), NULL);
  g_return_val_if_fail (seq->priv->category != NULL, NULL);

  c = (code_t) guppi_seq_integer_get (GUPPI_SEQ_INTEGER (seq), i);

  return guppi_category_find_by_code (seq->priv->category, c);
}

gboolean
guppi_seq_categorical_set (GuppiSeqCategorical *seq, gint i,
			   const gchar *str)
{
  code_t c;

  g_return_val_if_fail (seq != NULL && GUPPI_IS_SEQ_CATEGORICAL (seq), FALSE);
  g_return_val_if_fail (guppi_seq_in_bounds (GUPPI_SEQ (seq), i), FALSE);
  g_return_val_if_fail (guppi_data_can_change (GUPPI_DATA (seq)), FALSE);
  g_return_val_if_fail (seq->priv->category != NULL, FALSE);

  c = guppi_category_find_by_name (seq->priv->category, str);

  if (seq->priv->auto_add && c == GUPPI_INVALID_CODE)
    c = guppi_category_add_by_name (seq->priv->category, str);

  if (c != GUPPI_INVALID_CODE)
    guppi_seq_integer_set (GUPPI_SEQ_INTEGER (seq), i, c);

  return c != GUPPI_INVALID_CODE;
}

gboolean
guppi_seq_categorical_prepend (GuppiSeqCategorical *seq, const gchar *str)
{
  code_t c;

  g_return_val_if_fail (seq != NULL && GUPPI_IS_SEQ_CATEGORICAL (seq), FALSE);
  g_return_val_if_fail (guppi_data_can_change (GUPPI_DATA (seq)), FALSE);
  g_return_val_if_fail (seq->priv->category != NULL, FALSE);

  c = guppi_category_find_by_name (seq->priv->category, str);

  if (seq->priv->auto_add && c == GUPPI_INVALID_CODE)
    c = guppi_category_add_by_name (seq->priv->category, str);

  if (c != GUPPI_INVALID_CODE)
    guppi_seq_integer_prepend (GUPPI_SEQ_INTEGER (seq), c);

  return c != GUPPI_INVALID_CODE;
}

gboolean
guppi_seq_categorical_append (GuppiSeqCategorical *seq, const gchar *str)
{
  code_t c;

  g_return_val_if_fail (seq != NULL && GUPPI_IS_SEQ_CATEGORICAL (seq), FALSE);
  g_return_val_if_fail (guppi_data_can_change (GUPPI_DATA (seq)), FALSE);
  g_return_val_if_fail (seq->priv->category != NULL, FALSE);

  c = guppi_category_find_by_name (seq->priv->category, str);

  if (seq->priv->auto_add && c == GUPPI_INVALID_CODE)
    c = guppi_category_add_by_name (seq->priv->category, str);

  if (c != GUPPI_INVALID_CODE)
    guppi_seq_integer_append (GUPPI_SEQ_INTEGER (seq), c);

  return c != GUPPI_INVALID_CODE;
}

gboolean
guppi_seq_categorical_insert (GuppiSeqCategorical *seq, gint i,
			      const gchar *str)
{
  code_t c;

  g_return_val_if_fail (seq != NULL && GUPPI_IS_SEQ_CATEGORICAL (seq), FALSE);
  g_return_val_if_fail (guppi_data_can_change (GUPPI_DATA (seq)), FALSE);
  g_return_val_if_fail (seq->priv->category != NULL, FALSE);

  c = guppi_category_find_by_name (seq->priv->category, str);

  if (seq->priv->auto_add && c == GUPPI_INVALID_CODE)
    c = guppi_category_add_by_name (seq->priv->category, str);

  if (c != GUPPI_INVALID_CODE)
    guppi_seq_integer_insert (GUPPI_SEQ_INTEGER (seq), i, c);

  return c != GUPPI_INVALID_CODE;
}

gint
guppi_seq_categorical_frequency (GuppiSeqCategorical *seq, const gchar *str)
{
  code_t c;
  gpointer ptr;

  g_return_val_if_fail (seq != NULL && GUPPI_IS_SEQ_CATEGORICAL (seq), 0);
  g_return_val_if_fail (str != NULL, 0);
  g_return_val_if_fail (seq->priv->category != NULL, 0);

  c = guppi_category_find_by_name (seq->priv->category, str);

  ptr = g_hash_table_lookup (seq->priv->freq_table,
			     GUPPI_CATEGORY_CODE_TO_POINTER (c));

  return ptr ? *(gint *) ptr : 0;
}

double
guppi_seq_categorical_percentage (GuppiSeqCategorical *seq,
				  const gchar *str)
{
  gsize N;

  g_return_val_if_fail (seq != NULL && GUPPI_IS_SEQ_CATEGORICAL (seq), 0);
  g_return_val_if_fail (str != NULL, 0);
  g_return_val_if_fail (seq->priv->category != NULL, 0);

  N = guppi_seq_count (GUPPI_SEQ (seq));

  if (N == 0)
    return -1;

  return guppi_seq_categorical_frequency (seq, str) / (double) N;
}


syntax highlighted by Code2HTML, v. 0.9.1