/*
 * Interpreter.C - methods for turtle interpreter.
 *
 * Copyright (C) 1992, Christoph Streit (streit@iam.unibe.ch)
 *                     University of Berne, Switzerland
 * 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 "Interpreter.h"
#include "Options.h"
#include "Polygon.h"
#include "Turtle.h"
#include "DeviceDriver.h"
#include "Module.h"
#include "Hull.h"
#include "boolean.h"

#define startBranchSymbol "["
#define endBranchSymbol   "]"

#ifdef OLD_STYLE_CPP
 InterpreterFunctions Interpreter::FuncTable[] = {
#else
 Interpreter::InterpreterFunctions Interpreter::FuncTable[] = {
#endif

  {"F",               &Interpreter::forward},
  {"wi",              &Interpreter::width},
  {"G",               &Interpreter::go},
  {"f",               &Interpreter::go},
  {"pt",              &Interpreter::pitch},
  {"&",               &Interpreter::pitch_negativ},
  {"^",               &Interpreter::pitch},
  {"ro",              &Interpreter::roll},
  {"\\",              &Interpreter::roll_negativ},
  {"/",               &Interpreter::roll},
  {"tu",              &Interpreter::turn},
  {"+",               &Interpreter::turn_negativ},
  {"-",               &Interpreter::turn},
  {"rv",              &Interpreter::rotate_vertical},
  {"$",               &Interpreter::rotate_vertical},
  {"|",               &Interpreter::reverse},
  {startBranchSymbol, &Interpreter::push},
  {endBranchSymbol,   &Interpreter::pop},
  {"{",               &Interpreter::startPolygon},
  {"sv",              &Interpreter::saveVertex},
  {".",               &Interpreter::saveVertex},
  {"}",               &Interpreter::endPolygon},
  {"t",               &Interpreter::tropism},
  {"we",              &Interpreter::weight},
  {"co",              &Interpreter::color},
  {"sm",              &Interpreter::beginMacro},
  {"em",              &Interpreter::endMacro},
  {"xm",              &Interpreter::executeMacro},
  {"lib",             &Interpreter::libraryObject},
  {"s",               &Interpreter::sphere},
  {"tri",             &Interpreter::triangle},
  {"poly",            &Interpreter::polygon},
  {"ah",              &Interpreter::activateHull},
  {"dh",              &Interpreter::deactivateHull},
  {"cb",              &Interpreter::cutBranchWhenHit},
  {"%",               &Interpreter::cutBranch},
  {"texture",         &Interpreter::texture},
  {"LastFunc",        NULL}
};

FP* Interpreter::functable = NULL;
Value Interpreter::turtleX;
Value Interpreter::turtleY;
Value Interpreter::turtleZ;

typedef Turtle* TurtlePtr;
declareList(TurtleStack, TurtlePtr)
implementList(TurtleStack, TurtlePtr)

//___________________________________________________________ Interpreter

Interpreter::Interpreter(DeviceDriver* device, Options* theOptions)
: d(device), options(theOptions),
  t(NULL), m(NULL), ml(NULL), currentModule(-1), saveTurtle(NULL), 
  polyStack(new PolygonList(100)), turtleStack(new TurtleStack(100)),
  definingMacro(FALSE), reflected(FALSE), deleteBranchWhenHit(FALSE)
{

  /*
   * Set up function table if not already done.
   */
  if (functable == NULL) {

    /*
     * Add all the function names to global name space.
     */
    for (register int i=0; FuncTable[i].function != NULL; i++) 
      FuncTable[i].index = Name::addname(FuncTable[i].name);

    functable = new FP[Name::namesCount()];

    /*
     * Generate and initialize hash table for function pointers.
     */
    for (i=0; i<Name::namesCount(); i++)
      functable[i] = NULL;
    for (i=0; FuncTable[i].function != NULL; i++)
      functable[FuncTable[i].index] = FuncTable[i].function;
  }

  /*
   * Set up analytic tropism functions and hulls.
   */
  tropismX = options->tropismX; 
  tropismY = options->tropismY; 
  tropismZ = options->tropismZ; 
  tropismFunction = 
    (tropismX != NULL) || (tropismY != NULL) || (tropismZ != NULL);
  tropismWeight = options->weight;
  weightFunction = (tropismWeight != NULL);
  h = options->hulls;
}

Interpreter::~Interpreter()
{
  delete polyStack;
  delete turtleStack;
}

/*
 * Interpret the module string modules using the given dive driver.
 */

void Interpreter::interpret(ModuleList* modules)
{
  t = new Turtle; 
  ml = modules;

  /*
   * Start interpretation of module string ml.
   */
  if (!options->quiet)
    cerr << "Start of Interpretation ...\n\n";

  d->begin();

  /*
   * For each module lookup corresponding interpreter function, call it
   * if not NULL.
   */
  for (currentModule = 0; currentModule < ml->count(); currentModule++) {
    m = ml->item(currentModule);
    if (functable[ml->item(currentModule)->name().index()] != NULL) {
      FP currentFunc = functable[ml->item(currentModule)->name().index()];
      (this->*currentFunc)();
    }
      
    if (options->deleteModules)
      delete ml->item(currentModule);

    /*
     * If we hit any hull primitive and deleteBranchWhenHit option is
     * set, ignore all modules up to the next end branch command ("]").
     * Note that we have to skip all the subbranches also.
     */
    if (deleteBranchWhenHit && reflected) {
      reflected = FALSE; // reset
      cutBranch();
    }
  }

  flush();

  /*
   * Visualize the defined hulls.
   */
  if (options->showHulls) {
    PolygonList* polys;

    Color hullColor("HullColor");
    d->color(hullColor);
    for (HullSymtab_Iterator hullItr(*h); hullItr.more(); hullItr.next()) {
      polys = hullItr.cur_value()->convertToPolygonList(t->bbox());
      for (register long i=0; i<polys->count(); i++)
	d->polygon(polys->item(i));
      delete polys;
    } 
  }

  if (options->verbose) {
    cerr << "BoundingBox: " << t->bbox() << "\n\n";
    cerr << "intersection tests: " 
	 << GeoObject::getIntersectionTests() <<"\n"
         << "intersection hits:  " 
	 << GeoObject::getIntersections() << "\n\n";
  }

  d->end(t->bbox());

  delete t;
}

//___________________________________________________________ interpreter functions

/*
 * Flush checks if there is a segment in the pipe which has't been draw yet
 * (lastWidth > 0) and draws it in that case.
 */

void Interpreter::flush()
{
  /*
   * A segment left in the pipe?
   */
  if (t->getLastWidth() > 0) {
    if (tropismFunction || weightFunction)
      computeTropism();

    d->color(t->getLastColor());
    d->texture(t->getLastTexture());

    if (equal(t->getLastWidth(), t->getWidth()))
      d->cylinder(t->lastPos(), t->pos(), t->getWidth());
    else if (!options->coneSpheres)
      d->cone(t->lastPos(), t->getLastWidth(), t->pos(), t->getWidth());
    else
      createConeBetweenSpheres(t->lastPos(), t->getLastWidth(), 
			       t->pos(), t->getWidth());      
    if (options->coneSpheres)
      d->sphere(t->pos(), t->getWidth());
  }
  t->setLastWidth(-1); // no segments left in the pipe
}

/*
 * Go forward and draw a line.
 * lastWidth < 0: no move has been previously done
 */

void Interpreter::forward()
{ 
  real step;

  /*
   * Is there an argument for the move?
   */
  if (m->argCount() <= 0 || !m->arg(0)->toReal(step)) 
    step = options->defaultForward;

  /*
   * Do we really have to move?
   */
  if (equal(step, 0)) {
    Error(ERR_ADVISE, "Interpreter::forward with argument 0");
    return;
  }

  /*
   * Recompute tropism and weight functions if any present.
   */
  if (tropismFunction || weightFunction)
    computeTropism();

  if (t->getLastWidth() > 0) {
    d->color(t->getLastColor());
    d->texture(t->getLastTexture());
  }

  real lastWidth    = t->getLastWidth();
  real currentWidth = t->getWidth();
  Vector lastPos    = t->lastPos();
  Vector currentPos = t->pos();
  reflected = t->forward(step);
  Vector newPos     = t->pos();

  /*
   * Regular line segment or cone spheres?
   */
  if (lastWidth > 0) {
    if (equal(lastWidth, currentWidth))
      d->cylinder(lastPos, currentPos, lastWidth);
    else if (!options->coneSpheres)
      d->cone(lastPos, lastWidth, currentPos, currentWidth);    
    else 
      createConeBetweenSpheres(lastPos, lastWidth, currentPos, currentWidth);
    
   /*
    * Add sphere when there's an angle between the segments.
    */
    if (options->coneSpheres && !((lastPos-currentPos)*(currentPos-newPos)).zero())
      d->sphere(currentPos, currentWidth);
  }
}

/*
 * Go forward without drawing a line.
 */

void Interpreter::go()
{ 
  real step;

  flush();
  if (tropismFunction || weightFunction)
    computeTropism();

  if (m->argCount()>0 && m->arg(0)->toReal(step))
    reflected = t->forward(step);
  else
    reflected = t->forward(options->defaultForward);

  /*
   * There's no segment in the pipe left.
   */
  t->setLastWidth(-1);
}

/*
 * Set line width.
 */

void Interpreter::width()
{
  real width;
  if (m->argCount()>0 && m->arg(0)->toReal(width) && width>0) 
    t->setWidth(width);
}

/*
 * Pitch turtle in positive direction.
 */

void Interpreter::pitch()
{ 
  real a;
  if (m->argCount()>0 && m->arg(0)->toReal(a)) 
    t->pitch(dtor(a));
  else
    t->pitch(options->defaultPitch);
}

/*
 * Pitch turtle in negative direction.
 */

void Interpreter::pitch_negativ()
{ 
  real a;
  if (m->argCount()>0 && m->arg(0)->toReal(a)) 
    t->pitch(-dtor(a));
  else
    t->pitch(-options->defaultPitch);
}

/*
 * Turn turtle in positive direction.
 */

void Interpreter::turn()
{ 
  real a;
  if (m->argCount()>0 && m->arg(0)->toReal(a)) 
    t->turn(dtor(a));
  else
    t->turn(options->defaultTurn);
}

/*
 * Turn turtle in negative direction.
 */

void Interpreter::turn_negativ()
{ 
  real a;
  if (m->argCount()>0 && m->arg(0)->toReal(a)) 
    t->turn(-dtor(a));
  else
    t->turn(-options->defaultTurn);
}

/*
 * Roll turtle in positive direction.
 */

void Interpreter::roll()
{ 
  real a;
  if (m->argCount()>0 && m->arg(0)->toReal(a)) 
    t->roll(dtor(a));
  else
    t->roll(options->defaultRoll);
}

/*
 * Roll turtle in negative direction.
 */

void Interpreter::roll_negativ()
{ 
  real a;
  if (m->argCount()>0 && m->arg(0)->toReal(a)) 
    t->roll(-dtor(a));
  else
    t->roll(-options->defaultRoll);
}

/*
 * Align turtle vertical.
 */

void Interpreter::rotate_vertical()
{
  t->rotate_vertical();
}

/*
 * Reverse heading of the turtle.
 */

void Interpreter::reverse()
{
  t->reverse();
}

/*
 * Save current turtle state on the stack.
 */

void Interpreter::push()
{
  turtleStack->prepend(t);
  t = new Turtle(*t);
  t->setLastWidth(-1);
}

/*
 * Restore turtle state from stack.
 */

void Interpreter::pop()
{
  flush();

  if (turtleStack->count() > 0) {
    turtleStack->item(0)->expandBBoxBy(t->bbox());
    delete t;
    t = turtleStack->item(0);
    turtleStack->remove(0);
  }
  else 
    Error(ERR_WARN, "Interpreter::pop stack is empty");
}

/*
 * Start a new polygon.
 */

void Interpreter::startPolygon()
{
  polyStack->prepend(new Polygon);
}

/*
 * Add vertex to current polygon.
 */

void Interpreter::saveVertex()
{
  if (polyStack->count() > 0)
    polyStack->item(0)->addVertex(t->pos());
  else
    Error(ERR_WARN, "Interpreter::saveVertex polygon stack is empty");
}

/*
 * End of polygon definition.
 */

void Interpreter::endPolygon()
{
  if (polyStack->count() <= 0) {
    Error(ERR_WARN, "Interpreter::endPolygon polygon stack is empty");
    return;
  }

  Polygon* p = polyStack->item(0);

  if (p->numVertices() <= 0) {
    Error(ERR_WARN, "Interpreter::endPolygon no vertex in polygon");
    delete p;
    polyStack->remove(0);
    return;
  }
  
  /*
   * Start and endpoint should not be the same.
   */
  if (p->numVertices()>1 && p->vertex(0) == p->vertex(p->numVertices()-1))
    p->removeVertex(p->numVertices()-1);

  /* 
   * Set color and texture.
   */
  d->color(t->getColor());
  d->texture(t->getTexture());

  d->polygon(p);

  polyStack->remove(0);
}

/*
 * Set tropism vector.
 */

void Interpreter::tropism()
{
  real x, y, z, w;
  if (m->argCount() >= 3  &&  
      m->arg(0)->toReal(x) && m->arg(1)->toReal(y) && m->arg(2)->toReal(z)) {
    t->setTropism(x,y,z);

    if (m->argCount() >= 4 && m->arg(3)->toReal(w))
      t->setWeight(w);
  }
}

/*
 * Set weight for tropism calculations.
 */

void Interpreter::weight()
{
  real w;
  if (m->argCount()>0 && m->arg(0)->toReal(w)) 
    t->setWeight(w);
}

/*
 * Set color.
 */

void Interpreter::color()
{
  if (m->argCount()>0) {
    Color c(m->arg(0)->toString());
    t->setColor(c);
  }
}

/* 
 * Start macro definition.
 */

void Interpreter::beginMacro()
{
  if (definingMacro) 
    Error(ERR_PANIC, "Interpreter::beginMacro nested macro definition");
  
  if (m->argCount() <= 0) 
    Error(ERR_PANIC, "Interpreter::beginMacro macro definition without a name");

  rcString macroName(m->arg(0)->toString());

  if (!d->addMacroName(macroName))
    Error(ERR_PANIC, "Interpreter::beginMacro macro \"" 
                     + macroName + "\" already defined");
  
  saveTurtle = t; 
  t = new Turtle; 
  definingMacro = TRUE;
  d->beginMacro(macroName);
}

/*
 * End of macro definition.
 */

void Interpreter::endMacro()
{
  if (definingMacro == FALSE) {
    Error(ERR_WARN, "Interpreter::endMacro missing begin macro");
    return;
  }

  flush();
  d->endMacro();
  delete t;
  t = saveTurtle; saveTurtle = NULL;
  definingMacro = FALSE;
}

/*
 * Execute a already defined macro.
 */

void Interpreter::executeMacro()
{
  if (m->argCount() <= 0) {
    Error(ERR_WARN, "Interpreter::executeMacro macro without a name");
    return;
  }

  TransMatrix tmat;
  real scale;

  tmat(0,0) = t->vecU()[0]; tmat(0,1) = t->vecU()[1]; tmat(0,2) = t->vecU()[2]; 
  tmat(1,0) = t->vecL()[0]; tmat(1,1) = t->vecL()[1]; tmat(1,2) = t->vecL()[2]; 
  tmat(2,0) = t->vecH()[0]; tmat(2,1) = t->vecH()[1]; tmat(2,2) = t->vecH()[2]; 

  if (m->argCount()>1 && m->arg(1)->toReal(scale) && !equal(scale, 1))
    tmat.scale(scale, scale, scale);
  tmat.translate(t->pos());

  d->executeMacro(m->arg(0)->toString(), tmat);
}

/*
 * Include a predefined object.
 */

void Interpreter::libraryObject()
{
  if (m->argCount() <= 0) {
    Error(ERR_WARN, "Interpreter::libraryObject object without a name");
    return;
  }

  TransMatrix tmat;
  real scale;

  rcString libName(m->arg(0)->toString());

  d->addLibraryObjectName(libName);

  tmat(0,0) = t->vecU()[0]; tmat(0,1) = t->vecU()[1]; tmat(0,2) = t->vecU()[2]; 
  tmat(1,0) = t->vecL()[0]; tmat(1,1) = t->vecL()[1]; tmat(1,2) = t->vecL()[2]; 
  tmat(2,0) = t->vecH()[0]; tmat(2,1) = t->vecH()[1]; tmat(2,2) = t->vecH()[2]; 

  if (m->argCount()>1 && m->arg(1)->toReal(scale) && !equal(scale, 1))
    tmat.scale(scale, scale, scale);
  tmat.translate(t->pos());

  d->libraryObject(libName, tmat);
}

/*
 * Generate sphere at turtle position with given radius.
 */

void Interpreter::sphere()
{
  real r;

  /* 
   * Set color and texture.
   */
  d->color(t->getColor());
  d->texture(t->getTexture());

  if (m->argCount()>0 && m->arg(0)->toReal(r)) 
    d->sphere(t->pos(), r);
  else
    d->sphere(t->pos(), 1);
}

/*
 * Generate triangle with given vertices.
 */

void Interpreter::triangle()
{
  if (m->argCount() < 9)
    return;

  real coords[9];
  for (register int i=0; i<9; i++)
    if (!m->arg(i)->toReal(coords[i]))
      return;

  Vector p1(coords[0], coords[1], coords[2]); 
  Vector p2(coords[3], coords[4], coords[5]); 
  Vector p3(coords[6], coords[7], coords[8]); 

  BoundingBox tmpBBox;
  tmpBBox.expand(p1);
  tmpBBox.expand(p2);
  tmpBBox.expand(p3);
  t->expandBBoxBy(tmpBBox);

  /* 
   * Set color and texture.
   */
  d->color(t->getColor());
  d->texture(t->getTexture());

  d->polygon(new Polygon(p1, p2, p3));
}

/*
 * Generate polygon with given vertices.
 */

void Interpreter::polygon()
{
  if (m->argCount() < 9)
    return;

  real x, y, z;
  Polygon* p = new Polygon(3);
  BoundingBox tmpBBox;

  for (register int i=0; i<m->argCount()-2; i+=3) {
    if (m->arg(i)->toReal(x)   && 
	m->arg(i+1)->toReal(y) && 
	m->arg(i+2)->toReal(z)) 
      {
	p->addVertex(Vector(x,y,z));
	tmpBBox.expand(Vector(x,y,z));
      }
  }

  if (p->numVertices() > 2) {

    /* 
     * Set color and texture.
     */
    d->color(t->getColor());
    d->texture(t->getTexture());

    d->polygon(p);
    t->expandBBoxBy(tmpBBox);
  }
  else
    delete p;
}

/*
 * Activate a hull. Turtle will move with respect to the primitives
 * of the hull.
 */

void Interpreter::activateHull()
{
  if (m->argCount()<=0) {
    Error(ERR_WARN, "Interpreter::activateHull no hull name");
    return;
  }

  rcString hullName(m->arg(0)->toString());

  Hull* hull;
  if (!h->lookup(hullName, hull)) {
    Error(ERR_WARN, "Interpreter::activateHull unknown hull " + hullName);
    return;
  }
  real reflectanceFactor = 1;
  if (m->argCount()>1)
    m->arg(1)->toReal(reflectanceFactor);
  
  t->setHull(hull, reflectanceFactor);
}

void Interpreter::deactivateHull()
{
  t->unsetHull();
}

/*
 * Set/Unset of delete branch when conflict with current hull.
 */

void Interpreter::cutBranchWhenHit()
{
  real flag = TRUE;
  if (m->argCount() > 0)
    m->arg(0)->toReal(flag);
    
  if ((int)flag) {
    deleteBranchWhenHit = TRUE;
    t->setStopOnHit();
  }
  else {
    deleteBranchWhenHit = FALSE;
    t->unsetStopOnHit();
  }
}

/*
 * Ignore all the modules until we find a end branch symbol (ignore
 * subbranches also).
 */

void Interpreter::cutBranch()
{
  static Name startBranch(startBranchSymbol);
  static Name endBranch(endBranchSymbol);
  int nested = 0;

  /*
   * Ignore all modules up to the next end branch command ("]").
   * Note that we have to skip all the subbranches also.
   */
  while (++currentModule < ml->count()) {
    if (ml->item(currentModule)->name() == startBranch)
      nested++;
    else if (ml->item(currentModule)->name() == endBranch) {
      if (nested-- == 0) {
	currentModule--; 
	break;
      }
    }
    if (options->deleteModules)
      delete ml->item(currentModule);
  }
}

/*
 * Define texture for the following primitives.
 */
void Interpreter::texture()
{
  if (m->argCount()>0) {
    rcString theTexture(m->arg(0)->toString());
    t->setTexture(theTexture);
  }
  else
    t->setTexture(""); // disable textures
}

/*
 * Generate a cone between two given spheres. 
 */

void Interpreter::createConeBetweenSpheres(const Vector& p1, real r1, 
 				           const Vector& p2, real r2)
{
  real a = r1-r2;
  real r = r1;
  real c = p1.distance(p2);
  int secondIsLarger = FALSE;

  if (c<r1 || c<r2) {
    Error(ERR_ADVISE, rcString("Interpreter::createConeBetweenSpheres\n") + 
                      "\tdistance between spheres is too small -> can't create cone\n");
    return;
  }
    
  if (a < 0) {
    r = r2;
    a = -a;
    secondIsLarger = TRUE;
  }
  
  real p  = a*a/c;
  real ps = p*r/a;

  real h  = sqrt(p*(c-p));
  real hs = h*r/a;

  Vector dir = p2 - p1;

  if (secondIsLarger)
    d->cone(p1-dir*((ps-p)/c), hs-h, p2-dir*(ps/c), hs);
  else
    d->cone(p1+dir*(ps/c), hs, p2+dir*((ps-p)/c), hs-h);
}

/*
 * If tropism vector depends on turtle position, we recompute the
 * tropism vector after each move.
 */

void Interpreter::computeTropism()
{
  turtleX = t->pos()[0];
  turtleY = t->pos()[1];
  turtleZ = t->pos()[2];

  if (tropismFunction)
    t->setTropism(tropismX->evaluate(),
		  tropismY->evaluate(),
		  tropismZ->evaluate());  

  if (weightFunction)
    t->setWeight(tropismWeight->evaluate());
}
