/* 	File: am_string_array.c 
 * 	Author(s):
 * 	Date:
 * 	Purpose:
 */

#include "standard.h"
#include "ambs.h"
#include "amiv.h"
#include "am_string_array.h"
#include "amma.h"
#include "am_string.h"

/* *sigh* */
int String_Arrays_mallocked = 0;
int String_Arrays_freed = 0;
/* Private? */
void check_string_array_code(const string_array *sar, const char *name);

#define STRING_ARRAY_CODE 20542

string_array *mk_string_array(int size)
{
  string_array *result = AM_MALLOC(string_array);
  int i;

  result -> string_array_code = STRING_ARRAY_CODE;
  result -> size = size;
  result -> sarr_size = size;
  result -> sarr = AM_MALLOC_ARRAY(char_ptr,size);

  for ( i = 0 ; i < size ; i++ )
    result->sarr[i] = mk_copy_string("<UnDeFiNeD>");
  String_Arrays_mallocked += 1;
  return(result);
}

void free_string_array(string_array *sar)
{
  int i;
  check_string_array_code(sar,"free_string_array");
  sar -> string_array_code = 7777;

  for ( i = 0 ; i < sar->size ; i++ )
    if ( sar->sarr[i] != NULL )
      am_free_string(sar->sarr[i]);

  AM_FREE_ARRAY(sar->sarr,char_ptr,sar->sarr_size);
  AM_FREE(sar,string_array);

  String_Arrays_freed += 1;
}

string_array *mk_copy_string_array(const string_array *sa)
{
  string_array *nsa = mk_string_array(string_array_size(sa));
  int i;
  for ( i = 0 ; i < string_array_size(sa) ; i++ )
    string_array_set(nsa,i,string_array_ref(sa,i));
  return(nsa);
}

bool string_array_equal(string_array *a,string_array *b)
{
  int size = string_array_size(a);
  bool equal = size == string_array_size(b);
  int i;
  for ( i = 0 ; equal && i < size ; i++ )
    equal = equal && eq_string(string_array_ref(a,i),string_array_ref(b,i));
  return equal;
}

string_array *mk_string_array_1(char *x)
{
  string_array *sa = mk_string_array(1);
  string_array_set(sa,0,x);
  return(sa);
}


void check_string_array_code(const string_array *sar, const char *name)
{
  if ( sar == NULL )
  {
    fprintf(stderr,"NULL string_array passed in operation %s\n",name);
    my_error("string_array data structure");
  }
  if ( sar->string_array_code != STRING_ARRAY_CODE )
  {
    fprintf(stderr,"Attempt to access a non-allocated String Array\n");
    fprintf(stderr,"This is in the operation %s\n",name);
    my_error("string_array data structure error");
  }
}

void check_string_array_access(const string_array *sar, int i, const char *name)
{
  check_string_array_code(sar,name);

  if ( i < 0 || i >= sar->size )
  {
    fprintf(stderr,"In operation \"%s\"\n",name);
    fprintf(stderr,"the string_array has size = %d\n",sar->size);
    fprintf(stderr,"You tried to use index i=%d\n",i);
    fprintf(stderr,"Here is the string_array that was involved:\n");
    fprintf_string_array(stderr,"sarv",sar,"\n");
    my_error("check_string_array_access");
  }
}

void assert_string_array_shape(string_array *sar,int size,char *name)
{
  check_string_array_code(sar,name);

  if ( size != sar->size )
  {
    fprintf(stderr,"In operation \"%s\"\n",name);
    fprintf(stderr,"the string_array has size = %d\n", sar->size);
    fprintf(stderr,"But should have been predefined with the shape:\n");
    fprintf(stderr,"size = %d\n",size);
    my_error("assert_string_array_shape");
  }
}

string_array *mk_string_array_2(char *x1,char *x2)
{
  string_array *sa = mk_string_array(2);
  string_array_set(sa,0,x1);
  string_array_set(sa,1,x2);
  return(sa);
}

string_array *mk_string_array_3(char *x1,char *x2,char *x3)
{
  string_array *sa = mk_string_array(3);
  string_array_set(sa,0,x1);
  string_array_set(sa,1,x2);
  string_array_set(sa,2,x3);
  return(sa);
}

string_array *mk_string_array_4(char *x1,char *x2,char *x3,char *x4)
{
  string_array *sa = mk_string_array(4);
  string_array_set(sa,0,x1);
  string_array_set(sa,1,x2);
  string_array_set(sa,2,x3);
  string_array_set(sa,3,x4);
  return(sa);
}

string_array *mk_string_array_5(char *x1,char *x2,char *x3,char *x4,char *x5)
{
  string_array *sa = mk_string_array(5);
  string_array_set(sa,0,x1);
  string_array_set(sa,1,x2);
  string_array_set(sa,2,x3);
  string_array_set(sa,3,x4);
  string_array_set(sa,4,x5);
  return(sa);
}

string_array *mk_string_array_x( int size, ...)
{
  /* XXX: no type checking */
  int i;
  char *s;
  va_list argptr;
  string_array *sa;

  sa = mk_string_array( size);

  va_start( argptr, size);
  for (i=0; i<size; ++i) {
    s = va_arg( argptr, char *);
    string_array_set( sa, i, s); /* Copies string into string array. */
  }
  va_end(argptr);

  return sa;
}

char **mk_array_from_string_array( string_array *sa)
{
  int size, i;
  char **array, *s;

  size = string_array_size( sa);
  array = AM_MALLOC_ARRAY( char *, size);
  for (i=0; i<size; ++i) {
    s = string_array_ref( sa, i);
    array[i] = mk_copy_string( s);
  }

  return array;
}

void swap_string_array(string_array *sa, int i, int j)
{
	char *tmp = mk_copy_string(string_array_ref(sa,i));
	string_array_set(sa,i,string_array_ref(sa,j));
	string_array_set(sa,j,tmp);
  free_string(tmp);
}

void qsort_string_array(string_array *sa, int left, int right)
{
	int last, i;
	if(left>=right) return; /*basis*/
	swap_string_array(sa, left, (left+right)/2);
	last = left;
	for(i=left+1;i<=right;i++)
	{
		if(strcmp(string_array_ref(sa,i),string_array_ref(sa,left)) < 0)
		{
			swap_string_array(sa,++last,i);
		}
	}
		swap_string_array(sa, left, last);
	qsort_string_array(sa, left, last-1);
	qsort_string_array(sa,last+1,right);
}

void sort_string_array(string_array *sa)
{
  qsort_string_array(sa,0, string_array_size(sa)-1);
  
  /*the built in qsort didn't work for some reason.*/
  /*qsort((char *)sa->sarr,sa->size,sizeof(char *),strcmp);*/
}

string_array *mk_sort_string_array(string_array *sa)
{
  string_array *result = mk_copy_string_array(sa);
  sort_string_array(result);
  return result;
}

string_array *mk_sort_string_array_remove_duplicates(string_array *sa_base)
{
  string_array *sorted = mk_sort_string_array(sa_base);
  string_array *result = mk_string_array(0);
  int i;
  char *s_prev = NULL;

  for ( i = 0 ; i < string_array_size(sorted) ; i++ )
  {
    char *s_this = string_array_ref(sorted,i);
    if ( s_prev == NULL || !eq_string(s_this,s_prev) )
      add_to_string_array(result,s_this);
    s_prev = s_this;
  }

  free_string_array(sorted);

  return result;
}

/*reverse string array order */
string_array *mk_reverse_string_array(string_array *sa)
{
    string_array *reversed = mk_string_array(0);
    int i;
    int size = string_array_size(sa);

    for (i = size - 1 ; i >=0 ; i--)
    {
	string_array_add(reversed, string_array_ref(sa, i));
    }

    return reversed;
}

void fprintf_string_array(FILE *s,const char *m1,const string_array *sar,
                          const char *m2)
{
  int i;

  if (sar == NULL)
    fprintf(s,"%s = <NULL string array>%s",m1,m2);
  else
  {
    check_string_array_code(sar,"fprintf_string_array");
    if ( string_array_size(sar) == 0 )
      fprintf(s,"%s = <empty string array>%s",m1,m2);
    else
    {
      for ( i = 0 ; i < string_array_size(sar) ; i++ )
	fprintf(s,"%s[%3d] = %s%s",
		m1,i,
		(string_array_ref(sar,i)==NULL) ? "NULL" : string_array_ref(sar,i),
		m2
		);
    }
  }
}

void pstring_array(string_array *sa)
{
  fprintf_string_array(stdout,"sa",sa,"\n");
}


void fprintf_string_array_contents_on_one_line(FILE *s,string_array *sar)
{
  int i;
  check_string_array_code(sar,"fprintf_string_array_contents");

  for ( i = 0 ; i < string_array_size(sar) ; i++ )
    fprintf(s,"%s ",
            (string_array_ref(sar,i)==NULL) ? "NULL" : string_array_ref(sar,i)
           );
}

void fprintf_string_array_contents(FILE *s,string_array *sar)
{
  int i;
  check_string_array_code(sar,"fprintf_string_array_contents");

  for ( i = 0 ; i < string_array_size(sar) ; i++ )
    fprintf(s,"%s\n",
            (string_array_ref(sar,i)==NULL) ? "NULL" : string_array_ref(sar,i)
           );
}

char *string_array_ref(const string_array *sar, int i)
{
  check_string_array_access(sar,i,"string_array_ref");
  return(sar->sarr[i]);
}

void string_array_set(string_array *sar,int i,char *value)
/* value is COPIED in */
{
  check_string_array_access(sar,i,"string_array_set");
  if ( sar->sarr[i] != NULL )
    am_free_string(sar->sarr[i]);
  sar->sarr[i] = mk_copy_string(value);
}

int string_array_size(const string_array *sar)
{
  check_string_array_code(sar,"string_array_size");
  return(sar->size);
}

string_array *mk_string_array_from_array(char **sarr,int size)
{
  int i;
  string_array *result = mk_string_array(size);
  for ( i = 0 ; i < size ; i++ )
    string_array_set(result,i,sarr[i]);
  return(result);
}

void add_to_string_array(string_array *sa, const char *string)
{
  if ( sa -> size == sa -> sarr_size )
    {
    int new_sarr_size = 2 + 2 * sa->sarr_size;
    char **new_sarr = AM_MALLOC_ARRAY(char_ptr,new_sarr_size);
    int i;

    for ( i = 0 ; i < sa->size ; i++ )
      new_sarr[i] = sa->sarr[i];

    AM_FREE_ARRAY(sa->sarr,char_ptr,sa->sarr_size);
    sa -> sarr_size = new_sarr_size;
    sa -> sarr = new_sarr;
    }

  sa -> size += 1;
  sa -> sarr[sa->size-1] = (string==NULL) ? NULL : mk_copy_string(string);
}

void insert_in_string_array(string_array *sa, int pos, char *string)
{
  int i;
  if (pos > sa->size)
    my_error("Illegal string_array index.");

  i = sa->size - 1;
  add_to_string_array(sa, NULL);
  for (; i >= pos; i--)
    sa->sarr[i + 1] = sa->sarr[i];

  sa->sarr[pos] = (string == NULL) ? NULL : mk_copy_string(string);
}

/* FIXME: A depreciated alternate name for add_to_string_array.  */
void string_array_add(string_array *sa,char *string)
{
  add_to_string_array(sa,string);
}

void string_array_malloc_report(void)
{
  if ( String_Arrays_mallocked > 0 )
  {
    fprintf(stdout,"#         Number of string_arrays currently allocated: %d\n",
           String_Arrays_mallocked - String_Arrays_freed
          );
    if ( String_Arrays_mallocked - String_Arrays_freed != 0 )
    {
      fprintf(stdout,"#       Number of string_array allocs since prog start:  %d\n",
             String_Arrays_mallocked
            );
      fprintf(stdout,"#       Number of string_array frees  since prog start:  %d\n#\n",
            String_Arrays_freed
            );
    }
  }
}


void string_array_remove(string_array* sa, int idx)
{
  int i;
  if ( string_array_size(sa) <= 0 ) my_error("string_array_remove: empty string_array");
  if ( idx < 0 || idx >= string_array_size(sa) )
    my_error("string_array_remove: bad index");

  /* WKW - New version shuffles the pointers over by one instead
     of using string_array_set, which copies the (i+1)th element into the
     ith spot and then frees the (i+1)th element.  This is faster and
     it avoids some memory bugs in the old version */
  if( sa->sarr[idx] != NULL )
    {
    am_free_string(sa->sarr[idx]);
    sa->sarr[idx]=NULL;
    }

  for( i = idx ; i < (sa->size - 1); i++ )
    sa->sarr[i] = sa->sarr[i+1];

  sa->sarr[sa->size-1] = NULL; /* Might help catch some weird errors */
  sa -> size -= 1;
}


void string_array_remove_last_element(string_array* sa)
{ /* It'd be tempting to use a C99 function-macro to do this, if we could use C99 -- Pat */
  string_array_remove(sa,string_array_size(sa)-1);
}


string_array *mk_broken_string_using_seppers(const char *string, const char *seppers)
{
  string_array *sa = mk_string_array(0);
  int i = 0;
  while ( string != NULL && string[i] != '\0' )
    {
    int j = 0;
    while ( is_sepper(string[i],seppers) )
      i += 1;

    if ( string[i] != '\0' )
      {
      int part_size,backslashcount = 0;
      char *part,stopchar = ' ';
      int k;

      while ( string[i+j] != '\0' )
        {
	if(stopchar == ' ')
	  {
	  if(is_sepper(string[i+j],seppers))
            break;
	  else if((string[i+j]=='\"') && !(backslashcount%2))
            stopchar = '\"';
	  }
	else if(stopchar == '\"')
	  {
	  /* bug fix? this used to say stopchar = '\n' which made it put the
             whole rest of the line into one string once it had seen a double
             quote.  Now it only includes up to the next double quote. 8/24/99 JGS */
	  if((string[i+j] == '\"') && !(backslashcount %2))
            stopchar = ' ';
	  }

	if (string[i+j] == '\\') backslashcount++;
	else                     backslashcount=0;
	
        j++;
        }

      part_size = j+1;
      part = AM_MALLOC_ARRAY(char,part_size);

      for ( k = 0 ; k < j ; k++ )
        part[k] = string[i+k];
      if ( k != part_size-1 ) my_error("oaibxwibxpwibx");
      part[k] = '\0';
      string_array_add(sa,part);
      AM_FREE_ARRAY(part,char,part_size);
      }
    i = i+j;
    }
  return(sa);
}

string_array *mk_broken_string_using_seppers_only(char *string,char *seppers)
{
  string_array *sa = mk_string_array(0);
  char *p = strpbrk(string,seppers);
  char c;
  while(p){
    c = *p;
    *p = '\0';
    add_to_string_array(sa,string);
    *p = c;
    string = p+1;
    p = strpbrk(string,seppers);
  }
  add_to_string_array(sa,string);
  return sa;
}

string_array *mk_broken_string(const char *string)
{
  string_array *result = mk_broken_string_using_seppers(string,NULL);
  return(result);
}

string_array *mk_broken_quoteless_string(char *string)
{
  char *quoteless = mk_quoteless_string(string);
  string_array *result = mk_broken_string_using_seppers(quoteless,NULL);
  free_string(quoteless);
  return(result);
}

string_array *mk_string_array_subset(string_array *sa,ivec *indices)
{
  int size = ivec_size(indices);
  string_array *result = mk_string_array(size);
  int i;
  for ( i = 0 ; i < size ; i++ )
    string_array_set(result,i,string_array_ref(sa,ivec_ref(indices,i)));
  return result;
}


char *mk_string_from_last_n_with_separator(string_array *sa,int n,
					   bool use_sep,char sep)
{
  char *result;

  my_assert(sep != '\0');

  if ( n < 0 )
  {
    my_error("odcbowdn");
    result = NULL;
  }
  else if ( n == 0 )
    result = mk_copy_string("");
  else
  {
    int i;
    int array_size = ( use_sep ) ? n : 1;
    int k = 0;
    int start = string_array_size(sa) - n;

/* array_size = sum of string lengths + n-1 seperators + 1 \0 character.
   (note wouldnt be true for n==0 but thats dealt with above)
*/
    for ( i = 0 ; i < n ; i++ )
      array_size += strlen(string_array_ref(sa,start+i));

    result = AM_MALLOC_ARRAY(char,array_size);

    for ( i = 0 ; i < n ; i++ )
    {
      int j;
      char *s = string_array_ref(sa,start+i);
      for ( j = 0 ; s[j] != '\0' ; j++ )
      {
        result[k] = s[j];
        k += 1;
      }
      if(i==n-1)
      {
        result[k] = '\0';
        k += 1;
      } 
      else if ( use_sep ) 
      {
        result[k] = sep;
        k += 1;
      }
    }

    if ( k != array_size ) my_error("lkaxcnowsdcbolub");
  }
  return(result);
}

char *mk_string_from_last_n(string_array *sa,int n)
{
  return mk_string_from_last_n_with_separator(sa,n,TRUE,' ');
}

char *mk_string_from_string_array_basic(string_array *sa,bool use_sep,char sep)
{
  return mk_string_from_last_n_with_separator(sa,string_array_size(sa),
					      use_sep,sep);
}

char *mk_string_from_string_array_with_separator(string_array *sa,char sep)
{
  return mk_string_from_string_array_basic(sa,TRUE,sep);
}

char *mk_string_from_string_array(string_array *sa)
{
  return mk_string_from_string_array_with_separator(sa,' ');
}

char *mk_string_from_string_array_no_gaps(string_array *sa)
{
  return mk_string_from_string_array_basic(sa,FALSE,'7');
}

int find_index_in_string_array(const string_array *sa, const char *string)
{
  int result = -1;
  int i;
  for ( i = 0 ; i < string_array_size(sa) && result < 0 ; i++ )
    if ( eq_string(string,string_array_ref(sa,i)) )
      result = i;
  return(result);
}

int caseless_find_index_in_string_array(string_array *sa,char *string)
{
  int result = -1;
  int i;
  for ( i = 0 ; i < string_array_size(sa) && result < 0 ; i++ )
    if ( caseless_eq_string(string,string_array_ref(sa,i)) )
      result = i;
  return(result);
}

bool string_array_member(string_array *sa,char *string)
{
  return(find_index_in_string_array(sa,string)>=0);
}

string_array *mk_string_array_segment(string_array *sa,
                                      int start_index,int end_index)
{
  int seg_size = end_index - start_index;
  string_array *seg = mk_string_array(seg_size);
  int i;
  for ( i = 0 ; i < seg_size ; i++ )
    string_array_set(seg,i,string_array_ref(sa,start_index+i));
  return seg;
}

string_matrix *mk_string_matrix(int rows,int cols)
{
  string_matrix *sm = AM_MALLOC(string_matrix);
  int i;
  sm -> array_size = rows;
  sm -> rows = rows;
  sm -> cols = cols;
  sm -> sas = AM_MALLOC_ARRAY(string_array_ptr,rows);
  for ( i = 0 ; i < rows ; i++ )
  {
    int j;
    sm->sas[i] = mk_string_array(cols);
    for ( j = 0 ; j < cols ; j++ )
      string_array_set(sm->sas[i],j,"");
  }
  return(sm);
}

int string_matrix_rows(string_matrix *sm)
{
  return(sm->rows);
}

int string_matrix_cols(string_matrix *sm)
{
  return(sm->cols);
}

char *string_matrix_ref(string_matrix *sm,int row,int col)
{
  if ( row < 0 || col < 0 || row >= sm->rows || col >= sm->cols )
    my_error("string_matrix_ref bad row and/or col");
  return(string_array_ref(sm->sas[row],col));
}

/* Makes a COPY of char *value */
void string_matrix_set(string_matrix *sm,int row,int col,char *value)
{
  if ( row < 0 || col < 0 || row >= sm->rows || col >= sm->cols )
    my_errorf("string_matrix_set(sm,%d,%d) when sm_rows = %d and sm_cols = %d",
	      row,col,string_matrix_rows(sm),string_matrix_cols(sm));
  string_array_set(sm->sas[row],col,value);
}

/* Makes a COPY of char *value ... identical to string_matrix_set */
void sm_set(string_matrix *sm,int row,int col,char *value)
{
  string_matrix_set(sm,row,col,value);
}

string_array *mk_string_array_from_string_matrix_col(string_matrix *sm,
						     int col)
{
  int num_rows = string_matrix_rows(sm);
  int i;
  string_array *result = mk_string_array(num_rows);

  for ( i = 0 ; i < num_rows ; i++ )
  {
    char *string = string_matrix_ref(sm,i,col);
    string_array_set(result,i,string);
  }

  return result;
}

void fprintf_string_matrix(FILE *s,char *m1,string_matrix *sm,char *m2)
{
  if ( sm == NULL )
    fprintf(s,"%s = (string_matrix *)NULL%s",m1,m2);
  else if ( sm -> rows == 0 )
    fprintf(s,"%s = <string_matrix with 0 rows>%s",m1,m2);
  else
  {
    int i;
    for ( i = 0 ; i < string_matrix_rows(sm) ; i++ )
    {
      char buff[100];
      sprintf(buff,"%s[%2d]",m1,i);
      fprintf_string_array(s,buff,sm->sas[i],"\n");
    }
  }
}

void string_matrix_add_row(string_matrix *sm)
{
  int j;

  if ( sm->array_size < sm->rows )
    my_error("oujbcowlbucv");
  if ( sm->array_size == sm->rows )
  {
    int new_size = int_max(100,(int) (2.5 * sm->array_size));
    string_array **new_ar = AM_MALLOC_ARRAY(string_array_ptr,new_size);
    int i;
    for ( i = 0 ; i < new_size ; i++ )
      new_ar[i] = NULL;
    for ( i = 0 ; i < sm->rows ; i++ )
      new_ar[i] = sm->sas[i];

    am_free((char *)sm->sas,sm->array_size * sizeof(string_array_ptr));
    sm->sas = new_ar;
    sm->array_size = new_size;
  }
  sm->sas[sm->rows] = mk_string_array(sm->cols);
  
  /* Initialize new row with empty string entries. */
  for ( j = 0 ; j < sm->cols ; j++ )
    string_array_set(sm->sas[sm->rows],j,"");

  sm->rows += 1;
}


void free_string_matrix(string_matrix *sm)
{
  int i;
  for ( i = 0 ; i < string_matrix_rows(sm) ; i++ )
    free_string_array(sm->sas[i]);
  AM_FREE_ARRAY(sm->sas,string_array_ptr,sm->array_size);
  AM_FREE(sm,string_matrix);
}

int max_string_length_in_column(string_matrix *sm,int col)
{
  int result = 0;
  int row;
  for ( row = 0 ; row < string_matrix_rows(sm) ; row++ )
    result = int_max(result,(int)strlen(string_matrix_ref(sm,row,col)));
  return(result);
}

char *string_before_col(string_array *seps,int col)
{
  char *result;

  if (seps == NULL)
    result = (col == 0) ? "" : " ";
  else
    result = string_array_ref(seps,col);
  return(result);
}

char *rightmost_string(string_array *seps)
{
  char *result = (seps==NULL)?"":
                 string_array_ref(seps,string_array_size(seps)-1);
  return(result);
}

/* each entry i is the i'th row of a plain text representation of the contents
   of the string matrix. The columns of the table are separated by
   the strings in "seps". seps[0] always appears before the left-hand
   column. seps[1] before the second column. seps[cols] always
   appears to the right of the final column. The number of
   entries in "seps" must be one more than the number of columns
   in sm. 

   Alternatively, seps may be NULL in which case one space is
   printed between each column. No spaces to left or right.
*/
string_array *mk_tabular_string_array(string_matrix *sm,string_array *seps)
{
  int chars_per_line = 0;
  char *buffer;
  int row,col;
  int rows = string_matrix_rows(sm);
  int cols = string_matrix_cols(sm);
  int buffer_size;
  string_array *result = mk_string_array(rows);

  my_assert(seps == NULL || string_array_size(seps) == cols+1);

  for ( col = 0 ; col < cols ; col++ )
  {
    chars_per_line += max_string_length_in_column(sm,col);
    chars_per_line += strlen(string_before_col(seps,col));
  }
  chars_per_line += strlen(rightmost_string(seps));

  buffer_size = chars_per_line + 1;
  buffer = AM_MALLOC_ARRAY(char,buffer_size);

  for ( row = 0 ; row < rows ; row++ )
  {
    int i = 0;
    int j;
    char *last_sep = rightmost_string(seps);
    for ( col = 0 ; col < cols ; col++ )
    {
      char *sep = string_before_col(seps,col);
      char *con = string_matrix_ref(sm,row,col);
      int chars_after_con = max_string_length_in_column(sm,col) - 
                            strlen(con);
      if ( chars_after_con < 0 ) my_error("uwocbxwloubc");
      for ( j = 0 ; sep[j] != '\0' ; j++ )
      {
        buffer[i] = sep[j];
        i += 1;
      }
      for ( j = 0 ; con[j] != '\0' ; j++ )
      {
        buffer[i] = con[j];
        i += 1;
      }
      for ( j = 0 ; j < chars_after_con ; j++ )
      {
        buffer[i] = ' ';
        i += 1;
      }
    }
    for ( j = 0 ; last_sep[j] != '\0' ; j++ )
    {
      buffer[i] = last_sep[j];
      i += 1;
    }
    if ( i != chars_per_line ) my_error("owbxolwubdcoufcbv");
    buffer[i] = '\0';
    string_array_set(result,row,buffer);
  }

  AM_FREE_ARRAY(buffer,char,buffer_size);

  return(result);
}

string_array *mk_tabular_string_array_simple(string_matrix *sm,
					     char *seps)
{
  int size = (int)strlen(seps);
  string_array *sep_sa = mk_string_array(size);
  int i;
  string_array *result;

  for ( i = 0 ; i < size ; i++ )
  {
    char x[2];
    x[0] = seps[i];
    x[1] = '\0';
    string_array_set(sep_sa,i,x);
  }
  result = mk_tabular_string_array(sm,sep_sa);

  free_string_array(sep_sa);
  return result;
}

int find_index_in_sorted_string_array(string_array *sa,char *s)
{
  int result = -1;
  if ( string_array_size(sa) > 0 )
  {
    int i_lo = 0;
    int i_hi = string_array_size(sa)-1;
    char *s_lo = string_array_ref(sa,i_lo);
    char *s_hi = string_array_ref(sa,i_hi);
    if ( eq_string(s,s_lo) )
      result = i_lo;
    else if ( eq_string(s,s_hi) )
      result = i_hi;
    while ( result < 0 && i_hi > i_lo + 1 )
    {
      int i_mid = (i_lo + i_hi)/2;
      char *s_mid = string_array_ref(sa,i_mid);
      int cmp = strcmp(s,s_mid);
      /* cmp < 0 <=> s is lexicographically before s_mid */
      /* cmp = 0 <=> s equals s_mid */
      /* cmp > 0 <=> s is lexicographically after s_mid */
      if ( cmp == 0 )
        result = i_mid;
      else if ( cmp < 0 )
      {
        i_hi = i_mid;
        s_hi = s_mid;
      }
      else
      {
        i_lo = i_mid;
        s_lo = s_mid;
      }
    }
  }
  return(result);
}

/* FIXME: This routine is painfully inefficient.  It actually recopies
   (read malloc and free ferociously) the strings all the way down
   the line to make an insertion.  This can be easily cured with 
   some pointer swapping instead. -- ??? */
void insert_in_sorted_string_array(string_array *sa,char *s)
{
  int i = 0;
  int size = string_array_size(sa);
  while ( i < size && strcmp(string_array_ref(sa,i),s) < 0 )
    i += 1;
  if ( i == size )
    add_to_string_array(sa,s);
  else
  {
    int j;
    add_to_string_array(sa,"");
    for ( j = size-1 ; j >= i ; j-- )
      string_array_set(sa,j+1,string_array_ref(sa,j));
    string_array_set(sa,i,s);
  }
}

void maybe_insert_in_sorted_string_array(string_array *sa,char *s)
{
  if ( find_index_in_sorted_string_array(sa,s) < 0 )
    insert_in_sorted_string_array(sa,s);
}

/* mk_int_string_array("plop",3) would return { "plop0" , "plop1" , "plop2" } */
string_array *mk_int_string_array(char *prefix,int size)
{
  string_array *sa = mk_string_array(size);
  int i;
  for ( i = 0 ; i < size ; i++ )
  {
    char *s = mk_printf("%s%d",prefix,i);
    string_array_set(sa,i,s);
    free_string(s);
  }
  return sa;
}

void render_string_matrix(FILE *s,char *comment,string_matrix *sm)
{
  string_array *sa = mk_tabular_string_array(sm,NULL);
  int i;
  for ( i = 0 ; i < string_array_size(sa) ; i++ )
    fprintf(s,"%s%s\n",comment,string_array_ref(sa,i));
  free_string_array(sa);
}

/* See comments for mk_tabular_string_array_simple regaring the meaning
   of "seps". */
void render_string_matrix_with_seps(FILE *s,string_matrix *sm,char *seps)
{
  string_array *sa = mk_tabular_string_array_simple(sm,seps);
  int i;
  for ( i = 0 ; i < string_array_size(sa) ; i++ )
    fprintf(s,"%s\n",string_array_ref(sa,i));
  free_string_array(sa);
}

/* See comments for mk_tabular_string_array_simple regaring the meaning
   of "seps". 

   If the first string on a line is a single dash, then this version
   draws a complete line of dashes for the full width of the string
   matrix output. 
*/
void render_string_matrix_with_seps_and_dashes(FILE *s,string_matrix *sm,
					       char *seps)
{
  string_array *sa = mk_tabular_string_array_simple(sm,seps);
  int num_dashes = (string_matrix_rows(sm) < 1) ? 80 :
                   (int)strlen(string_array_ref(sa,0));
  int i;
  for ( i = 0 ; i < string_array_size(sa) ; i++ )
  {
    if ( eq_string(string_matrix_ref(sm,i,0),"-") )
      fprint_dashes(s,num_dashes);
    else
      fprintf(s,"%s\n",string_array_ref(sa,i));
  }
  free_string_array(sa);
}

void string_matrix_real_set(string_matrix *sm,int row,int col,double value)
{
  char buff[100];
  sprintf(buff,"%g",value);
  string_matrix_set(sm,row,col,buff);
}

string_array *mk_string_array_from_line(FILE *s)
{
  char *st = mk_string_from_line(s);
  string_array *sa;
  if ( st == NULL )
    sa = NULL;
  else
    sa = mk_broken_string(st);

  if ( st != NULL ) am_free_string(st);
  return(sa);
}

void string_matrix_row_from_broken_string(string_matrix *sm, int row, char *row_string)
{
  string_array *sa = mk_broken_string(row_string);
  int i;
  if ( string_array_size(sa) != string_matrix_cols(sm) )
    my_error("sm from broken row: wrong size");
  for ( i = 0 ; i < string_array_size(sa) ; i++ )
    string_matrix_set(sm,row,i,string_array_ref(sa,i));
  free_string_array(sa);
}

string_array *parse_string_array(string_array *sa, char *sep)
{
  return parse_string_array_endpoints(sa, sep, 0, string_array_size(sa));
}

string_array *parse_string_array_endpoints(string_array *sa, char *sep, int start, int end) {
  int i;
  ivec *subset = mk_ivec(0);
  char *string;
  string_array *temp_sa;
  string_array *result = mk_string_array(0);

  start = (start <0)?0:start;
  end = (end <string_array_size(sa))?end:string_array_size(sa);

  for (i=start; i<end; i++) {
    if (eq_string(string_array_ref(sa, i), sep)) {
      temp_sa = mk_string_array_subset(sa, subset);
      string =  mk_string_from_string_array(temp_sa);
      add_to_string_array(result, string);

      free_string(string);
      free_string_array(temp_sa);      
      free_ivec(subset);
      subset = mk_ivec(0);

    } else {
      add_to_ivec(subset, i);
    }
  }
  
  if (ivec_size(subset) > 0) {
    temp_sa = mk_string_array_subset(sa, subset);
    string =  mk_string_from_string_array(temp_sa);
    add_to_string_array(result, string);
    
    free_string(string);
    free_string_array(temp_sa);      
  }
  free_ivec(subset);
  return result;
}

string_array *mk_string_array_from_set_notation(char *setnot)
{
  char *s = mk_copy_string(setnot);
  string_array *sa;
  int i;
  for ( i = 0 ; s[i] != '\0' ; i++ )
  {
    char c = s[i];
    if ( c == '[' || c == ']' || c == '{' || c == '}' || c == ',' )
      s[i] = ' ';
  }
  sa = mk_broken_string(s);
  free_string(s);
  return sa;
}

/************* NEW LINE PARSING CODE *************/

/* If line_format is WHITESPACE then the line is read SPACE_STYLE
   if line_format is COMMA      then the line is read COMMA_STYLE
   if line_format is BAR        then the line is read BAR_STYLE
   if lineformat is  ANY        then

        count the number of unquoted commas on the line (n_comma)
        and count the number of unquoted bars on the line (n_bar)

        if ( n_comma == 0 && n_bar == 0 ) use SPACE_FORMAT
        if ( n_comma >= n_bar )           use COMMA_FORMAT
        if ( n_bar > n_comma )            use BAR_FORMAT

   The line parser runs through a finite state machine. On
   each character it looks at the character type:

     S Space       - The character is the ' ' char
     C Comma       - The character is the ',' char
     A SingleQuote - The character is the '\'' char
     Q DoubleQuote - The character is the '\"' char
     T Token       - The character is something other than the above
     
   The line parser is building up an array of tokens. It begins with
   an empty array of tokens. It has a current token being built. It begins
   with the current token empty. After each character is read, it performs
   one of the following actions:

     ADD   Add the curent token to the array. Set the current token to empty
     PUSH  Put the current character at the end of the current token
     NIL   Do nothing
     DASH  Put a dash character at the end of the current token
     DP    Put a dash, then the current character at end of token
     UNKN  Add the UNKNOWN_STRING to the array. Clear current token


  COMMA_STYLE parsing:

       All whitespace to immediate left and right of commas is removed.
       All other contiguous blocks of whitespace are replaced with - symbols
         (outside quotes, N contiguous spaces are replaced with one -.
          inside quotes, N contiguous spaces are replaced with N -'s)
       The resulting tokens between commas are used.
       Empty string between commas is turned into UNKNOWN STRING

  BAR_STYLE parsing:
       Just like commas, except use bar (|) symbol instead of , symbol

  SPACE_STYLE parsing:

       All whitespace inside quotes are turned to dashes
       All other CONTIGUOUS blocks of whitespace are collapsed to one space
       Then the resulting tokens between whitespaces are used.
*/

/* private struct */
typedef struct parse_state
{
  int id;
  int s_action;  int s_next;
  int c_action;  int c_next;
  int a_action;  int a_next;
  int q_action;  int q_next;
  int t_action;  int t_next;
  int end_action;
} parse_state;

#define CGO  0
#define C1   1
#define C2   2
#define CQS  3
#define CQ   4
#define CAS  5
#define CA   6
#define SGO  7
#define S1   8
#define SQST 9
#define SQ   10
#define SAST 11
#define SA   12

#define ADD  0
#define PUSH 1
#define NIL  2
#define DASH 3
#define DP   4
#define UNKN 5
parse_state Parse_array[] =
/* State   S(act,next)  C(act,next)  A(act,next)  Q(act,next)  T(act,next) END(act)*/
{{ CGO ,   NIL ,CGO  ,  UNKN,CGO  ,  NIL ,CAS  ,  NIL ,CQS  ,  PUSH,C1   , UNKN},
 { C1  ,   NIL ,C2   ,  ADD ,CGO  ,  PUSH,C1   ,  PUSH,C1   ,  PUSH,C1   , ADD },
 { C2  ,   NIL ,C2   ,  ADD ,CGO  ,  DP  ,C1   ,  DP  ,C1   ,  DP  ,C1   , ADD },
 { CQS ,   DASH,CQ   ,  PUSH,CQ   ,  PUSH,CQ   ,  NIL ,CGO  ,  PUSH,CQ   , UNKN},
 { CQ  ,   DASH,CQ   ,  PUSH,CQ   ,  PUSH,CQ   ,  NIL ,C1   ,  PUSH,CQ   , ADD },
 { CAS ,   DASH,CA   ,  PUSH,CA   ,  NIL ,CGO  ,  PUSH,CA   ,  PUSH,CA   , UNKN},
 { CA  ,   DASH,CA   ,  PUSH,CA   ,  NIL ,C1   ,  PUSH,CA   ,  PUSH,CA   , ADD },
 { SGO ,   NIL ,SGO  ,  PUSH,S1   ,  NIL ,SAST ,  NIL ,SQST ,  PUSH,S1   , NIL },
 { S1  ,   ADD ,SGO  ,  PUSH,S1   ,  PUSH,S1   ,  PUSH,S1   ,  PUSH,S1   , ADD },
 { SQST,   DASH,SQ   ,  PUSH,SQ   ,  PUSH,SQ   ,  UNKN,SGO  ,  PUSH,SQ   , UNKN},
 { SQ  ,   DASH,SQ   ,  PUSH,SQ   ,  PUSH,SQ   ,  ADD ,SGO  ,  PUSH,SQ   , ADD },
 { SAST,   DASH,SA   ,  PUSH,SA   ,  UNKN,SA   ,  PUSH,SA   ,  PUSH,SA   , UNKN},
 { SA  ,   DASH,SA   ,  PUSH,SA   ,  ADD ,SA   ,  PUSH,SA   ,  PUSH,SA   , ADD }};

string_array *mk_string_array_from_parse(char *s,char separator)
{
  bool space_separated = separator == ' ';
  int state = (space_separated) ? SGO : CGO;
  bool finished = FALSE;
  int s_ptr = 0;
  string_array *tokarray = mk_string_array(0);
  int currtok_size = strlen(s) + 1;
  char *currtok = AM_MALLOC_ARRAY(char,currtok_size);
  int currtok_ptr = 0;

  while ( !finished )
  {
    parse_state *ps = &(Parse_array[state]);
    char c = s[s_ptr];
    int action;
    int next;

    if ( state != ps->id ) my_error("Parse_array misspecified");

    if ( c == '\0' )
    {
      finished = TRUE;
      next = -1;
      action = ps->end_action;
    }
    else if ( c == ' '  ) { action = ps->s_action ; next = ps->s_next; }
    else if ( c == '\t'  ) { action = ps->s_action ; next = ps->s_next; }
    else if ( c == separator ) { action = ps->c_action ; next = ps->c_next; }
    else if ( c == '\'' ) { action = ps->a_action ; next = ps->a_next; }
    else if ( c == '\"' ) { action = ps->q_action ; next = ps->q_next; }
    else                  { action = ps->t_action ; next = ps->t_next; }

    switch ( action )
    {
      case ADD :
        currtok[currtok_ptr] = '\0';
        add_to_string_array(tokarray,currtok);
        currtok_ptr = 0;
      break;
      case PUSH:
        currtok[currtok_ptr] = c;
        currtok_ptr += 1;
      break;
      case NIL :
        /* skip */
      break;
      case DASH:
        currtok[currtok_ptr] = '_';
        currtok_ptr += 1;
      break;
      case DP  :
        currtok[currtok_ptr] = '_';
        currtok_ptr += 1;
        currtok[currtok_ptr] = c;
        currtok_ptr += 1;
      break;
      case UNKN:
        add_to_string_array(tokarray,"?");
        currtok_ptr = 0;
      break;
      default: my_error("ljdnlkjs"); break;
    }

    state = next;
    s_ptr += 1;
  }

  AM_FREE_ARRAY(currtok,char,currtok_size);
  return(tokarray);
}

/* Returns a string array of values for the given key, from the CL.
   Usually the sa will just have one entry, but
   for quoted key values, splits it into the string array, also,
   [ and ] and "" act as generalized quotes for this purpose. */
string_array *mk_string_array_from_args(char *key,int argc,char** argv)
{
  string_array* returner;

  returner = mk_maybe_string_array_from_args(key, argc, argv);
  if(returner == NULL)
    returner = mk_string_array(0);

  return returner;
}


string_array *mk_maybe_string_array_from_args(char *key,int argc,char** argv)
{
  int idx = index_of_arg(key,argc,argv);
  string_array *sa;

  if ( idx < 0 || idx >= argc-1 )
    return NULL;

  else
  {
    char *token = argv[idx+1];
    bool many = token[0] == '[';
    if ( !many )
      sa = mk_string_array_from_set_notation(token);
    else
    {
      string_array *collect = mk_string_array_1(token);
      char *s;
      bool finished = FALSE;
      int i;
      for ( i = idx+1 ; i < argc && !finished ; i++ )
      {
	char *next = argv[i];
	int size = (int) strlen(next);
	add_to_string_array(collect,next);
	finished = size > 0 && next[size-1] == ']';
      }
      s = mk_string_from_string_array(collect);
      sa = mk_string_array_from_set_notation(s);
      free_string_array(collect);
      free_string(s);
    }
  }
  return sa;
}

string_array *mk_string_array_from_argc_argv(int argc,char *argv[])
{
  string_array *sa = mk_string_array(argc);
  int i;
  for ( i = 0 ; i < argc ; i++ )
    string_array_set(sa,i,argv[i]);
  return(sa);
}

/* Makes and returns a string array of given size in which
   every entry contains a copy of the string stored in value */
string_array *mk_constant_string_array(int size,char *value)
{
  string_array *sa = mk_string_array(size);
  int i;
  for ( i = 0 ; i < size ; i++ )
    string_array_set(sa,i,value);
  return sa;
}

