/* expr.c,v 1.2 1995/04/08 19:52:54 explorer Exp */

/*
 * Copyright (C) 1989, 1991, Craig E. Kolb, Rod G. Bogart
 * All rights reserved.
 *
 * This software may be freely copied, modified, and redistributed
 * provided that this copyright notice is preserved on all copies.
 *
 * You may not distribute this software, in whole or in part, as part of
 * any commercial product without the express consent of the authors.
 * 
 * There is no warranty or other guarantee of fitness of this software
 * for any purpose.  It is provided solely "as is".
 *
 */

#include "common.h"

static Expr	*ExprCreate _PROTO((int, int));
static void	ExprFree _PROTO((Expr *));
static Expr	*ExprMalloc _PROTO((void));
static void	CheckMode _PROTO((Expr *, char *));
static Float	ModeEval _PROTO((Expr *, Float));

Expr	*TimeExpr = NULL,
	*FrameExpr = NULL,
	*DefaultMode = NULL;


Expr *
ExprFloatCreate(val, timevary)
     Float val;
     int timevary;
{
  Expr *res;
  
  res = ExprCreate(FLOAT_EXPR, timevary);
  res->value = val;
  return res;
}

Expr *
ExprReuseFloatCreate(val)
     Float val;
{
  Expr *res;
  
  /* this should get the expr from a magic list
   * that will reuse the exprs.
   * But not yet...
   */
  res = ExprCreate(FLOAT_EXPR, FALSE);
  res->value = val;
  return res;
}

Expr *
ExprAnimCreate(animval)
  AnimVal	*animval;
{
  Expr	*res;

  res = ExprCreate(FLOAT_EXPR, TRUE);
  res->animval = animval;
  return res;
}

Expr *
ExprMalloc()
{
  return (Expr *)Malloc(sizeof(Expr));
}

static
Expr *
ExprCreate(type, timevary)
     int type, timevary;
{
  Expr *res;
  
  res = ExprMalloc();
  res->type = type;
  res->timevary = timevary;
  res->symtab = FALSE;
  res->nparams = 0;
  res->params = (Expr **)NULL;
  res->function = (Float (*)())NULL;
  res->value = 0.0;
  res->timenow = -FAR_AWAY;
  res->animval = (AnimVal *)NULL;
  return res;	
}

Float
ExprEval(expr)
  Expr	*expr;	/* Expression to evaluate */
{
  /*
   * If the expression is a time-varying function,
   * and its time is incorrect, evaluate at
   * the current time.
   */
  if (expr->timevary && !equal(TimeGet(), expr->timenow)) {
    if (expr->animval != NULL) {
      AnimEval(expr);

    } else if (expr->nparams) {
      switch (expr->nparams) {
      case 1:
	expr->value = (*expr->function)(ExprEval(expr->params[0]));
	break;
      case 2:
	expr->value = (*expr->function)(ExprEval(expr->params[0]),
					ExprEval(expr->params[1]));
	break;
      case 3:
	expr->value = (*expr->function)(ExprEval(expr->params[0]),
					ExprEval(expr->params[1]),
					ExprEval(expr->params[2]));
	break;
      case 4:
	expr->value = (*expr->function)(ExprEval(expr->params[0]),
					ExprEval(expr->params[1]),
					ExprEval(expr->params[2]),
					ExprEval(expr->params[3]));
	break;
      case 5:
	expr->value = (*expr->function)(ExprEval(expr->params[0]),
					ExprEval(expr->params[1]),
					ExprEval(expr->params[2]),
					ExprEval(expr->params[3]),
					ExprEval(expr->params[4]));
	break;
      default:
	RLerror(RL_PANIC, "Expression with > 5 args?\n");
	break;
      }
    }
    expr->timenow = TimeGet();
  }
  return expr->value;
}

Expr *
ExprFunctionCreate(fp, nparams, params, timevary)
     Float (*fp)();
     int nparams, timevary;
     Expr **params;
{
  Expr *res;
  
  res = ExprCreate(FLOAT_EXPR, timevary);
  res->function = fp;
  res->nparams = nparams;
  res->params = params;
  
  return res;
}

Expr *
ExprResolve1(a, fp, timevary)
     Expr *a;
     Float (*fp)();
     int timevary;
{
  Expr **params, *res;
  
  if (!timevary && !a->timevary) {
    res = ExprFloatCreate((*fp)(a->value), FALSE);
    ExprFree(a);
    return res;
  } else {
    params = (Expr **)Malloc(sizeof(Expr *));
    params[0] = a;
    return ExprFunctionCreate(fp, 1, params, TRUE);
  }
}

Expr *
ExprResolve2(a, b, fp, timevary)
     Expr *a, *b;
     Float (*fp)();
     int timevary;
{
  Expr **params, *res;
  if (!timevary && !a->timevary && !b->timevary) {
    res = ExprFloatCreate((*fp)(a->value, b->value), FALSE);
    ExprFree(a);
    ExprFree(b);
    return res;
  } else {
    params = (Expr **)Malloc(2 * sizeof(Expr *));
    params[0] = a;
    params[1] = b;
    return ExprFunctionCreate(fp, 2, params, TRUE);
  }
}

Expr *
ExprResolve3(a, b, c, fp, timevary)
     Expr *a, *b, *c;
     Float (*fp)();
     int timevary;
{
  Expr **params, *res;
  if (!timevary && !a->timevary && !b->timevary && !c->timevary) {
    res = ExprFloatCreate((*fp)(a->value, b->value, c->value), 
			  FALSE);
    ExprFree(a);
    ExprFree(b);
    ExprFree(c);
    return res;
  } else {
    params = (Expr **)Malloc(3 * sizeof(Expr *));
    params[0] = a;
    params[1] = b;
    params[2] = c;
    return ExprFunctionCreate(fp, 3, params, TRUE);
  }
}

Expr *
ExprResolve4(a, b, c, d, fp, timevary)
     Expr *a, *b, *c, *d;
     Float (*fp)();
     int timevary;
{
  Expr **params, *res;
  if (!timevary && !a->timevary && !b->timevary && !c->timevary &&
      !d->timevary) {
    res = ExprFloatCreate((*fp)(a->value, b->value, c->value, 
				d->value), FALSE);
    ExprFree(a);
    ExprFree(b);
    ExprFree(c);
    ExprFree(d);
    return res;
  } else {
    params = (Expr **)Malloc(4 * sizeof(Expr *));
    params[0] = a;
    params[1] = b;
    params[2] = c;
    params[3] = d;
    return ExprFunctionCreate(fp, 4, params, TRUE);
  }
}

Expr *
ExprResolve5(a, b, c, d, e, fp, timevary)
     Expr *a, *b, *c, *d, *e;
     Float (*fp)();
     int timevary;
{
  Expr **params, *res;
  if (!timevary && !a->timevary && !b->timevary && !c->timevary &&
      !d->timevary && !e->timevary) {
    res = ExprFloatCreate((*fp)(a->value, b->value, c->value, 
				d->value, e->value), FALSE);
    ExprFree(a);
    ExprFree(b);
    ExprFree(c);
    ExprFree(d);
    ExprFree(e);
    return res;
  } else {
    params = (Expr **)Malloc(5 * sizeof(Expr *));
    params[0] = a;
    params[1] = b;
    params[2] = c;
    params[3] = d;
    params[4] = e;
    return ExprFunctionCreate(fp, 5, params, TRUE);
  }
}

ExprAssoc *
AssocCreate(lhs, expr, next)
     Float *lhs;
     Expr *expr;
     ExprAssoc *next;
{
  ExprAssoc *new;
  
  new = (ExprAssoc *)Malloc(sizeof(ExprAssoc));
  new->lhs = lhs;
  new->expr = expr;
  new->next = next;
  return new;
}

void
TimeSet(time)
  Float	time;
{
  TimeExpr->value = time;
}

Float
TimeGet()
{
  return TimeExpr->value;
}

void
FrameSet(frame)
  Float	frame;
{
  FrameExpr->value = frame;
}

Float
FrameGet()
{
  return FrameExpr->value;
}

static void
ExprFree(expr)
  Expr	*expr;
{
  if (!expr->symtab) {
    if (expr->type == BUILTIN_EXPR && expr->params)
      free((voidstar)expr->params);
    free((voidstar)expr);
  }
}


/******************************************************************************
 * Animated variable support
 */

/* Add a new value (including sync and mode) to the list */
AnimVal *
AddVal(value, sync, mode, vallist)
  Float	value;
  Float	sync;
  Expr	*mode;
  AnimVal	*vallist;
{
  AnimVal	*animval;

  if (mode != (Expr *)NULL)
    CheckMode(mode, "");

  /* Make a new node */
  animval = (AnimVal *)Malloc(sizeof(AnimVal));
  animval->value = value;
  animval->mode = mode;
  animval->sync = sync;

  /* And add it to the list */
  animval->next = vallist;
  vallist = animval;

  return vallist;
}


/* Check the list of values:
 * - first value should have a sync, if not, set it to 0.
 * - if first value has sync after 0., insert a new value for moment 0 so
 *   value is constant from 0 on to first real value
 * - add syncs for all sync-less values. The syncs will be added by
 *   linearly interpolating between two given syncs.
 * - add default mode to values that don't have a mode
 * - check whether syncs are in order
 * - check whether last value has a sync
 * - and also that last value doesn't have a mode
 */
AnimVal *
CheckVal(animval)
  AnimVal	*animval;
{
  AnimVal	*p;

  if (animval == (AnimVal *)NULL)
    return NULL;

  /* Sync start to time=0 */
  if (animval->sync == FAR_AWAY) {
    animval->sync = 0.;
  } else if (animval->sync > 0.) {
    /* Insert an extra node with sync time=0 and value the same */
    animval = AddVal(animval->value, 0., DefaultMode, animval);
  }
  p = animval;

  /* Now check the rest of the list */
  while (p != NULL) {
    AnimVal	*q = p;
    Float	moment = p->sync;
    int		n = 1;		/* Number of nodes skipped */

    q = p;

    /* Current node has a sync, so go to next */
    if ((p = p->next) == NULL)
      break;

    /* Find next sync-full node */
    while (p->sync == FAR_AWAY && p->next != NULL) {
      p = p->next;
      n++;
    }

    if (p->next == NULL) {		/* End of list */
      if (p->sync == FAR_AWAY)	/* No sync at end */
	RLerror(RL_ABORT, "No sync at end of variable\n");

      if (p->mode != NULL)
	RLerror(RL_ABORT, "Cannot have a mode at the last value.\n");
    }

    if (p->sync <= (moment + EPSILON))
      RLerror(RL_ABORT, "Syncs not in correct order\n");

    /* At least one sync-less node is skipped */
    if (n > 1) {
      int	i;
      Float	step = (p->sync - moment)/n;

      /* Fill in missing syncs, linearly interpolated */
      for (i = 1, q = q->next;
	   i < n;
	   i++, q = q->next)
	q->sync = step * i;
    }
  }

  return animval;
}


static void
CheckMode(mode, name)
  Expr	*mode;
  char	*name;
{
  /* Check whether mode function is well behaved:
   * - starts at (0,0),
   * - ends at (1,1)
   */
  if (mode == (Expr *)NULL)
    return;
  if (!equal(ModeEval(mode, 0.0), (Float)0.0))
    RLerror(RL_ADVISE, "Mode %s doesn't start at 0.0 but %f\n",
	    name, ModeEval(mode, 0.0));
  if (!equal(ModeEval(mode, 1.0), (Float)1.0))
    RLerror(RL_ADVISE, "Mode %s doesn't end at 1.0 but %f\n",
	    name, ModeEval(mode, 1.0));
}


static Float
ModeEval(mode, t)
  Expr	*mode;
  Float	t;
{
  Float	SaveTime;

  /* Save the current (actual) time */
  SaveTime = TimeGet();

  /* Now set the specified time and evaluate the mode function at
   * that time.
   */
  TimeSet(t);
  t = ExprEval(mode == (Expr *)NULL ? DefaultMode : mode);

  /* Restore the real time */
  TimeSet(SaveTime);

  return t;
}


void
AnimEval(e)
  Expr	*e;
{
  AnimVal	*animval;
  Float	t1, t2;
  Float	v1, v2;

  if (e == (Expr *)NULL)
    return;

  animval = e->animval;

  /* Did we get to the next sync point yet? */
  while (animval->next != NULL
	 && TimeGet() >= (animval->next->sync - EPSILON)) {
    e->animval = animval->next;
    free((voidstar)animval);
    animval = e->animval;
  }

  if (animval->next == NULL) {
    e->value = animval->value;

  } else {
    Expr	*mode;

    /* Value on previous sync t1 is v1,
     * value on next sync t2 is v2
     */
    t1 = animval->sync;
    v1 = animval->value;
    t2 = animval->next->sync;
    v2 = animval->next->value;

    if (animval->mode == NULL) {
      mode = DefaultMode;
    } else {
      mode = animval->mode;
    }

    /* Now calculate the value by evaluating the mode function at
     * the scaled time.
     */
    e->value = v1 + (v2 - v1) * ModeEval(mode, (TimeGet() - t1) / (t2 - t1));
  }
}

