/******************************************************************************
 *
 * 9998sketch/expr.c
 *
 * (c) Eugene K. Ressler, Jr. 2004, 2005
 *
 * A program related to the book
 *
 * Fundamentals of Virtual World Simulation
 * by Eugene K. Ressler, Jr.
 *
 * No warranty of correctness or capability of any kind is expressed or implied.
 *
 * The author grants free use of this code for any purpose as long any text,
 * executable program, library file, or source code distributed to others 
 * and employing any portion of this code clearly cites the book named above.
 *
 ******************************************************************************/

#include <stdio.h>
#include <math.h>
#include "expr.h"
#include "error.h"

#define F "%.3f"

char *expr_val_type_str[] = {
  "float",
  "point",
  "vector",
  "transform",
};

// set expression value to given type and value
void set_float(EXPR_VAL *r, FLOAT val)
{
  r->tag = E_FLOAT;
  r->val.flt = val;
}

void print_float(FILE *f, EXPR_VAL *val)
{
  fprintf(f, F, val->val.flt);
}

void set_point(EXPR_VAL *r, POINT_3D val)
{
  r->tag = E_POINT;
  copy_pt_3d(r->val.pt, val);
}

void print_point(FILE *f, EXPR_VAL *val)
{
  FLOAT *p = val->val.pt;
  fprintf(f, "("F","F","F")" , p[X], p[Y], p[Z]);
}

void set_vector(EXPR_VAL *r, VECTOR_3D val)
{
  r->tag = E_VECTOR;
  copy_vec_3d(r->val.vec, val);
}

void print_vector(FILE *f, EXPR_VAL *val)
{
  FLOAT *v = val->val.vec;
  fprintf(f, "["F","F","F"]" , v[X], v[Y], v[Z]);
}

void set_transform(EXPR_VAL *r, TRANSFORM val)
{
  r->tag = E_TRANSFORM;
  copy_transform(r->val.xf, val);
}

void print_transform(FILE *f, EXPR_VAL *val)
{
  FLOAT *xf = val->val.xf;
  int i, j;

  fprintf(f, "[");
  for (i = 0; i < 4; i++) {
    fprintf(f, "[");
    for (j = 0; j < 16; j += 4)
      fprintf(f, "%s"F, 
        (j == 0) ? "" : ",", 
        xf[i + j]);
    fprintf(f, "]");
  }
  fprintf(f, "]");
}

// coerce an expression value to given type 
// generate error message if it can't be done
void coerce_to_float(EXPR_VAL *r, FLOAT *val, SRC_LINE line)
{
  if (r->tag == E_FLOAT) {
    *val = r->val.flt;
  }
  else {
    *val = 0;
    err(line, "expected float, found %s", expr_val_type_str[r->tag]);
  }
}

void coerce_to_point(EXPR_VAL *r, POINT_3D val, SRC_LINE line)
{
  if (r->tag == E_POINT) {
    copy_pt_3d(val, r->val.pt);
  }
  else {
    val[X] = val[Y] = val[Z] = 0;
    err(line, "expected point, found %s", expr_val_type_str[r->tag]);
  }
}

void coerce_to_vector(EXPR_VAL *r, VECTOR_3D val, SRC_LINE line)
{
  if (r->tag == E_VECTOR) {
    copy_vec_3d(val, r->val.vec);
  }
  else {
    val[X] = val[Y] = val[Z] = 0;
    err(line, "expected vector, found %s", expr_val_type_str[r->tag]);
  }
}

void coerce_to_transform(EXPR_VAL *r, TRANSFORM val, SRC_LINE line)
{
  if (r->tag == E_TRANSFORM) {
    copy_transform(val, r->val.xf);
  }
  else {
    set_ident(val);
    err(line, "expected transform, found %s", expr_val_type_str[r->tag]);
  }
}

typedef void (*PRINT_FUNC)(FILE *, EXPR_VAL *);

static PRINT_FUNC print_expr_val_tbl[] = {
  print_float,
  print_point,
  print_vector,
  print_transform,
};

void print_expr_val(FILE *f, EXPR_VAL *r)
{
  (*print_expr_val_tbl[r->tag])(f, r);
}

#define HASH(A, B) (((A) << 2) | (B))

void do_add(EXPR_VAL *r, EXPR_VAL *a, EXPR_VAL *b, SRC_LINE line)
{
  switch ( HASH(a->tag, b->tag) ) {
  case HASH(E_FLOAT, E_FLOAT): 
    set_float(r, a->val.flt + b->val.flt);
    break;
  case HASH(E_POINT, E_VECTOR):
    r->tag = E_POINT;
    add_vec_to_pt_3d(r->val.pt, a->val.pt, b->val.vec);
    break;
  case HASH(E_VECTOR, E_POINT):
    r->tag = E_POINT;
    add_vec_to_pt_3d(r->val.pt, b->val.pt, a->val.vec);
    break;
  case HASH(E_VECTOR, E_VECTOR):
    r->tag = E_VECTOR;
    add_vecs_3d(r->val.vec, a->val.vec, b->val.vec);
    break;
  default:
    err(line, "operands of + (types %s and %s) cannot be added",
      expr_val_type_str[a->tag], expr_val_type_str[b->tag]);
    set_float(r, 0);
    break;
  }
}

void do_sub(EXPR_VAL *r, EXPR_VAL *a, EXPR_VAL *b, SRC_LINE line)
{
  switch ( HASH(a->tag, b->tag) ) {
  case HASH(E_FLOAT, E_FLOAT): 
    set_float(r, a->val.flt - b->val.flt);
    break;
  case HASH(E_POINT, E_POINT): 
    r->tag = E_VECTOR;
    sub_pts_3d(r->val.vec, a->val.pt, b->val.pt);
    break;
  case HASH(E_POINT, E_VECTOR): 
    r->tag = E_POINT;
    add_scaled_vec_to_pt_3d(r->val.pt, a->val.pt, b->val.vec, -1);
    break;
  case HASH(E_VECTOR, E_VECTOR): 
    r->tag = E_VECTOR;
    sub_vecs_3d(r->val.vec, a->val.vec, b->val.vec);
    break;
  default:
    err(line, "operands of - (types %s and %s) cannot be subtracted",
      expr_val_type_str[a->tag], expr_val_type_str[b->tag]);
    set_float(r, 0);
    break;
  }
}

void do_mul(EXPR_VAL *r, EXPR_VAL *a, EXPR_VAL *b, SRC_LINE line)
{
  switch ( HASH(a->tag, b->tag) ) {
  case HASH(E_FLOAT, E_FLOAT): 
    set_float(r, a->val.flt * b->val.flt);
    break;
  case HASH(E_VECTOR, E_FLOAT): 
    r->tag = E_VECTOR;
    scale_vec_3d(r->val.vec, a->val.vec, b->val.flt); 
    break;
  case HASH(E_FLOAT, E_VECTOR): 
    r->tag = E_VECTOR;
    scale_vec_3d(r->val.vec, b->val.vec, a->val.flt); 
    break;
  case HASH(E_VECTOR, E_VECTOR): 
    r->tag = E_VECTOR;
    cross(r->val.vec, a->val.vec, b->val.vec);
    break;
  case HASH(E_TRANSFORM, E_TRANSFORM): 
    r->tag = E_TRANSFORM;
    compose(r->val.xf, a->val.xf, b->val.xf);
    break;
  case HASH(E_TRANSFORM, E_POINT): 
    r->tag = E_POINT;
    transform_pt_3d(r->val.pt, a->val.xf, b->val.pt);
    break;
  case HASH(E_TRANSFORM, E_VECTOR): 
    r->tag = E_VECTOR;
    transform_vec_3d(r->val.vec, a->val.xf, b->val.vec);
    break;
  default:
    err(line, "operands of * (types %s and %s) cannot be multiplied",
      expr_val_type_str[a->tag], expr_val_type_str[b->tag]);
    set_float(r, 0);
    break;
  }
}

void do_thn(EXPR_VAL *r, EXPR_VAL *a, EXPR_VAL *b, SRC_LINE line)
{
  switch ( HASH(a->tag, b->tag) ) {
  case HASH(E_TRANSFORM, E_TRANSFORM): 
    r->tag = E_TRANSFORM;
    compose(r->val.xf, b->val.xf, a->val.xf);
    break;
  case HASH(E_POINT, E_TRANSFORM): 
    r->tag = E_POINT;
    transform_pt_3d(r->val.pt, b->val.xf, a->val.pt);
    break;
  case HASH(E_VECTOR, E_TRANSFORM): 
    r->tag = E_VECTOR;
    transform_vec_3d(r->val.vec, b->val.xf, a->val.vec);
    break;
  default:
    err(line, "operands of 'then' (types %s and %s) cannot be multiplied",
      expr_val_type_str[a->tag], expr_val_type_str[b->tag]);
    set_float(r, 0);
    break;
  }
}

static FLOAT safe_dvd(FLOAT a, FLOAT b, SRC_LINE line)
{
  if (-FLOAT_EPS < b && b < FLOAT_EPS) {
    err(line, "attempt to divide " F " by zero", a);
    return 0;
  }
  return a / b;
}

void do_dvd(EXPR_VAL *r, EXPR_VAL *a, EXPR_VAL *b, SRC_LINE line)
{
  switch ( HASH(a->tag, b->tag) ) {
  case HASH(E_FLOAT, E_FLOAT):
    set_float(r, safe_dvd(a->val.flt, b->val.flt, line));
    break;
  case HASH(E_VECTOR, E_FLOAT): 
    r->tag = E_VECTOR;
    scale_vec_3d(r->val.vec, a->val.vec, safe_dvd(1, b->val.flt, line)); 
    break;
  case HASH(E_FLOAT, E_VECTOR): 
    r->tag = E_VECTOR;
    scale_vec_3d(r->val.vec, b->val.vec, safe_dvd(1, a->val.flt, line)); 
    break;
  default:
    err(line, "operands of / (types %s and %s) cannot be divided",
      expr_val_type_str[a->tag], expr_val_type_str[b->tag]);
    set_float(r, 0);
    break;
  }
}

void do_dot(EXPR_VAL *r, EXPR_VAL *a, EXPR_VAL *b, SRC_LINE line)
{
  switch ( HASH(a->tag, b->tag) ) {
  case HASH(E_VECTOR, E_VECTOR): 
    r->tag = E_FLOAT;
    r->val.flt = dot_3d(a->val.vec, b->val.vec);
    break;
  case HASH(E_FLOAT, E_FLOAT): 
  case HASH(E_VECTOR, E_FLOAT): 
  case HASH(E_FLOAT, E_VECTOR): 
  case HASH(E_TRANSFORM, E_TRANSFORM): 
  case HASH(E_TRANSFORM, E_POINT): 
  case HASH(E_TRANSFORM, E_VECTOR): 
    do_mul(r, a, b, line);
    break;
  default:
    err(line, "operands of dot (types %s and %s) cannot be multiplied",
      expr_val_type_str[a->tag], expr_val_type_str[b->tag]);
    set_float(r, 0);
    break;
  }
}

void do_index(EXPR_VAL *r, EXPR_VAL *a, int index, SRC_LINE line)
{
  switch(a->tag) {
  case E_VECTOR:
    set_float(r, a->val.vec[index]);
    break;
  case E_POINT:
    set_float(r, a->val.pt[index]);
    break;
  default:
    err(line, "operand of 'index is a %s and should be a point or a vector",
      expr_val_type_str[a->tag]);
    set_float(r, 0);
    break;
  }
}

void do_inverse(TRANSFORM inv, TRANSFORM xf, SRC_LINE line)
{
  FLOAT det;
  invert(inv, &det, xf, 1e-4);
  if (det == 0) {
    err(line, "inverse of singular transform");
    set_ident(inv);
  }
}

// put a^n into r;  r and a can't both be the same storage
// exploits a^(2n) = (a^n)^2 to reduce work
void do_transform_power(TRANSFORM r, TRANSFORM a, int n, SRC_LINE line)
{
  if (n < 0) {
    TRANSFORM inv;
    do_inverse(inv, a, line);
    do_transform_power(r, inv, -n, line);
  }
  else if (n == 0) {
    set_ident(r);
  }
  else {
    int m = (int)bit(30);
    while ((m & n) == 0)
      m >>= 1;
    copy_transform(r, a);
    for (m >>= 1; m; m >>= 1) {
      compose(r, r, r);
      if (m & n)
        compose(r, r, a);
    }
  }
}

int to_integer(FLOAT x, int *n)
{
  double frac_part, int_part;

  frac_part = modf(x, &int_part);
  if (-1e9 <= int_part && int_part <= 1e9) {
    *n = (int)int_part;
    return 1;
  }
  return 0;
}

void do_pwr(EXPR_VAL *r, EXPR_VAL *a, EXPR_VAL *b, SRC_LINE line)
{
  TRANSFORM xf_pwr;
  int n;

  switch ( HASH(a->tag, b->tag) ) {
  case HASH(E_FLOAT, E_FLOAT): 
    set_float(r, pow(a->val.flt, b->val.flt));
    break;
  case HASH(E_TRANSFORM, E_FLOAT):
    if (to_integer(b->val.flt, &n)) {
      do_transform_power(xf_pwr, a->val.xf, n, line);
    }
    else {
      err(line, "transform power out of domain (integer -1e9..1e9)");
      set_ident(xf_pwr);
    }
    set_transform(r, xf_pwr);
    break;
  default:
    err(line, "operands of ^ (types %s and %s) must be type float",
      expr_val_type_str[a->tag], expr_val_type_str[b->tag]);
    set_float(r, 0);
    break;
  }
}

void do_mag(EXPR_VAL *r, EXPR_VAL *a, SRC_LINE line)
{
  switch (a->tag) {
  case E_FLOAT:
    set_float(r, a->val.flt >= 0 ? a->val.flt : -a->val.flt);
    break;
  case E_VECTOR:
    set_float(r, length_vec_3d(a->val.vec));
    break;
  default:
    err(line, "operand of magnitude operator (type %s) must be vector",
      expr_val_type_str[a->tag]);
    *r = *a;
    break;
  }
}

void do_neg(EXPR_VAL *r, EXPR_VAL *a, SRC_LINE line)
{
  switch (a->tag) {
  case E_FLOAT:
    set_float(r, -a->val.flt);
    break;
  case E_VECTOR:
    r->tag = E_VECTOR;
    negate_vec_3d(r->val.vec, a->val.vec);
    break;
  default:
    err(line, "operand of unary minus (type %s) cannot be negated",
      expr_val_type_str[a->tag]);
    *r = *a;
    break;
  }
}

void do_unit(EXPR_VAL *r, EXPR_VAL *a, SRC_LINE line)
{
  if (a->tag == E_VECTOR) { 
    r->tag = E_VECTOR;
    find_unit_vec_3d(r->val.vec, a->val.vec);
  }
  else {
    static VECTOR_3D k = { 0, 0, 1 };
    err(line, "operand of unit operator (type %s) must be vector",
      expr_val_type_str[a->tag]);
    set_vector(r, k);
  }
}

void do_sqrt(EXPR_VAL *r, EXPR_VAL *a, SRC_LINE line)
{
  switch (a->tag) {
  case E_FLOAT:
    if (a->val.flt < 0)
      err(line, "square root of negative number");
    set_float(r, sqrt(a->val.flt));
    break;
  default:
    err(line, "operand of sqrt (type %s) must be float",
      expr_val_type_str[a->tag]);
    break;
  }
}

void do_sin(EXPR_VAL *r, EXPR_VAL *a, SRC_LINE line)
{
  switch (a->tag) {
  case E_FLOAT:
    set_float(r, sin((PI / 180) * a->val.flt));
    break;
  default:
    err(line, "operand of sin (type %s) must be float",
      expr_val_type_str[a->tag]);
    break;
  }
}

void do_cos(EXPR_VAL *r, EXPR_VAL *a, SRC_LINE line)
{
  switch (a->tag) {
  case E_FLOAT:
    set_float(r, cos((PI / 180) * a->val.flt));
    break;
  default:
    err(line, "operand of cos (type %s) must be float",
      expr_val_type_str[a->tag]);
    break;
  }
}

void do_atan2(EXPR_VAL *r, EXPR_VAL *a, EXPR_VAL *b, SRC_LINE line)
{
 switch ( HASH(a->tag, b->tag) ) {
  case HASH(E_FLOAT, E_FLOAT): 
     set_float(r, (180 / PI) * atan2(a->val.flt, b->val.flt));
    break;
  default:
    err(line, "operands of atan2 (types %s, %s) must be float",
      expr_val_type_str[a->tag], expr_val_type_str[b->tag]);
    break;
  }
}
