/*
   File:        constraint.c
   Author:      Andrew W. Moore
   Created:     Wed Jul 15 13:31:36 EDT 1998
   Description: Constraints language to specify subsets of rows

   Copyright 1998, Andrew Moore
*/

#include "constraint.h"

/* Constraint Syntax:

   <constraint> ::= all |
                    missing |
                    row < n |
                    row > n |
                    <symbolicattname> == <symbolicvalue> |
                    <realattname> < <number> |
                    <realattname> > <number> 

   A constraint can be evaluated on any requested row of the datset.

   An "all" constraint matches all rows

   A "missing" constraint is TRUE iff the row contains a missing value

   An == constraint evals to TRUE if and only if the value of the given
       attribute on the given row matches the specified value (it evals
       to false if the requested value is missing)

   A < (>) constraint evals to TRUE if and only if the value of the
       given attribute is strictly less than (greater than) the
       supplied number.
*/

int name_to_optype(char *name)
{
  int result = -1;
  if ( eq_string(name,"==") )
    result = EQUAL_CONTYPE;
  else if ( eq_string(name,"<") )
    result = LESS_CONTYPE;
  else if ( eq_string(name,">") )
    result = GREATER_CONTYPE;
  return result;
}

/* Parses the tokens in the string array according to the
   above syntax. If it fails to parse legally, returns NULL and
   sets *r_errmess to point to a NULL-terminated NEWLY-ALLOCATED
   string with a short error message (the caller must free this
   string with the free_string( ) function). 

   If it DOES successfully parse, sets *r_errmess to NULL, and returns
   the new constraint. */
constraint *mk_constraint_from_string_array(datset *ds,string_array *sa,
                                            char **r_errmess)
{
  constraint *cs = AM_MALLOC(constraint);
  int size = string_array_size(sa);
  *r_errmess = NULL;
  if ( size == 1 )
  {
    char *s = string_array_ref(sa,0);
    if ( caseless_eq_string(s,"all") )
      cs -> contype = ALL_CONTYPE;
    else if ( caseless_eq_string(s,"missing") )
      cs -> contype = MISSING_CONTYPE;
    else
      *r_errmess = mk_copy_string("If your constraint has only one token, it should be ALL or MISSING");
  }
  else if ( size == 3 && eq_string(string_array_ref(sa,0),"row") )
  {
    int optype = name_to_optype(string_array_ref(sa,1));
    int n = atoi(string_array_ref(sa,2));
    if ( optype == LESS_CONTYPE )
    {
      cs->contype = ROWLESS_CONTYPE;
      cs->real_thresh = (double)n;
    }
    else if ( optype == GREATER_CONTYPE )
    {
      cs->contype = ROWGREATER_CONTYPE;
      cs->real_thresh = (double)n;
    }
    else
      *r_errmess = mk_printf("row constraint should be row < <number> or row > <number>");
  }
  else if ( size == 3 )
  {
    char *attname = string_array_ref(sa,0);
    char *ctname = string_array_ref(sa,1);
    char *valname = string_array_ref(sa,2);
    cs->contype = name_to_optype(ctname);

    if ( cs->contype < 0 )
      *r_errmess = mk_printf("%s is not a legal operator. Use ==, < or >",ctname);

    if ( *r_errmess == NULL )
    {
      cs->attnum = ds_name_to_attnum(ds,attname);
      if ( cs->attnum < 0 )
        *r_errmess = mk_printf("%s is not a legal attribute name",attname);
    }

    if ( *r_errmess == NULL && ds_attnum_to_symbolicp(ds,cs->attnum) )
    {
      if ( cs->contype != EQUAL_CONTYPE )
        *r_errmess = mk_copy_string("For a symbolic attribute, must use == contype");

      if ( *r_errmess == NULL )
      {
        cs -> symbol_value = ds_attnum_name_to_value(ds,cs->attnum,valname);
        if ( cs -> symbol_value < 0 )
          *r_errmess = mk_printf("%s is not a valid value for attribute %s",valname,attname);
      }
    }

    if ( *r_errmess == NULL && !ds_attnum_to_symbolicp(ds,cs->attnum) )
    {
      if ( cs->contype != GREATER_CONTYPE && cs->contype != LESS_CONTYPE )
        *r_errmess = mk_copy_string("For a real attribute, must use > or < contype");

      if ( *r_errmess == NULL && !is_a_number(valname) )
        *r_errmess = mk_printf("%s is not a number, yet %s IS a numeric attribute",valname,attname);

      if ( *r_errmess == NULL )
        cs->real_thresh = atof(valname);
    }
  }
  else
    *r_errmess = mk_copy_string("Any constraint specificatin involves 1 or 3 tokens.");

  if ( *r_errmess != NULL )
  {
    free_constraint(cs);
    cs = NULL;
  }

  return cs;
}

/* Breaks the string into tokens and then parses according to the
   same convention as the above function */
constraint *mk_constraint_from_string(datset *ds,char *s,char **r_errmess)
{
  string_array *sa = mk_broken_string(s);
  constraint *cs = mk_constraint_from_string_array(ds,sa,r_errmess);
  free_string_array(sa);
  return cs;
}

/* MAKES a string which, if parsed, would re-produce the
   specified constriant. */
char *mk_string_from_constraint(datset *ds,constraint *cs)
{
  char buff[1000];
  if ( cs->contype == ALL_CONTYPE )
    sprintf(buff,"all");
  else if ( cs->contype == MISSING_CONTYPE )
    sprintf(buff,"missing");
  else if ( cs->contype == EQUAL_CONTYPE )
    sprintf(buff,"%s == %s",ds_attnum_to_name(ds,cs->attnum),
            ds_attnum_value_to_name(ds,cs->attnum,cs->symbol_value));
  else if ( cs->contype==LESS_CONTYPE || cs->contype == GREATER_CONTYPE )
  {
    char *x = (cs->contype==LESS_CONTYPE) ? "<" : ">";
    sprintf(buff,"%s %s %g",ds_attnum_to_name(ds,cs->attnum),x,cs->real_thresh);
  }
  else if ( cs->contype==ROWLESS_CONTYPE || cs->contype == ROWGREATER_CONTYPE )
  {
    char *x = (cs->contype==ROWLESS_CONTYPE) ? "<" : ">";
    sprintf(buff,"row %s %g",x,cs->real_thresh);
  }
  else
    my_error("Unknown contype");

  return mk_copy_string(buff);
}
   
/* MAKES a string_array which, if parsed, would re-produce the
   specified constriant. */
string_array *mk_string_array_from_constraint(datset *ds,constraint *cs)
{
  char *s = mk_string_from_constraint(ds,cs);
  string_array *sa = mk_broken_string(s);
  free_string(s);
  return sa;
}
 
/* Frees the constraint and all its contents */
void free_constraint(constraint *cs)
{
  AM_FREE(cs,constraint);
}

void fprintf_constraint(FILE *s,char *m1,datset *ds,constraint *cs,char *m2)
{
  char *string = mk_string_from_constraint(ds,cs);
  fprintf(s,"%s = %s%s",m1,string,m2);
  free_string(string);
}

/* Returns TRUE if and only if the specified row matches the
   constraint according to the rules at the top of the file. */
bool ds_row_matches_constraint(datset *ds,constraint *cs,int row)
{
  bool result = FALSE;
  if ( cs->contype == ALL_CONTYPE )
    result = TRUE;
  else if ( cs->contype == MISSING_CONTYPE )
    result = ds_row_to_num_missing(ds,row) > 0;
  else if ( cs->contype == EQUAL_CONTYPE )
    result = !ds_value_is_missing(ds,row,cs->attnum) &&
             cs->symbol_value == ds_symbol_ref(ds,row,cs->attnum);
  else if ( cs->contype==LESS_CONTYPE )
    result = !ds_value_is_missing(ds,row,cs->attnum) &&
             ds_real_ref(ds,row,cs->attnum) < cs->real_thresh;
  else if ( cs->contype==GREATER_CONTYPE )
    result = !ds_value_is_missing(ds,row,cs->attnum) &&
             ds_real_ref(ds,row,cs->attnum) > cs->real_thresh;
  else if ( cs->contype==ROWLESS_CONTYPE )
    result = row < (int)floor(0.5 + cs->real_thresh);
  else if ( cs->contype==ROWGREATER_CONTYPE )
    result = row > (int)floor(0.5 + cs->real_thresh);
  else
    my_error("Unknown contype");

  return result;
}
