/*
   File:        column.c
   Author:      Andrew W. Moore
   Created:     Sat Aug 14 12:50:29 EDT 1999
   Description: SPR/Auton Column of a datset structure

   Copyright 1999, Schenley Park Research
*/

#include "column.h"

/* Private prototypes */
static int column_arity(column *col);
static pivec *column_symbol_values(column *col);
static pvector *column_real_values(column *col);
static void maybe_free_pivec(pivec **x_ptr);
static void maybe_free_pvector(pvector **x_ptr);





void free_column(column *x)
{
  if ( x == NULL) return;
  if ( x->symbol_values != NULL ) free_pivec(x->symbol_values);
  if ( x->real_values != NULL ) free_pvector(x->real_values);
  if ( x->missing != NULL ) free_pivec(x->missing);
  AM_FREE(x,column);
}

column *mk_copy_column(column *x)
{
  column *newx = AM_MALLOC(column);
  newx -> symbol_values = mk_copy_pivec_maybe_null(x->symbol_values);
  newx -> real_values = mk_copy_pvector_maybe_null(x->real_values);
  newx -> num_missing = x -> num_missing;
  newx -> missing = mk_copy_pivec_maybe_null(x->missing);
  return(newx);
}

void fprintf_column(FILE *s,char *m1,column *x,char *m2)
{
  char buff[200];
  sprintf(buff,"%s -> symbol_values",m1);
  fprintf_pivec_maybe_null(s,buff,x->symbol_values,m2);
  sprintf(buff,"%s -> real_values",m1);
  fprintf_pvector_maybe_null(s,buff,x->real_values,m2);
  sprintf(buff,"%s -> num_missing",m1);
  fprintf_int(s,buff,x->num_missing,m2);
  sprintf(buff,"%s -> missing",m1);
  fprintf_pivec_maybe_null(s,buff,x->missing,m2);
}

column *mk_zero_column(colshape *cs,int num_entries)
{
  column *col = AM_MALLOC(column);
  col -> symbol_values = NULL;
  col -> real_values = NULL;
  col -> missing = NULL; /* By default, no missing values */
  col -> num_missing = 0;

  if ( colshape_real_ref_ok(cs) )
    col -> real_values = mk_zero_pvector(num_entries);

  if ( colshape_symbol_ref_ok(cs) )
  {
    int arity = string_array_size(cs->scs->value_names);
    col -> symbol_values = mk_zero_pivec(num_entries,(arity>0)?arity-1:0);
  }
  
  return(col);
}

bool column_real_ref_ok(column *col)
{
  return col->real_values != NULL;
}

bool column_symbol_ref_ok(column *col)
{
  return col->symbol_values != NULL;
}

void save_column_fast(FILE *s,column *col)
{
  if ( column_symbol_ref_ok(col) ) save_pivec_fast(s,col->symbol_values);
  if ( column_real_ref_ok(col) ) save_pvector_fast(s,col->real_values);
  save_pivec_fast(s,col->missing);  /* note: its ok for this to be NULL */
}

static int column_arity(column *col)
{
  check_column_symbol_ref_ok(col);
  return pivec_max_value(col->symbol_values)+1;
}

column *mk_column_from_stream_fast(FILE *s,colshape *cs,int *line,int version,
                                   char **r_errmess)
{
  column *col = AM_MALLOC(column);
  *r_errmess = NULL;

  col -> real_values = NULL;
  col -> symbol_values = NULL;
  col -> missing = NULL; /* By default, nothing missing */
  col -> num_missing = 0;

  if ( *r_errmess == NULL && colshape_symbol_ref_ok(cs) )
  {
    col -> symbol_values = mk_load_pivec_fast(s,line,r_errmess);
    if ( column_arity(col) != colshape_arity(cs) && column_arity(col) != 1 && colshape_arity(cs) != 0 )
    {
      printf("column_arity(col) = %d\n",column_arity(col));
      printf("colshape_arity(cs) = %d\n",colshape_arity(cs));
      *r_errmess = mk_printf("Column is wrong arity");
    }
  }

  if ( *r_errmess == NULL && colshape_real_ref_ok(cs) )
  {
    col -> real_values = mk_load_pvector_fast(s,line,r_errmess);
  }
  
  if ( *r_errmess == NULL && version > 0 )
  {
    if ((col->missing = mk_load_pivec_fast(s,line,r_errmess)))
      col->num_missing = pivec_sum(col->missing);
  }

  if ( *r_errmess != NULL )
  {
    free_column(col);
    col = NULL;
  }

  return(col);
}

void check_column_not_missing(column *col,int row)
{
  if ( column_value_is_missing(col,row) )
    my_error(mk_printf("column_ref: row=%d: value is missing\n",row));
}

static pivec *column_symbol_values(column *col)
{
#ifndef AMFAST
  if ( !column_symbol_ref_ok(col) )
    my_error("Attempted symbol_ref on an inappropriate column");
#endif
  return col->symbol_values;
}

static pvector *column_real_values(column *col)
{
#ifndef AMFAST
  if ( !column_real_ref_ok(col) )
    my_error("Attempted real_ref on an inappropriate column");
#endif
  return col->real_values;
}

int column_symbol_ref(column *col,int row)
{
#ifndef AMFAST
  check_column_not_missing(col,row);
#endif
  return pivec_ref(column_symbol_values(col),row);
}

double column_real_ref(column *col,int row)
{
#ifndef AMFAST
  check_column_not_missing(col,row);
#endif
  return pvector_ref(column_real_values(col),row);
}

void column_set_value_non_missing(column *col,int row)
{
  if ( col -> num_missing < 0 )
    my_error("col->num_missing confused");
  else 
  {
    bool previously_missing = column_value_is_missing(col,row);
    if ( previously_missing )
    {
      pivec_set(col->missing,row,0);
      col-> num_missing -= 1;
      if ( col->num_missing == 0 )
      {
        free_pivec(col->missing);
        col->missing = NULL;
      }
    }
  }
}

int column_size(column *col)
{
  return column_symbol_ref_ok(col) ? pivec_size(col->symbol_values) :
	                             pvector_size(col->real_values);
}

void column_set_value_missing(column *col,int row)
{
  if ( col -> num_missing > column_size(col) )
    my_error("col->num_missing confused");
  else 
  {
    bool previously_missing = column_value_is_missing(col,row);
    if ( previously_missing )
    {
      my_assert(col->num_missing > 0);
    }
    else
    {
      if ( col->num_missing == 0 )
      {
	my_assert(col->missing == NULL);
        col -> missing = mk_zero_pivec(column_size(col),2);
      }
      col -> num_missing += 1;
      pivec_set(col->missing,row,1);
    }
  }
}

/* If the column has both coltypes this function does not
   complain and merely updates the symbol coltype */
void column_symbol_set(column *col,int row,int value)
{
  pivec_set(column_symbol_values(col),row,value);

  if ( column_value_is_missing(col,row) )
    column_set_value_non_missing(col,row);
}

/* If the column has both coltypes this function does not
   complain and merely updates the real coltype */
void column_real_set(column *col,int row,double value)
{
  pvector_set(column_real_values(col),row,value);

  if ( column_value_is_missing(col,row) )
    column_set_value_non_missing(col,row);
}

static void maybe_free_pivec(pivec **x_ptr)
{
  if ( *x_ptr != NULL )
  {
    free_pivec(*x_ptr);
    *x_ptr = NULL;
  }
}

static void maybe_free_pvector(pvector **x_ptr)
{
  if ( *x_ptr != NULL )
  {
    free_pvector(*x_ptr);
    *x_ptr = NULL;
  }
}

/* Use this function to mess with the column types. You have three
   options:

     major_coltype == REAL_COL , minor_coltype == UNDEFINED_COL
        This with allow real_refs and real_sets in future and will
        forbid symbol_refs and symbol_sets. Any previous symbolic 
        values will be eliminated.

        If real_ref was okay on entry (i.e. if real values were previously
        defined) they remain unchanged by this function. If they were
        previously undefined, they will start life with all 0.0 values.


     major_coltype == SYMBOL_COL , minor_coltype == UNDEFINED_COL
        This with allow symbol_refs and symbol_sets in future and will
        forbid real_refs and real_sets. Any previous real 
        values will be eliminated.

        If symbol_ref was okay on entry (i.e. if symbol values were previously
        defined) they remain unchanged by this function. If they were
        previously undefined, they will start life with all 0 values.


     major_coltype == SYMBOL_COL , minor_coltype == REAL_COL
     major_coltype == REAL_COL   , minor_coltype == SYMBOL_COL
        (The above two are equivalent in meaning...this behavior
         differs from colshape_set_type()'s behavior)

        This will allow both symbol_refs and symbol_sets in future and will
        allow real_refs and real_sets.

        If symbol_ref was okay on entry (i.e. if symbol values were previously
        defined) they remain unchanged by this function. If they were
        previously undefined, they will start life with all 0 values.
        If real_ref was okay on entry (i.e. if real values were previously
        defined) they remain unchanged by this function. If they were
        previously undefined, they will start life with all 0.0 values.

  So what about the arity of the symbol component? If there no
  symbol component, then it doesn't matter. If there *is* is new 
  symbol component (previously none) then the arity will be zero.
  And if there was a symbol component before and there's still one,
  the arity will be unchanged.
*/
void column_set_coltype(column *col,int major_coltype,int minor_coltype)
{
  if ( major_coltype == UNDEFINED_COL || major_coltype == minor_coltype )
    my_error("set_colshape_type");
  else
  {
    int col_size = column_size(col);
    if ( major_coltype == REAL_COL || minor_coltype == REAL_COL )
    {
      if ( col -> real_values == NULL )
	col -> real_values = mk_zero_pvector(col_size);
    }
    else 
      maybe_free_pvector(&col->real_values);

    if ( major_coltype == SYMBOL_COL || minor_coltype == SYMBOL_COL )
    {
      if ( col -> symbol_values == NULL )
      {
	int max_value = 0;
	col -> symbol_values = mk_zero_pivec(col_size,max_value);
      }
    }
    else 
      maybe_free_pivec(&col->symbol_values);
  }
}

void check_column_symbol_ref_ok(column *co)
{
  if ( co->symbol_values == NULL )
    my_error("check_column_symbol_ref_ok");
}

/* Changes the arity of the column to the new arity. What happens to the
   values in the new column? If the arity is increased it remains 
   unchanged. If the arity is decreased, all previous values of "arity"
   or greater are changed to arity-1.

   It is okay to call this with an arity of zero, but in that case
   values may never be referenced. */
void column_set_arity(column *col,int arity)
{
  int max_value = (arity == 0) ? 0 : arity-1;
  check_column_symbol_ref_ok(col);
  pivec_set_max_value(col->symbol_values,max_value);
}

/* All values greater than "value" are decremented. All
   values that were previously equal to "value" are
   set to be missing values. */
void column_delete_value(column *col,int value)
{
  int row;
  int num_rows = column_size(col);

  check_column_symbol_ref_ok(col);

  for ( row = 0 ; row < num_rows ; row++ )
  {
    if ( !column_value_is_missing(col,row) )
    {
    	int v = column_symbol_ref(col,row);
	    if ( v == value )
        column_set_value_missing(col,row);
      else if ( v > value )
	      column_symbol_set(col,row,v-1);
    }  
  }

  column_set_arity(col,column_arity(col)-1);
}
 
#ifdef NEVER
void convert_symbol_col_to_real_col(colshape *cs,column *col,
                                    colshape **r_cs,column **r_col)
{
  int i;
  *r_cs = mk_colshape(cs->scs->name,REAL_COL);
  *r_col = mk_zero_column(*r_cs,column_size(col));
  for ( i = 0 ; i < column_size(col) ; i++ )
  {
    if ( column_value_is_missing(col,i) )
      column_set_value_missing(*r_col,i);
    else
    {
      int value = column_symbol_ref(col,i);
      char *name = string_array_ref(cs->scs->value_names,value);
      if ( !is_a_number(name) )
        my_error("convert_symbol_col_to_real_col: non numeric symbol value");
      column_real_set(*r_col,i,atof(name));
    }
  }
}

void convert_real_col_to_symbol_col(colshape *cs,column *col,
                                    dyv *breaks,
                                    colshape **r_cs,column **r_col)
{
  int i;
  char buff[1000];
  char *format = (dyv_size(breaks)<10) ? "v%d:%g" : "v%02d:%g";
  char format_lo[100];
  char format_hi[100];

  sprintf(format_lo,"%s-",format);
  sprintf(format_hi,"%s+",format);

  *r_cs = mk_colshape(cs->rcs->name,SYMBOL_COL);

  if ( dyv_size(breaks) > 99 ) my_error("at most 99 breaks");
  /* Otherwise the value names defined below will not have alphabetical
     order equals value order which would lead to dserious bug */

  sprintf(buff,format_lo,0,dyv_ref(breaks,0));
  scolshape_add_value_name((*r_cs)->scs,buff);
  for ( i = 0 ; i < dyv_size(breaks) ; i++ )
  {
    sprintf(buff,format_hi,i+1,dyv_ref(breaks,i));
    scolshape_add_value_name((*r_cs)->scs,buff);
  }

  *r_col = mk_zero_column(*r_cs,column_size(col));

  for ( i = 0 ; i < column_size(col) ; i++ )
  {
    if ( column_value_is_missing(col,i) )
      column_set_value_missing(*r_col,i);
    else
    {
      double value = column_real_ref(col,i);
      int j = 0;

      while ( j < dyv_size(breaks) && dyv_ref(breaks,j) < value )
        j += 1;

      column_symbol_set(*r_col,i,j);
    }
  }
}




column *mk_random_column(column *x, int arity, int num_rows)
{
  column *newx = AM_MALLOC(column);
  int i;
  
  newx -> symbol_values = mk_copy_pivec( x -> symbol_values );

  for(i=0;i<num_rows;i++)
  {
    int rand_num = int_random(arity);
    pivec_set(newx -> symbol_values,i,rand_num);
  }
  newx -> real_values = NULL;
  newx -> num_missing = 0;
  newx -> missing = NULL;
  return(newx);
}
#endif

void column_delete_last_n_records(column *co,int n)
{
  int new_size = column_size(co)-n;
  if ( co -> num_missing > 0 )
  {
    pivec *new_missing = mk_pivec_segment(co->missing,0,new_size);
    free_pivec(co->missing);
    co -> missing = new_missing;
    co -> num_missing = pivec_num_non_zero(co->missing);
    if ( co -> num_missing == 0 )
    {
      free_pivec(co->missing);
      co -> missing = NULL;
    }
  }

  if (co->real_values)
  {
    /* It's real valued */
    pvector *new_pvec = mk_pvector_segment(co->real_values,0,new_size);
    free_pvector(co->real_values);
    co -> real_values = new_pvec;
  }
  if (co->symbol_values)
  {
    /* It's symbol valued */
    pivec *new_piv = mk_pivec_segment(co->symbol_values,0,new_size);
    free_pivec(co->symbol_values);
    co -> symbol_values = new_piv;
  }
}

/* Like above, except just keep those given in subset */
void column_keep_subset(column *co,ivec *subset)
{
  if ( co -> num_missing > 0 )
  {
    pivec *new_missing = mk_pivec_subset(co->missing,subset);
    free_pivec(co->missing);
    co -> missing = new_missing;
    co -> num_missing = pivec_num_non_zero(co->missing);
    if ( co -> num_missing == 0 )
    {
      free_pivec(co->missing);
      co -> missing = NULL;
    }
  }

  if (co->real_values)
  {
    /* It's real valued */
    pvector *new_pvec = mk_pvector_subset(co->real_values,subset);
    free_pvector(co->real_values);
    co -> real_values = new_pvec;
  }
  if (co->symbol_values)
  {
    /* It's symbol valued */
    pivec *new_piv = mk_pivec_subset(co->symbol_values,subset);
    free_pivec(co->symbol_values);
    co -> symbol_values = new_piv;
  }
}

/* This function used to exclusively add either to the symbol values OR
   to the real values.  This is a problem when an attribute has both.
   I made the most naive update to allow it to do both, hopefully this
   does not introduce some new problem --- JGS 02/05/01
*/
void column_add_records(column *co,int n)
{
  if ( co -> num_missing > 0 )
  {
    pivec *new_missing = mk_add_elements_to_pivec(co->missing,n);
    free_pivec(co->missing);
    co -> missing = new_missing;
    /* Note co->num_missing will be unchanged */
  }

  if (co->real_values)
  {
    /* It's real valued */
    pvector *new_pvec = mk_add_elements_to_pvector(co->real_values,n);
    free_pvector(co->real_values);
    co -> real_values = new_pvec;
  }
  if (co->symbol_values)
  {
    /* It's symbol valued */
    pivec *new_piv = mk_add_elements_to_pivec(co->symbol_values,n);
    free_pivec(co->symbol_values);
    co -> symbol_values = new_piv;
  }
}

void column_increment_arity(column *co)
{
  check_column_symbol_ref_ok(co);
  pivec_increment_max_value(co->symbol_values);
}


