/*
   File:        query.c
   Author:      Andrew W. Moore
   Created:     Wed Dec 30 05:28:25 EST 1998
   Description: Query for symbolic dsets

   Copyright 1998, Schenley Park Research, Inc
*/

#include "query.h"

/* Private prototypes */
static query *mk_query_from_simplified_sa(datset *ds,string_array *sa, char **r_errmess);
static char *mk_replace_string(char *string,char oldc,char newc); /* FIXME This really should be in utils/amstr */
/* Private headers for unused functions */
#ifdef AUTON_UNUSED_CODE
#endif /* AUTON_UNUSED_CODE */

query *mk_empty_query(void)
{
  query *q = AM_MALLOC(query);
  q -> attnums = mk_ivec(0);
  q -> values = mk_ivec(0);
  return q;
}

query *mk_copy_query(query *q)
{
  query *newq = AM_MALLOC(query);
  check_attnums_increasing(q->attnums);
  newq -> attnums = mk_copy_ivec(q->attnums);
  newq -> values = mk_copy_ivec(q->values);
  return newq;
}

void free_query(query *q)
{
  check_attnums_increasing(q->attnums);
  free_ivec(q->attnums);
  free_ivec(q->values);
  AM_FREE(q,query);
}

int slow_query_size(query *q)
{
  return ivec_size(q->attnums);
}

int slow_query_attnum_ref(query *q,int idx)
{
  return ivec_ref(q->attnums,idx);
}

int slow_query_value_ref(query *q,int idx)
{
  return ivec_ref(q->values,idx);
}

/* Adds "attnum == value" to the query, maintaining the invariant that
   the attnums are stored in ascending order with no duplicates. If attnum
   is already in the query, its corresponding value is overwritten. Else
   the query size is increased by one. */
void add_to_query(query *q,int attnum,int value)
{
  int i = 0;
  int size = query_size(q);

  check_attnums_increasing(q->attnums);

  while ( i < size && attnum > query_attnum_ref(q,i) )
    i += 1;

  if ( i < size && attnum == query_attnum_ref(q,i) )
    ivec_set(q->values,i,value);
  else
  {
    ivec_insert(q->attnums,i,attnum);
    ivec_insert(q->values,i,value);
  }
  check_attnums_increasing(q->attnums);
}


/* Makes a query from the given attnums
   and values. It's an error to call if attnums are not sorted. 
   Important warning... if you want to make an query from unsorted
   atts, you should use mk_query(...).

   Use this if you want some slight extra efficiency over
   mk_query(...) in cases where you happen to know attnums
   will be sorted.  */
query *mk_query_from_sorted_attnums(ivec *attnums,ivec *values)
{
  query *q = AM_MALLOC(query);
  check_attnums_increasing(attnums);
  q -> attnums = mk_copy_ivec(attnums);
  q -> values = mk_copy_ivec(values);
  return q;
}

/* It's okay if attnums is out of order... this reorders them.
   If attnums contains duplicates, then the rightmost occurence
   is the value that is remembered */  
query *mk_query(ivec *attnums,ivec *values)
{
  query *q = mk_empty_query();
  int i;
  for ( i = 0 ; i < ivec_size(attnums) ; i++ )
    add_to_query(q,ivec_ref(attnums,i),ivec_ref(values,i));
  return q;
}

/* Assumes string array is a sequence 
   <attname1> <value1> <attname2> <value2> ... <attnameN> <valnameN> */
static query *mk_query_from_simplified_sa(datset *ds,string_array *sa,
				   char **r_errmess)
{
  query *q = mk_empty_query();
  int size = string_array_size(sa);
  int i;

  *r_errmess = NULL;
  if ( size % 2 != 0 )
    *r_errmess = mk_printf("I need a sequence of attribute value pairs");

  for ( i = 0 ; *r_errmess == NULL && i < size ; i += 2 )
  {
    char *attname = string_array_ref(sa,i);
    int attnum = ds_name_to_attnum(ds,attname);
    int value = -1;
    if ( attnum < 0 )
      *r_errmess = mk_printf("No attribute named %s",attname);
    
    if ( *r_errmess == NULL )
    {
      char *valname = string_array_ref(sa,i+1);
      value = ds_attnum_name_to_value(ds,attnum,valname);
      if ( value < 0 )
	*r_errmess = mk_printf("Attribute %s has no value called %s",
			       attname,valname);
    }

    if ( *r_errmess == NULL )
      add_to_query(q,attnum,value);
  }

  if ( *r_errmess != NULL )
  {
    free_query(q);
    q = NULL;
  }

  return q;
}

static char *mk_replace_string(char *string,char oldc,char newc)
{
  int i;
  char *copy_string = mk_copy_string(string);

  for ( i = 0 ; copy_string[i] != '\0' ; i++ )
  {
    if ( copy_string[i] == oldc )
      copy_string[i] = newc;
  }
  return copy_string;
}

/* Syntax: A bunch of <attribute> <value> pairs, with (optionally)
   equal-signs and commas as separators. Examples of legal queries..

     "gender=female,state=pa"
     "  gender female     state== pa    , agegroup = young"
     */
query *mk_query_from_string(datset *ds,char *string,char **r_errmess)
{
  char *string2 = mk_replace_string(string,',',' ');
  char *string3 = mk_replace_string(string,'=',' ');
  string_array *sa = mk_broken_string(string3);
  query *q = mk_query_from_simplified_sa(ds,sa,r_errmess);
  free_string_array(sa);
  free_string(string3);
  free_string(string2);
  return q;
}

/* Syntax as above */
query *mk_query_from_string_array(datset *ds,string_array *sa,char **r_errmess)
{
  char *string = mk_string_from_string_array(sa);
  query *q = mk_query_from_string(ds,string,r_errmess);
  free_string(string);
  return q;
}

/* Uses attribute names unless ds is NULL in which case
   just draws raw integers */
char *mk_string_from_query(datset *ds,query *q)
{
  char *s = mk_copy_string("");
  int i;

  for ( i = 0 ; i < query_size(q) ; i++ )
  {
    int attnum = query_attnum_ref(q,i);
    int value = query_value_ref(q,i);
    char *attname = (ds == NULL) ? mk_printf("attnum%d",attnum) :
                             mk_copy_string(ds_attnum_to_name(ds,attnum));
    char *valname = (ds == NULL) ? mk_printf("value%d",value) :
                   mk_copy_string(ds_attnum_value_to_name(ds,attnum,value));
    char *stemp = mk_printf("%s%s%s=%s",s,(i==0)?"":",",attname,valname);
    free_string(s);
    free_string(attname);
    free_string(valname);
    s = stemp;
  }
  return s;
}

/* Uses attribute names unless ds is NULL in which case
   just draws raw integers */
void fprintf_query_basic(FILE *s,char *m1,char *equals,
                         datset *ds,query *q,char *m2)
{
  char *string = mk_string_from_query(ds,q);
  fprintf(s,"%s%s%s%s",m1,equals,(query_size(q)==0)?"<empty query>":string,m2);
  free_string(string);
}

void fprintf_query(FILE *s,char *m1,query *q,char *m2)
{
  fprintf_query_basic(s,m1," = ",NULL,q,m2);
  check_attnums_increasing(q->attnums);
}

void pquery(datset *ds,query *q)
{
  fprintf_query_basic(stdout,"q"," = ",ds,q,"\n");
}

void explain_query(datset *ds,query *q)
{
  fprintf_query_basic(stdout,"","",ds,q,"\n");
  check_attnums_increasing(q->attnums);
}

int query_attnum_to_index(query *q,int attnum)
{
  return find_index_in_ivec(q->attnums,attnum);
}

bool query_contains_attnum(query *q,int attnum)
{
  return query_attnum_to_index(q,attnum) >= 0;
}

void query_remove_index(query *q,int idx)
{
  ivec_remove(q->attnums,idx);
  ivec_remove(q->values,idx);
  check_attnums_increasing(q->attnums);
}

void query_remove_attnum(query *q,int attnum)
{
  int idx = query_attnum_to_index(q,attnum);
  if ( idx >= 0 )
    query_remove_index(q,idx);
}

void query_unite_second_into_first(query *q1,query *q2)
{
  int i;
  for ( i = 0 ; i < query_size(q2) ; i++ )
    add_to_query(q1,query_attnum_ref(q2,i),query_value_ref(q2,i));
}

void query_remove_attnums(query *q,ivec *attnums)
{
  int i;
  for ( i = 0 ; i < ivec_size(attnums) ; i++ )
    query_remove_attnum(q,ivec_ref(attnums,i));
}

ivec *query_attnums(query *q)
{
  return q->attnums;
}

ivec *query_values(query *q)
{
  return q->values;
}

/* Returns TRUE if and only if "a" refers to a strict
   subset of the attribute/value pairs in "b". */
bool query_strictly_more_general(query *a,query *b)
{
  int asize = query_size(a);
  int bsize = query_size(b);
  bool result = asize < bsize;
  int ai = 0;
  int bi = 0;
  while ( result && ai < asize )
  {
    int aval = ivec_ref(a->attnums,ai);
    while ( bi < bsize && ivec_ref(b->attnums,bi) < aval )
      bi += 1;
    if ( bi >= bsize || 
         ivec_ref(b->attnums,bi) > aval ||
         ivec_ref(a->values,ai) != ivec_ref(b->values,bi) )
      result = FALSE;
    else
      ai += 1;
  }
  return result;
}

bool ivec_strictly_increasing(ivec *iv)
{
  bool result = TRUE;
  int i;
  for ( i = 0 ; result && i < ivec_size(iv)-1 ; i++ )
    result = ivec_ref(iv,i) < ivec_ref(iv,i+1);
  return(result);
}

void slow_check_attnums_increasing(ivec *attnums)
{
  if ( !ivec_strictly_increasing(attnums) )
  {
    fprintf_ivec(stderr,"attnums",attnums,"\n");
    my_error("You passed me a set of attributes that aren't strictly increasing");
  }
}

