#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include "error.h"
#include "tuple.h"
#include "predicate.h"
#include "iterator.h"
#include "scan_iterator.h"

bool_expr *make_eq_expr(int arg1, int arg2){
  bool_expr *result;
  
  result = (bool_expr *) calloc(1, sizeof(bool_expr));
  result->bool_type = EQ_EXPR;
  result->expr_body.r_expr.arg1 = arg1;
  result->expr_body.r_expr.arg2 = arg2; 
  return result;
}

bool_expr *make_neq_expr(int arg1, int arg2){
  bool_expr *result;
  
  result = (bool_expr *) calloc(1, sizeof(bool_expr));
  result->bool_type = NEQ_EXPR;
  result->expr_body.r_expr.arg1 = arg1;
  result->expr_body.r_expr.arg2 = arg2; 
  return result;
}

bool_expr *make_lt_expr(int arg1, int arg2){
  bool_expr *result;
  
  result = (bool_expr *) calloc(1, sizeof(bool_expr));
  result->bool_type = LT_EXPR;
  result->expr_body.r_expr.arg1 = arg1;
  result->expr_body.r_expr.arg2 = arg2; 
  return result;
}

bool_expr *make_lte_expr(int arg1, int arg2){
  bool_expr *result;
  
  result = (bool_expr *) calloc(1, sizeof(bool_expr));
  result->bool_type = LTE_EXPR;
  result->expr_body.r_expr.arg1 = arg1;
  result->expr_body.r_expr.arg2 = arg2; 
  return result;
}

bool_expr *make_gt_expr(int arg1, int arg2){
  bool_expr *result;
  
  result = (bool_expr *) calloc(1, sizeof(bool_expr));
  result->bool_type = GT_EXPR;
  result->expr_body.r_expr.arg1 = arg1;
  result->expr_body.r_expr.arg2 = arg2; 
  return result;
}

bool_expr *make_gte_expr(int arg1, int arg2){
  bool_expr *result;
  
  result = (bool_expr *) calloc(1, sizeof(bool_expr));
  result->bool_type = GTE_EXPR;
  result->expr_body.r_expr.arg1 = arg1;
  result->expr_body.r_expr.arg2 = arg2; 
  return result;
}

bool_expr *make_and_expr(bool_expr *left, bool_expr *right){
  bool_expr *result;
  
  result = (bool_expr *) calloc(1, sizeof(bool_expr));
  result->bool_type = AND_EXPR;
  result->expr_body.l_expr.left = left;
  result->expr_body.l_expr.right = right;
  return result;
}

bool_expr *make_or_expr(bool_expr *left, bool_expr *right){
  bool_expr *result;
  
  result = (bool_expr *) calloc(1, sizeof(bool_expr));
  result->bool_type = OR_EXPR;
  result->expr_body.l_expr.left = left;
  result->expr_body.l_expr.right = right;
  return result;
}

bool_expr *make_not_expr(bool_expr *left){
  bool_expr *result;
  
  result = (bool_expr *) calloc(1, sizeof(bool_expr));
  result->bool_type = NOT_EXPR;
  result->expr_body.l_expr.left = left;
  result->expr_body.l_expr.right = NULL;
  return result;
}

void get_arg_attributes(bool_expr *expr, tuple* arg_tuple, tuple *cons,
			attribute& attr1, attribute& attr2){
  int arg1, arg2;
  
  // This method assumes that expr points to a relational 
  // expression: ==, !=, >, >=, < , <=
  // Otherwise, unknown behavior will occur.

  arg1 = expr->expr_body.r_expr.arg1;
  arg2 = expr->expr_body.r_expr.arg2;
 
  // sanity checks, these cannot be NULL
  assert(expr != NULL);
  assert(arg_tuple != NULL);


  if (cons == NULL){
    // no constants are present

    // make sure args are in within bounds
    assert((arg1 >= 0) && (arg1 < arg_tuple->num_attrs));
    assert((arg2 >= 0) && (arg2 < arg_tuple->num_attrs));
   
    attr1 = arg_tuple->attr_list[arg1];
    attr2 = arg_tuple->attr_list[arg2];
  }
  else {
    // some constants are present
    assert((arg1 >= 0) && (arg1 < (arg_tuple->num_attrs + cons->num_attrs)));
    assert((arg2 >= 0) && (arg2 < (arg_tuple->num_attrs + cons->num_attrs)));
   
    // GET FIRST ARGUMENT
    if (arg1 >= arg_tuple->num_attrs){
      // this must be a constant, so remove the offset added by
      // having the arg_tuple->num_attrs
      arg1 -= arg_tuple->num_attrs;
      attr1 = cons->attr_list[arg1];
    }
    else {
      // argument is an attribute from tuple read from DB
      attr1 = arg_tuple->attr_list[arg1];
    }

    // GET SECOND ARGUMENT
    if (arg2 >= arg_tuple->num_attrs){
      // this must be a constant, so remove the offset added by
      // having the arg_tuple->num_attrs
      arg2 -= arg_tuple->num_attrs;
      attr2 = cons->attr_list[arg2];
    }
    else {
      // argument is an attribute from tuple read from DB
      attr2 = arg_tuple->attr_list[arg2];
    }
  }
}


bool eval_eq_expr(bool_expr *eq_expr, tuple *arg_tuple, tuple *cons){
  char *str1, *str2;
  attribute attr1, attr2;
  int res = 0;
  // initialize the space for the arguments
  memset((void *)&attr1, 0, sizeof(attribute));
  memset((void *)&attr2, 0, sizeof(attribute));
  
  // extract the arguments
  get_arg_attributes(eq_expr, arg_tuple, cons, attr1, attr2);
  
  // make sure they are comparable
  assert(attr1.attr_type == attr2.attr_type);
  
  if (attr1.attr_type == INT_TYPE){
    return attr1.attr_val.int_val == attr2.attr_val.int_val;
  }
  else if (attr1.attr_type == DOUBLE_TYPE){
    return attr1.attr_val.double_val == attr2.attr_val.double_val;
  }
  else {
    // get the string 1
    str1 = attr1.attr_val.char_val.str;
    
    // get the string 2
    str2 = attr2.attr_val.char_val.str;
    
    // determine if they are the same
    res = strcmp(str1, str2);
    return res == 0;
  }
}

bool eval_neq_expr(bool_expr *neq_expr, tuple *arg_tuple, tuple *cons){
  
  return !eval_eq_expr(neq_expr, arg_tuple, cons);
}

bool eval_lt_expr(bool_expr *lt_expr, tuple *arg_tuple, tuple *cons){
  char *str1, *str2;
  attribute attr1, attr2;
  int res = 0;
  
  // initialize the space for the arguments
  memset((void *)&attr1, 0, sizeof(attribute));
  memset((void *)&attr2, 0, sizeof(attribute));
  
  // extract the arguments
  get_arg_attributes(lt_expr, arg_tuple, cons, attr1, attr2);
  
  // make sure they are comparable
  assert(attr1.attr_type == attr2.attr_type);
  
  if (attr1.attr_type == INT_TYPE){
    return attr1.attr_val.int_val < attr2.attr_val.int_val;
  }
  else if (attr1.attr_type == DOUBLE_TYPE){
    return attr1.attr_val.double_val < attr2.attr_val.double_val;
  }
  else {
    // get the string 1
    str1 = attr1.attr_val.char_val.str;
    
    // get the string 2
    str2 = attr2.attr_val.char_val.str;
    
    // determine if they are the same
    res = strcmp(str1, str2);
    return res < 0;
  }
}

bool eval_lte_expr(bool_expr *lte_expr, tuple *arg_tuple, tuple *cons){
  
  return (eval_eq_expr(lte_expr, arg_tuple, cons) ||
	  eval_lt_expr(lte_expr, arg_tuple, cons));
}

bool eval_gt_expr(bool_expr *gt_expr, tuple *arg_tuple, tuple *cons){
  
  // a > b iff a <= b is false.
  return !eval_lte_expr(gt_expr, arg_tuple, cons);
}

bool eval_gte_expr(bool_expr *gte_expr, tuple *arg_tuple, tuple *cons){
  
  // a >= b iff a = b or a > b
  return (eval_eq_expr(gte_expr, arg_tuple, cons) ||
	  eval_gt_expr(gte_expr, arg_tuple, cons));
}

bool eval_and_expr(bool_expr *expr, tuple *src, tuple *cons){
  return eval_selection(expr->expr_body.l_expr.left, src, cons) &&
    eval_selection(expr->expr_body.l_expr.right, src, cons);
}

bool eval_or_expr(bool_expr *expr, tuple *src, tuple *cons){
  return eval_selection(expr->expr_body.l_expr.left, src, cons) ||
    eval_selection(expr->expr_body.l_expr.right, src, cons);
}

bool eval_not_expr(bool_expr *expr, tuple *src, tuple *cons){
  return !eval_selection(expr->expr_body.l_expr.left, src, cons);
}
