/* This is -*- C -*- */
/* $Id: guppi-seq.c,v 1.28 2002/01/14 05:01:17 trow Exp $ */

/*
 * guppi-seq.c
 *
 * Copyright (C) 2000 EMC Capital Management, Inc.
 * Copyright (C) 2001 The Free Software Foundation
 *
 * Developed by Jon Trowbridge <trow@gnu.org> and
 * Havoc Pennington <hp@pobox.com>.
 *
 * 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.h"

#include <string.h>
#include <gtk/gtksignal.h>
#include <guppi-memory.h>
#include <guppi-debug.h>
#include <guppi-convenient.h>
#include "guppi-seq-boolean.h"
#include "../libguppidataimpl/guppi-seq-boolean-core.h" /* Ack! */

static GtkObjectClass *parent_class = NULL;

enum {
  CHANGED_SHIFT_INDICES,
  CHANGED_SET,
  CHANGED_INSERT,
  CHANGED_GROW,
  CHANGED_DELETE,
  LAST_SIGNAL
};

static guint seq_signals[LAST_SIGNAL] = { 0 };

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

/* Possible Operations */

typedef struct _GuppiDataOp_Seq GuppiDataOp_Seq;
struct _GuppiDataOp_Seq {
  GuppiDataOp op;
  gint i;
  gint i1;
  gsize N;
};

static void
op_shift (GuppiData *d, GuppiDataOp *op)
{
  GuppiSeq *seq = GUPPI_SEQ (d);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (d)->klass);
  GuppiDataOp_Seq *seq_op = (GuppiDataOp_Seq *) op;

  g_assert (klass->shift_indices);
  klass->shift_indices (seq, seq_op->i);
}

static void
op_grow_to_include (GuppiData *d, GuppiDataOp *op)
{
  GuppiSeq *seq = GUPPI_SEQ (d);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (d)->klass);
  GuppiDataOp_Seq *seq_op = (GuppiDataOp_Seq *) op;
  gint i0, i1, j0, j1;

  g_assert (klass->insert_generic);

  j0 = seq_op->i;
  j1 = seq_op->i1;

  guppi_seq_bounds (seq, &i0, &i1);

  if (guppi_seq_size (seq) == 0) {
    klass->insert_generic (seq, 0, j1 - j0 + 1);
    klass->shift_indices (seq, j0 - i0);
    return;
  }

  /* Grow to the front */
  if (j0 < i0) {
    klass->insert_generic (seq, i0, i0 - j0);
    klass->shift_indices (seq, j0 - i0);
  }

  /* Grow to the back */
  if (i1 < j1) {
    klass->insert_generic (seq, i1+1, j1 - i1);
  }
}

static void
op_set_missing (GuppiData *d, GuppiDataOp * op)
{
  GuppiSeq *seq = GUPPI_SEQ (d);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (d)->klass);
  GuppiDataOp_Seq *seq_op = (GuppiDataOp_Seq *) op;

  g_assert (klass->set_missing);
  klass->set_missing (seq, seq_op->i, TRUE);
}

static void
op_insert_missing (GuppiData *d, GuppiDataOp *op)
{
  GuppiSeq *seq = GUPPI_SEQ (d);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (d)->klass);
  GuppiDataOp_Seq *seq_op = (GuppiDataOp_Seq *) op;

  g_assert (klass->insert_missing && klass->insert_generic);
  klass->insert_generic (seq, seq_op->i, 1);
  klass->set_missing (seq, seq_op->i, TRUE);
}

static void
op_delete (GuppiData *d, GuppiDataOp *op)
{
  GuppiSeq *seq = GUPPI_SEQ (d);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (d)->klass);
  GuppiDataOp_Seq *seq_op = (GuppiDataOp_Seq *) op;

  g_assert (klass->delete_many);
  klass->delete_many (seq, seq_op->i, seq_op->N);
}

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

/**
 * guppi_seq_size_hint
 * @seq: The sequence
 * @expected_size: The expected maximum size of the sequence
 *
 * Inform the implementation of the expected maximum size of the sequence.
 * In some cases, this information can be used to allocate memory in a
 * more efficient fashion.
 */
void
guppi_seq_size_hint (GuppiSeq *seq, gsize expected_size)
{
  GuppiSeqClass *klass;

  g_return_if_fail (GUPPI_IS_SEQ (seq));

  /* Read-only data silently ignores size hints.  It likes the size it
     is just fine, thank you. */
  if (guppi_data_is_read_only (GUPPI_DATA (seq)))
    return;

  klass = GUPPI_SEQ_CLASS (GTK_OBJECT (seq)->klass);
  if (klass->size_hint)
    klass->size_hint (seq, expected_size);
}

/**
 * guppi_seq_indices
 * @seq: The sequence
 * @min: A pointer to a variable to contain the lowest index value
 * @max: A pointer to a variable to contain the highest index bound
 * 
 * Get the minimum and maximum legal values of the sequence's
 * position index.  Either @min or @max may be %NULL.
 *
 * guppi_seq_indices() is a synonym for guppi_seq_bounds().
 */

/**
 * guppi_seq_bounds
 * @seq: The sequence
 * @min: A pointer to a variable to contain the lowest index value
 * @max: A pointer to a variable to contain the highest index bound
 * 
 * Get the minimum and maximum legal values of the sequence's
 * position index.  Either @min or @max may be %NULL.
 *
 * guppi_seq_bounds() is a synonym for guppi_seq_indices().
 */
void
guppi_seq_indices (const GuppiSeq *seq, gint *min, gint *max)
{
  GuppiSeqClass *klass;

  g_return_if_fail (GUPPI_IS_SEQ (seq));

  klass = GUPPI_SEQ_CLASS (GTK_OBJECT (seq)->klass);

  g_assert (klass->get_bounds);
  klass->get_bounds ((GuppiSeq *) seq, min, max);
}

/**
 * guppi_seq_min_index
 * @seq: The sequence
 *
 * Returns: The sequence's lowest legal index value.
 */
gint
guppi_seq_min_index (const GuppiSeq *seq)
{
  gint min = 0;
  g_return_val_if_fail (GUPPI_IS_SEQ (seq), 0);
  guppi_seq_indices (seq, &min, NULL);
  return min;
}

/**
 * guppi_seq_max_index
 * @seq: The sequence
 *
 * Returns: The sequence's highest legal index value.
 */
gint
guppi_seq_max_index (const GuppiSeq *seq)
{
  gint max = -1;
  g_return_val_if_fail (GUPPI_IS_SEQ (seq), -1);
  guppi_seq_indices (seq, NULL, &max);
  return max;
}

/**
 * guppi_seq_size
 * @seq: The sequence
 *
 * Computes the length of the sequence.
 *
 * Returns: The number of elements in the sequence.
 */
gsize
guppi_seq_size (const GuppiSeq *seq)
{
  gint min, max;
  g_return_val_if_fail (GUPPI_IS_SEQ (seq), 0);
  guppi_seq_indices (seq, &min, &max);
  g_assert (max + 1 >= min);
  return max + 1 - min;
}

/**
 * guppi_seq_count
 * @seq: The sequence
 *
 * Counts how many elements of @seq are known values.
 *
 * Returns: The number of known/non-missing elements in the sequence.
 */
gsize
guppi_seq_count (const GuppiSeq *seq)
{
  gsize size, missing;
  g_return_val_if_fail (GUPPI_IS_SEQ (seq), 0);

  size = guppi_seq_size (seq);
  missing = guppi_seq_missing_count ((GuppiSeq *) seq);
  g_return_val_if_fail (size >= missing, 0);

  return size - missing;
}

/**
 * guppi_seq_empty
 * @seq: The sequence
 *
 * Checks if @seq is of zero length.
 *
 * Returns: %TRUE if @seq contains any elements, %FALSE otherwise.
 */
gboolean
guppi_seq_empty (const GuppiSeq *seq)
{
  g_return_val_if_fail (GUPPI_IS_SEQ (seq), TRUE);
  return guppi_seq_size (seq) == 0;
}

/**
 * guppi_seq_nonempty
 * @seq: The sequence
 *
 * Checks if @seq contains any elements.
 *
 * Returns: %TRUE if @seq contains no elements whatsoever, %FALSE otherwise.
 */
gboolean
guppi_seq_nonempty (const GuppiSeq *seq)
{
  g_return_val_if_fail (GUPPI_IS_SEQ (seq), FALSE);
  return guppi_seq_size (seq) > 0;
}

/**
 * guppi_seq_absent
 * @seq: The sequence
 *
 * Checks if @seq consists only of missing values.
 *
 * Returns: %TRUE is @seq is either empty or contains only missing values,
 * %FALSE otherwise.
 */
gboolean 
guppi_seq_absent (const GuppiSeq *seq)
{
  g_return_val_if_fail (GUPPI_IS_SEQ (seq), TRUE);
  return guppi_seq_count (seq) == 0;
}

/**
 * guppi_seq_present
 * @seq: The sequence
 *
 * Checks if @seq contains any known values.
 *
 * Returns: %TRUE is @seq contains at least one non-missing value,
 * %FALSE otherwise.
 */
gboolean 
guppi_seq_present (const GuppiSeq *seq)
{
  g_return_val_if_fail (GUPPI_IS_SEQ (seq), FALSE);
  return guppi_seq_count (seq) > 0;
}

/**
 * guppi_seq_in_bounds
 * @seq: The sequence
 * @i: An index value
 *
 * Checks if @i lies within the sequence's bounds.
 *
 * Returns: %TRUE if @i is a legal index value for @seq.
 */
gboolean 
guppi_seq_in_bounds (const GuppiSeq *seq, gint i)
{
  gint min = 0, max = -1;
  g_return_val_if_fail (GUPPI_IS_SEQ (seq), FALSE);
  guppi_seq_indices (seq, &min, &max);
  return min <= i && i <= max;
}

/**
 * guppi_seq_contains_bounds
 * @seq: A sequence
 * @seq2: Another sequence
 *
 * Check if a sequence's index bounds completely contain those
 * of another sequence.
 *
 * Returns: %TRUE if the bounds of @seq2 are contained entirely within
 * those of @seq.
 */
gboolean 
guppi_seq_contains_bounds (const GuppiSeq *seq, const GuppiSeq *seq2)
{
  gint amin = 0, amax = -1, bmin = 0, bmax = -1;
  g_return_val_if_fail (GUPPI_IS_SEQ (seq), FALSE);
  g_return_val_if_fail (GUPPI_IS_SEQ (seq2), FALSE);
  guppi_seq_indices (seq, &amin, &amax);
  guppi_seq_indices (seq2, &bmin, &bmax);

  return amin <= bmin && bmax <= amax;
}

/**
 * guppi_seq_equal_bounds
 * @seq: A sequence
 * @seq2: Another sequence
 *
 * Check if two sequences have the same upper and lower index bounds.
 *
 * Returns: %TRUE if the bounds of @seq and @seq2 are identical.
 */
gboolean
guppi_seq_equal_bounds (const GuppiSeq *seq, const GuppiSeq *seq2)
{
  gint amin = 0, amax = -1, bmin = 0, bmax = -1;
  g_return_val_if_fail (seq != NULL, FALSE);
  g_return_val_if_fail (seq2 != NULL, FALSE);
  guppi_seq_indices (seq, &amin, &amax);
  guppi_seq_indices (seq2, &bmin, &bmax);

  return amin == bmin && bmax == amax;
}

/**
 * guppi_seq_common_bounds
 * @seq: A sequences
 * @seq2: Another sequences
 * @min: A pointer to a variable to contain the lower index value
 * @max: A pointer to a variable to contain the upper index value
 *
 * Compute the largest range of index values that are valid
 * for both @seq and @seq2.
 */
void
guppi_seq_common_bounds (const GuppiSeq *seq, const GuppiSeq *seq2,
			 gint *min, gint *max)
{
  gint amin = 0, amax = -1, bmin = 0, bmax = -1;
  g_return_if_fail (seq != NULL);
  g_return_if_fail (seq != NULL);
  guppi_seq_indices (seq, &amin, &amax);
  guppi_seq_indices (seq, &bmin, &bmax);

  if (min)
    *min = MAX (amin, bmin);
  if (max)
    *max = MIN (amax, bmax);
}

/**
 * guppi_seq_shift_indices
 * @seq: A sequences
 * @delta: The amount of shift the indices by.
 *
 * Shift the sequence's indices by @delta.
 */
void
guppi_seq_shift_indices (GuppiSeq *seq, gint delta)
{
  GuppiDataOp_Seq op;

  g_return_if_fail (GUPPI_IS_SEQ (seq));
  g_return_if_fail (guppi_data_can_change (GUPPI_DATA (seq)));

  if (delta == 0)
    return;

  /* I confess... I love typing op.op.op! */

  op.op.op = op_shift;
  op.i = delta;

  guppi_seq_changed_shift_indices (seq, delta, (GuppiDataOp *) &op);
}

/**
 * guppi_seq_set_min_index
 * @seq: A sequence
 * @min: The new lower index value
 *
 * Adjust the sequence's index values such that
 * the lower bound becomes @min.
 */
void
guppi_seq_set_min_index (GuppiSeq *seq, gint min)
{
  gint old_min;
  g_return_if_fail (GUPPI_IS_SEQ (seq));
  old_min = guppi_seq_min_index (seq);
  guppi_seq_shift_indices (seq, min - old_min);
}

/**
 * guppi_seq_set_max_index
 * @seq: A sequence
 * @max: The new upper index value
 *
 * Adjust the sequence's index values such that
 * the lower bound becomes @max.
 */
void
guppi_seq_set_max_index (GuppiSeq *seq, gint max)
{
  gint old_max;
  g_return_if_fail (GUPPI_IS_SEQ (seq));
  old_max = guppi_seq_max_index (seq);
  guppi_seq_shift_indices (seq, max - old_max);
}


/**
 * guppi_seq_delete
 * @seq: A sequence
 * @i: A position index
 *
 * Remove the @i-th element of sequence @seq.  The position index
 * of all elements above the @i-th position are decremented.
 * @seq must be writeable and @i must be in-bounds.
 */
void
guppi_seq_delete (GuppiSeq *seq, gint i)
{
  GuppiDataOp_Seq op;

  g_return_if_fail (GUPPI_IS_SEQ (seq));
  g_return_if_fail (guppi_data_can_change (GUPPI_DATA (seq)));
  g_return_if_fail (guppi_seq_in_bounds (seq, i));

  op.op.op = op_delete;
  op.i = i;
  op.N = 1;

  guppi_seq_changed_delete (seq, i, 1, (GuppiDataOp *) & op);
}

/**
 * guppi_seq_delete_many
 * @seq: A sequence
 * @i: A position index
 * @N: A count
 *
 * Remove @N elements from sequence @seq, starting at the @i-th.  The
 * position index of all elements following the removed elements are
 * decreased accordingly.
 * @seq must be writeable and @i must be in-bounds.
 */
void
guppi_seq_delete_many (GuppiSeq *seq, gint i, gsize N)
{
  GuppiDataOp_Seq op;
  gint i1;

  g_return_if_fail (GUPPI_IS_SEQ (seq));
  g_return_if_fail (guppi_data_can_change (GUPPI_DATA (seq)));
  g_return_if_fail (guppi_seq_in_bounds (seq, i));

  if (N == 0)
    return;

  i1 = guppi_seq_max_index (seq);
  if (i + N - 1 > i1)
    N = i1 - i + 1;

  op.op.op = op_delete;
  op.i = i;
  op.N = N;

  guppi_seq_changed_delete (seq, i, N, (GuppiDataOp *) & op);
}

/**
 * guppi_seq_delete_range
 * @seq: A sequence
 * @i0: The lower index bound
 * @i1: The upper index bound
 *
 * Remove the elements at positions @i0 through @i1 from sequence
 * @seq.  The position index of all elements following the removed
 * elements are decreased accordingly.  @seq must be writeable and both
 * @i0 and @i1 must be in-bounds.
 */
void
guppi_seq_delete_range (GuppiSeq *seq, gint i0, gint i1)
{
  g_return_if_fail (GUPPI_IS_SEQ (seq));
  g_return_if_fail (guppi_data_can_change (GUPPI_DATA (seq)));
  g_return_if_fail (guppi_seq_in_bounds (seq, i0));
  g_return_if_fail (guppi_seq_in_bounds (seq, i1));

  guppi_2sort_i (&i0, &i1);

  guppi_seq_delete_many (seq, i0, i1 - i0 + 1);
}

/**
 * guppi_seq_grow_to_include
 * @seq: A sequence
 * @i: A position index
 *
 * Expand the bounds of the sequence @seq sufficiently to
 * contain @i.  If @i is already in-bounds, this operation
 * does nothing.  @seq must be writeable.
 *
 * The default values assigned to any elements added to @seq
 * are type-dependent.
 */
void
guppi_seq_grow_to_include (GuppiSeq *seq, gint i)
{
  g_return_if_fail (GUPPI_IS_SEQ (seq));
  guppi_seq_grow_to_include_range (seq, i, i);
}

/**
 * guppi_seq_grow_to_include_range
 * @seq: A sequence
 * @i0: The lower index bound
 * @i1: The upper index bound
 *
 * Expand the bounds of the sequence @seq sufficiently to contain the
 * range @i0 to @i1.  If that range is already in-bounds, this
 * operation does nothing.  @seq must be writeable.
 *
 * The default values assigned to any elements added to @seq
 * are type-dependent.
 */
void
guppi_seq_grow_to_include_range (GuppiSeq *seq, gint i0, gint i1)
{
  GuppiDataOp_Seq op;

  g_return_if_fail (GUPPI_IS_SEQ (seq));
  g_return_if_fail (guppi_data_can_change (GUPPI_DATA (seq)));

  if (guppi_seq_in_bounds (seq, i0) && guppi_seq_in_bounds (seq, i1))
    return;

  guppi_2sort_i (&i0, &i1);
  
  op.op.op = op_grow_to_include;
  op.i = i0;
  op.i1 = i1;

  guppi_seq_changed_grow (seq, i0, i1, (GuppiDataOp *) &op);
}

/**
 * guppi_seq_grow_to_overlap
 * @seq: A sequence
 * @seq_to_overlap: The sequence that @seq is to overlap
 *
 * Expand the bounds of the sequence @seq sufficiently to contain the
 * full range of indices of @seq_to_overlap.  If that range is already
 * in-bounds, this operation does nothing.  @seq must be writeable.
 *
 * The default values assigned to any elements added to @seq
 * are type-dependent.
 */

void
guppi_seq_grow_to_overlap (GuppiSeq *seq, const GuppiSeq *seq_to_overlap)
{
  gint i0, i1;

  g_return_if_fail (GUPPI_IS_SEQ (seq));
  g_return_if_fail (GUPPI_IS_SEQ (seq_to_overlap));

  if (guppi_seq_size (seq_to_overlap) > 0) {
    guppi_seq_indices (seq_to_overlap, &i0, &i1);
    guppi_seq_grow_to_include_range (seq, i0, i1);
  }
}

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

/**
 * guppi_seq_has_missing
 * @seq: The sequence
 *
 * Checks whether or not the sequence contains any missing values.
 *
 * Returns: %TRUE if @seq contains missing values.
 */
gboolean
guppi_seq_has_missing (GuppiSeq *seq)
{
  GuppiSeqClass *klass;

  g_return_val_if_fail (GUPPI_IS_SEQ (seq), FALSE);
  klass = GUPPI_SEQ_CLASS (GTK_OBJECT (seq)->klass);

  if (! klass->support_missing_values)
    return FALSE;

  g_assert (klass->has_missing);
  return klass->has_missing (seq);
}

/**
 * guppi_seq_missing_count
 * @seq: The sequence
 *
 * Counts the number of missing values in the sequence.
 *
 * Returns: The number of missing values in @seq.
 */
gsize
guppi_seq_missing_count (GuppiSeq *seq)
{
  GuppiSeqClass *klass;

  g_return_val_if_fail (GUPPI_IS_SEQ (seq), 0);
  klass = GUPPI_SEQ_CLASS (GTK_OBJECT (seq)->klass);

  if (! klass->support_missing_values)
    return 0;

  g_assert (klass->missing_count);
  return klass->missing_count (seq);
}

/**
 * guppi_seq_missing
 * @seq: The sequence
 * @i: An index value
 * 
 * Check to see if a particular element of a sequence is a missing value.
 *
 * Returns: %TRUE is @i is a missing value.
 */
gboolean
guppi_seq_missing (GuppiSeq *seq, gint i)
{
  GuppiSeqClass *klass;

  g_return_val_if_fail (GUPPI_IS_SEQ (seq), FALSE);
  g_return_val_if_fail (guppi_seq_in_bounds (seq, i), FALSE);

  klass = GUPPI_SEQ_CLASS (GTK_OBJECT (seq)->klass);

  if (! klass->support_missing_values)
    return FALSE;

  g_assert (klass->missing);
  return klass->missing (seq, i);
}

/**
 * guppi_seq_available
 * @seq: The sequence
 * @i: An index value
 * 
 * Check to see if a particular element of a sequence is a known value.
 *
 * Returns: %TRUE is @i is not a missing value.
 */
gboolean
guppi_seq_available (GuppiSeq *seq, gint i)
{
  return !guppi_seq_missing (seq, i);
}

/**
 * guppi_seq_set_missing
 * @seq: The sequence
 * @i: An index value
 *
 * Marks an element of the sequence as a missing value.
 * @i must be a valid index for @seq.
 * @seq must be writeable.
 */
void
guppi_seq_set_missing (GuppiSeq *seq, gint i)
{
  gboolean x;

  g_return_if_fail (GUPPI_IS_SEQ (seq));
  g_return_if_fail (guppi_data_can_change (GUPPI_DATA (seq)));
  g_return_if_fail (guppi_seq_in_bounds (seq, i));

  x = guppi_seq_missing (seq, i);
  if (!x) {

    GuppiDataOp_Seq op;
    op.op.op = op_set_missing;
    op.i = i;

    guppi_seq_changed_set (seq, i, i, (GuppiDataOp *) & op);
  }
}

/**
 * guppi_seq_insert_missing
 * @seq: The sequence
 * @i: An index value
 *
 * Inserts a missing value into a sequence at a particular position.
 * @i must be a valid index for @seq.
 * @seq must be writeable.
 */
void
guppi_seq_insert_missing (GuppiSeq *seq, gint i)
{
  GuppiDataOp_Seq op;

  g_return_if_fail (GUPPI_IS_SEQ (seq));
  g_return_if_fail (guppi_data_can_change (GUPPI_DATA (seq)));

  op.op.op = op_insert_missing;
  op.i = i;

  guppi_seq_changed_insert (seq, i, 1, (GuppiDataOp *) & op);
}

/**
 * guppi_seq_prepend_missing
 * @seq: The sequence
 *
 * Prepend a missing value to the sequence.
 * @seq must be writeable.
 */
void
guppi_seq_prepend_missing (GuppiSeq *seq)
{
  gint first;

  g_return_if_fail (GUPPI_IS_SEQ (seq));
  g_return_if_fail (guppi_data_can_change (GUPPI_DATA (seq)));

  first = guppi_seq_min_index (seq);
  guppi_seq_insert_missing (seq, first);
}

/**
 * guppi_seq_append_missing
 * @seq: The sequence
 *
 * Append a missing value to the sequence.
 * @seq must be writeable.
 */
void
guppi_seq_append_missing (GuppiSeq *seq)
{
  gint last;

  g_return_if_fail (GUPPI_IS_SEQ (seq));
  g_return_if_fail (guppi_data_can_change (GUPPI_DATA (seq)));

  last = guppi_seq_max_index (seq);
  guppi_seq_insert_missing (seq, last + 1);
}

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

void
guppi_seq_changed_shift_indices (GuppiSeq *seq, gint i, GuppiDataOp * op)
{
  g_return_if_fail (GUPPI_IS_SEQ (seq));
  g_return_if_fail (op != NULL);

  guppi_data_add_pending_op (GUPPI_DATA (seq), op);
  gtk_signal_emit (GTK_OBJECT (seq), seq_signals[CHANGED_SHIFT_INDICES], i);
}

void
guppi_seq_changed_set (GuppiSeq *seq, gint i0, gint i1, GuppiDataOp * op)
{
  g_return_if_fail (GUPPI_IS_SEQ (seq));
  g_return_if_fail (op != NULL);

  guppi_data_add_pending_op (GUPPI_DATA (seq), op);
  gtk_signal_emit (GTK_OBJECT (seq), seq_signals[CHANGED_SET], i0, i1);
}

void
guppi_seq_changed_insert (GuppiSeq *seq, gint i, gsize N, GuppiDataOp * op)
{
  g_return_if_fail (GUPPI_IS_SEQ (seq));
  g_return_if_fail (op != NULL);

  guppi_data_add_pending_op (GUPPI_DATA (seq), op);
  gtk_signal_emit (GTK_OBJECT (seq), seq_signals[CHANGED_INSERT], i, N);
}

void
guppi_seq_changed_grow (GuppiSeq *seq, gint i0, gint i1, GuppiDataOp *op)
{
  g_return_if_fail (GUPPI_IS_SEQ (seq));
  g_return_if_fail (op != NULL);

  guppi_data_add_pending_op (GUPPI_DATA (seq), op);
  gtk_signal_emit (GTK_OBJECT (seq), seq_signals[CHANGED_GROW], i0, i1);
}

void
guppi_seq_changed_delete (GuppiSeq *seq, gint i, gsize N, GuppiDataOp * op)
{
  g_return_if_fail (GUPPI_IS_SEQ (seq));
  g_return_if_fail (op != NULL);

  guppi_data_add_pending_op (GUPPI_DATA (seq), op);
  gtk_signal_emit (GTK_OBJECT (seq), seq_signals[CHANGED_DELETE], i, N);
}

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

/*
 * Base implementation of missing values.
 * This could be optimized substantially.
 */

static GuppiSeqBoolean *
get_missing (GuppiSeq *seq)
{
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (seq)->klass);

  if (! klass->support_missing_values)
    return NULL;

  /* Magic hack to disable missing data */
  if (seq->missing_data == get_missing)
    return NULL;

  if (seq->missing_data == NULL) {
    seq->missing_data = guppi_seq_boolean_core_new ();
    GUPPI_SEQ (seq->missing_data)->missing_data = get_missing; /* avoid infinite recursion */
    g_assert (seq->missing_data);
  }

  return seq->missing_data;
}

static void
missing_sanity_check (GuppiSeq *seq)
{
  GuppiSeqBoolean *miss = get_missing (seq);
  gint seq_i0, seq_i1, miss_i0, miss_i1;

  if (miss != NULL) {
    guppi_seq_bounds (seq, &seq_i0, &seq_i1);
    guppi_seq_bounds (GUPPI_SEQ (miss), &miss_i0, &miss_i1);

    if (seq_i0 != miss_i0 || seq_i1 != miss_i1) {
      g_warning ("seq: [%d, %d]  miss: [%d, %d]", seq_i0, seq_i1, miss_i0, miss_i1);
      g_assert (0);
    }
  }
}

static void
shift_indices (GuppiSeq *seq, gint delta)
{
  GuppiSeqBoolean *miss = get_missing (seq);

  if (miss) {
    guppi_seq_shift_indices (GUPPI_SEQ (miss), delta);
  }
}

static void
delete_many (GuppiSeq *seq, gint i, gsize N)
{
  GuppiSeqBoolean *miss = get_missing (seq);

  if (miss) {
    guppi_seq_delete_many (GUPPI_SEQ (miss), i, N);
  }
}

static gboolean
has_missing (GuppiSeq *seq)
{
  GuppiSeqBoolean *miss = get_missing (seq);

  return miss && guppi_seq_boolean_first_true (miss) <= guppi_seq_max_index (GUPPI_SEQ (miss));
}

static gsize
missing_count (GuppiSeq *seq)
{
  GuppiSeqBoolean *miss = get_missing (seq);

  return miss ? guppi_seq_boolean_true_count (miss) : 0;
}

static gboolean
missing (GuppiSeq *seq, gint i)
{
  GuppiSeqBoolean *miss = get_missing (seq);

  return miss ? guppi_seq_boolean_get (miss, i) : FALSE;
}

static void
set_missing (GuppiSeq *seq, gint i, gboolean x)
{
  GuppiSeqBoolean *miss = get_missing (seq);

  if (miss) {
    guppi_seq_boolean_set (miss, i, x);
  }

  missing_sanity_check (seq);
}

static void
set_range_missing (GuppiSeq *seq, gint i0, gint i1, gboolean x)
{
  GuppiSeqBoolean *miss = get_missing (seq);

  if (miss) {
    gint i;
    for (i = i0; i <= i1; ++i) {
      guppi_seq_boolean_set (miss, i, x);
    }
  }

  missing_sanity_check (seq);
}

static void
set_many_missing (GuppiSeq *seq, gint *ind, gsize N, gboolean x)
{
  GuppiSeqBoolean *miss = get_missing (seq);

  if (miss) {
    guppi_seq_boolean_set_many (miss, ind, N, x);
  }

  missing_sanity_check (seq);
}

static void
set_all_missing (GuppiSeq *seq, gboolean x)
{
  GuppiSeqBoolean *miss = get_missing (seq);

  if (miss) {
    guppi_seq_boolean_set_all (miss, x);
  }
}

static void
insert_missing (GuppiSeq *seq, gint i, gboolean x, gsize N)
{
  GuppiSeqBoolean *miss = get_missing (seq);

  if (miss) {
    guppi_seq_boolean_insert_many (miss, i, x, N);
  }

  missing_sanity_check (seq);
}

static void
copy_missing (GuppiSeq *dest, GuppiSeq *src)
{
  GuppiSeq *src_miss;
  
  g_return_if_fail (guppi_seq_equal_bounds (dest, src));

  guppi_unref0 (dest->missing_data);
  src_miss = (GuppiSeq *) get_missing (src);
  if (src_miss) {
    dest->missing_data = guppi_data_copy (GUPPI_DATA (src_miss));
  }
}

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

static void
export_xml (GuppiData *data, GuppiXMLDocument *doc, xmlNodePtr node)
{
  GuppiSeq *seq = GUPPI_SEQ (data);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (data)->klass);
  gint i, i0, i1;
  xmlNodePtr seq_node;
  gboolean has_missing;

  g_return_if_fail (klass->export_xml_element != NULL);

  guppi_seq_bounds (seq, &i0, &i1);
  has_missing = guppi_seq_has_missing (seq);

  seq_node = guppi_xml_new_node (doc, "Sequence");
  if (i0 <= i1) {
    guppi_xml_set_property_int (seq_node, "min_index", i0);
    guppi_xml_set_property_int (seq_node, "max_index", i1);
  }
  guppi_xml_set_property_bool (seq_node, "has_missing", has_missing);

  xmlAddChild (node, seq_node);

  for (i = i0; i <= i1; ++i) {
    xmlNodePtr el_node;
    
    if (has_missing && guppi_seq_missing (seq, i)) {
      el_node = guppi_xml_new_node (doc, "missing_value");
    } else {
      el_node = klass->export_xml_element (seq, i, doc);
    }
    xmlAddChild (seq_node, el_node);
  }

  if (GUPPI_DATA_CLASS (parent_class)->export_xml)
    GUPPI_DATA_CLASS (parent_class)->export_xml (data, doc, node);
}

static gboolean
import_xml (GuppiData *data, GuppiXMLDocument *doc, xmlNodePtr node)
{
  GuppiSeq *seq = GUPPI_SEQ (data);
  GuppiSeqClass *klass = GUPPI_SEQ_CLASS (GTK_OBJECT (data)->klass);

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

    gint i, i0, i1;
    gboolean has_missing;
  
    i0 = guppi_xml_get_property_int (node, "min_index", 0);
    i1 = guppi_xml_get_property_int (node, "max_index", -1);
    has_missing = guppi_xml_get_property_bool (node, "has_missing", TRUE);

    if (i0 > i1)
      return TRUE;

    node = node->xmlChildrenNode;

    i = i0;
    while (node != NULL && i <= i1) {
      if (has_missing && ! strcmp (node->name, "missing_value")) {
	guppi_seq_append_missing (seq);
      } else if (! klass->import_xml_element (seq, doc, node))
	return FALSE;
      node = node->next;
      ++i;
    }
    
    return TRUE;
  }

  /* Pass along any non-Sequence nodes. */

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

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

static gchar *
get_size_info (GuppiData *d)
{
  gint i0, i1;
  guppi_seq_bounds (GUPPI_SEQ (d), &i0, &i1);
  if (i0 > i1)
    return g_strdup ("empty");
  else
    return g_strdup_printf ("%d to %d", i0, i1);
}

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

static void
guppi_seq_finalize (GtkObject * obj)
{
  GuppiSeq *seq = GUPPI_SEQ (obj);

  if (seq->missing_data != get_missing)
    guppi_unref (seq->missing_data);

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

static void
changed_shift_indices (GuppiSeq *seq, gint i)
{
  guppi_data_changed (GUPPI_DATA (seq));
}

static void
changed_set (GuppiSeq *seq, gint i0, gint i1)
{
  guppi_data_changed (GUPPI_DATA (seq));
}

static void
changed_insert (GuppiSeq *seq, gint i, gsize N)
{
  guppi_data_changed (GUPPI_DATA (seq));
}

static void
changed_grow (GuppiSeq *seq, gint i0, gint i1)
{
  guppi_data_changed (GUPPI_DATA (seq));
}

static void
changed_delete (GuppiSeq *seq, gint i, gsize N)
{
  guppi_data_changed (GUPPI_DATA (seq));
}

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

static gboolean
validate_class (GuppiDataClass *klass)
{
  GuppiSeqClass *seq_klass = GUPPI_SEQ_CLASS (klass);
  gboolean ok = TRUE;

  if (seq_klass->get_bounds == NULL) {
    g_warning ("Method GuppiSeq::get_bounds not defined.");
    ok = FALSE;
  }

  if (seq_klass->insert_generic == NULL) {
    g_warning ("Method GuppiSeq::insert_generic not defined.");
    ok = FALSE;
  }

  if (seq_klass->shift_indices == shift_indices) {
    g_warning ("Method GuppiSeq::shift_indices not defined.");
    ok = FALSE;
  }

  if (seq_klass->export_xml_element == NULL) {
    g_warning ("Method GuppiSeq::export_xml_element not defined.");
    ok = FALSE;
  }

  if (seq_klass->import_xml_element == NULL) {
    g_warning ("Method GuppiSeq::export_xml_element not defined.");
    ok = FALSE;
  }

  if (GUPPI_DATA_CLASS (parent_class)->validate_class
      && ! GUPPI_DATA_CLASS (parent_class)->validate_class (klass))
    ok = FALSE;

  return ok;
}

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

static void
guppi_seq_class_init (GuppiSeqClass *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_seq_finalize;

  data_class->validate_class = validate_class;
  data_class->import_xml     = import_xml;
  data_class->export_xml     = export_xml;
  data_class->get_size_info  = get_size_info;
  
  klass->shift_indices = shift_indices;
  klass->delete_many   = delete_many;

  klass->has_missing       = has_missing;
  klass->missing_count     = missing_count;
  klass->missing           = missing;
  klass->set_missing       = set_missing;
  klass->set_range_missing = set_range_missing;
  klass->set_many_missing  = set_many_missing;
  klass->set_all_missing   = set_all_missing;
  klass->insert_missing    = insert_missing;
  klass->copy_missing      = copy_missing;

  klass->changed_shift_indices = changed_shift_indices;
  klass->changed_set           = changed_set;
  klass->changed_insert        = changed_insert;
  klass->changed_grow          = changed_grow;
  klass->changed_delete        = changed_delete;

  seq_signals[CHANGED_SHIFT_INDICES] =
    gtk_signal_new ("changed_shift_indices",
		    GTK_RUN_LAST,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GuppiSeqClass, changed_shift_indices),
		    gtk_marshal_NONE__INT, GTK_TYPE_NONE, 1, GTK_TYPE_INT);

  seq_signals[CHANGED_SET] =
    gtk_signal_new ("changed_set",
		    GTK_RUN_LAST,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GuppiSeqClass, changed_set),
		    gtk_marshal_NONE__INT_INT,
		    GTK_TYPE_NONE, 2, GTK_TYPE_INT, GTK_TYPE_INT);

  /* I've used the NONE__INT_INT marshaller here, even though the
     correct thing would be do use NONE__INT_UINT.  I'm just being
     lazy and using the predefined marshallers, though.  This should
     work fine --- Famous last words :-) */

  seq_signals[CHANGED_INSERT] =
    gtk_signal_new ("changed_insert",
		    GTK_RUN_LAST,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GuppiSeqClass, changed_insert),
		    gtk_marshal_NONE__INT_INT,
		    GTK_TYPE_NONE, 2, GTK_TYPE_INT, GTK_TYPE_UINT);

  seq_signals[CHANGED_GROW] =
    gtk_signal_new ("changed_grow",
		    GTK_RUN_LAST,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GuppiSeqClass, changed_grow),
		    gtk_marshal_NONE__INT_INT,
		    GTK_TYPE_NONE, 2, GTK_TYPE_INT, GTK_TYPE_UINT);

  seq_signals[CHANGED_DELETE] =
    gtk_signal_new ("changed_delete",
		    GTK_RUN_LAST,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GuppiSeqClass, changed_delete),
		    gtk_marshal_NONE__INT_INT,
		    GTK_TYPE_NONE, 2, GTK_TYPE_INT, GTK_TYPE_UINT);

  gtk_object_class_add_signals (object_class, seq_signals, LAST_SIGNAL);

}

static void
guppi_seq_init (GuppiSeq *obj)
{

}

GtkType guppi_seq_get_type (void)
{
  static GtkType guppi_seq_type = 0;
  if (!guppi_seq_type) {
    static const GtkTypeInfo guppi_seq_info = {
      "GuppiSeq",
      sizeof (GuppiSeq),
      sizeof (GuppiSeqClass),
      (GtkClassInitFunc) guppi_seq_class_init,
      (GtkObjectInitFunc) guppi_seq_init,
      NULL, NULL, (GtkClassInitFunc) NULL
    };
    guppi_seq_type = gtk_type_unique (GUPPI_TYPE_DATA, &guppi_seq_info);
  }
  return guppi_seq_type;
}

/* $Id: guppi-seq.c,v 1.28 2002/01/14 05:01:17 trow Exp $ */


syntax highlighted by Code2HTML, v. 0.9.1