/*  this class interprets a parsed Lstring into spatial commands, and 
    passes the shapes formed to an Lgraphics class. The main work is 
    done in the "PlotLstring" method */

#ifdef WIN32
#include "stdafx.h"
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <math.h>
#include <vector>
#include <stack>
#include "Lturtle.h"
#include "extra_math.h"

using namespace std;

Lturtle::Lturtle(UIClass *ptr):
  UIClassPtr(ptr)
{
  sky[0]= 0.0; sky[1]= 0.0; sky[2]= 1.0;
  rand_amount = 3; //todo
  RandSeed    = 10;
}

Lturtle::~Lturtle()
{
}

bool  Lturtle::set_Lgraphics(Lgraphics *TheAssociatedLgraphicsClass)
{ //true if graphics could be initialised
	LgraphicsClassPtr=TheAssociatedLgraphicsClass;
	return true;
}


bool  Lturtle::set_Lstring(Lstring *TheAssociatedLstringClass)
{
	LstringClassPtr=TheAssociatedLstringClass;
	return true;
}

Lstring *Lturtle::get_Lstring()
{
  return LstringClassPtr;
}

bool Lturtle::InitDraw(double &centre_x, double &centre_y, double &centre_z, double &scale)
{
  static vect3<float>  the_centre;
  static double max_length=0;
  
  if (CheckSetup()==false)
    return false;

  //clear the buffer
  LgraphicsClassPtr->Clear();
 
  //calculate & store all the shapes
  LgraphicsClassPtr->BeginStore();
  this->PlotLstring(the_centre,max_length);
  LgraphicsClassPtr->EndStore();  
  
  centre_x = the_centre[_x];
  centre_y = the_centre[_y];
  centre_z = the_centre[_z];
  scale  = max_length;
  
  return true;
};

bool Lturtle::Draw() 
{  
  if (CheckSetup()==false)
    return false;

  //check that it has been stored. If not, draw it
  if (LgraphicsClassPtr->HasBeenStored()==false) {
    double d1,d2,d3,d4;
    if (InitDraw(d1, d2, d3, d4)==false)
      return false;
  };

  //actually draw
  LgraphicsClassPtr->Output();
  return true;
}

void Lturtle::SetRandSeed(int i)
{
  RandSeed=i;
}
/******** Private methods ***********/

bool Lturtle::CheckSetup()
{                                    /* checks that all the necessary
					pointers are set up so that
					the User Interface is known, 
					the Lstring can be accessed,
					the Graphics routines are known,
					etc. */
  if (UIClassPtr==NULL) {
    fprintf(stderr, "No user interface class set in Lturtle::CheckSetup method\n");
    return false;
  };
  if (LgraphicsClassPtr==NULL) {
    UIClassPtr->ProgrammingError("Lturtle::CheckSetup",
				 "No LgraphicsClassPtr");
    return false;
  };
  if (LstringClassPtr==NULL) {
    UIClassPtr->ProgrammingError("Lturtle::CheckSetup",
					"No LstringClassPtr");
    return false;
  };
  if (!LstringClassPtr->FinalStringExists()) {
    UIClassPtr->ProgrammingError("Lturtle::CheckSetup",
					"Unparsed Lstring");
    return false;
  };
  return true;
}

void Lturtle::PlotLstring(vect3<float> &centre, double &max_length)
{
  /* Process a production string */

  vect3<float>	 min,max,end;
  r32	 min_thick, fraction, r, a, thick_l, ang_l, dis_l, dis2_l, trope_l, val;
  int    r_depth;
  bool	 last_recur=false, rand_set, poly_on = false;
  bool   user_form=false, closed_form=false, trope_set;
  s_rec	 current_s,save;
  p_rec  current_p;
  stack<s_rec, vector<s_rec> >	s_stack;
  stack<p_rec, vector<p_rec> >	p_stack;

  //declare references (aliases) to commonly used members of current_s;
  vect3<float>  &pos  = current_s.pos;
  vect3<float>  &fow  = current_s.fow;
  vect3<float>  &lef  = current_s.lef;
  vect3<float>  &upp  = current_s.upp;
  vect3<float>  &last = current_s.last;
  r32	        &dis  = current_s.dis;
  r32           &ang  = current_s.ang;
  r32	        &thick= current_s.thick;
  r32	        &dis2 = current_s.dis2;
  r32	        &tr   = current_s.tr;
  u16	        &col  = current_s.col;
  
  // Setup initial values and vects
  zmin         = (r32) 1e30;
  rand_set     = UIClassPtr->is_rand_set();
  trope_set    = UIClassPtr->is_trope_set();
  trope_amount = UIClassPtr->get_trope_amount();
  min_thick    = percent2unit(UIClassPtr->get_min_thick());
  tr           = UIClassPtr->get_tr();
  fraction     = LstringClassPtr->get_fraction();
  ang	       = deg2rad(LstringClassPtr->get_ang());
  thick	       = percent2unit(LstringClassPtr->get_thick());
  dis	       = percent2unit(UIClassPtr->get_dis());
  dis2	       = dis / 2.0;
  col          = 0;

  min.assign(HUGE_VAL,HUGE_VAL,HUGE_VAL);
  max.assign(-HUGE_VAL,-HUGE_VAL,-HUGE_VAL);
  last.assign  (1,1,1);
  pos.assign   (0,0,0);
  fow.assign   (0,0,1);
  lef.assign   (0,1,0);
  upp.assign   (1,0,0);
  SRndM(RandSeed);

  size_t        StrLength = LstringClassPtr->FinalStringLength();
  unsigned long StrPos    = 0;
  while ( StrPos < StrLength) {
    r_depth=LstringClassPtr->FinalStringRecLev();
    min.SetToMinOf(pos);
    max.SetToMaxOf(pos);
    switch (LstringClassPtr->FinalStringChr(StrPos++)) {             // the current char in the string 
    default:
      break;
      
    case '(':          //any lone brackets are skipped
      StrPos--;
      LstringClassPtr->Get_value(StrPos,val);
      break;

    case '@':          // Marks last recursion level during growing phase
      last_recur = !last_recur;
      if (last_recur) {              // Store all vars and do fraction
	thick_l = thick;
	ang_l = ang;
	dis_l = dis;
	dis2_l = dis2;
	trope_l = trope_amount;
	
	dis *= fraction;
	dis2 *= fraction;
	thick *= fraction;
	ang *= fraction;
	trope_amount *= fraction;
      } else {                       // Restore
	thick = thick_l;
	ang = ang_l;
	dis = dis_l;
	dis2 = dis2_l;
	trope_amount = trope_l;
      }
      break;
      
    case '+':
      save.ang = ang;
      if (LstringClassPtr->Get_value(StrPos,val)) { //has brackets
	ang = deg2rad(val);
	if (last_recur)
	  ang *= fraction * fraction; //why?
	  }
      Set_rot(-ang, upp);
      fow=fow.Util_t(C1, C2, C3);
      lef=lef.Util_t(C1, C2, C3);
      fow.normalize();
      lef.normalize();
      ang = save.ang;
      break;
      
    case '-':
      save.ang = ang;
      if (LstringClassPtr->Get_value(StrPos,val)) {
	ang = deg2rad(val);
	if (last_recur)
	  ang *= fraction * fraction; //why?
      }
      Set_rot(ang, upp);
      fow= fow.Util_t(C1, C2, C3);
      lef= lef.Util_t(C1, C2, C3);
      fow.normalize();
      lef.normalize();
      ang = save.ang;
      break;
      
    case '`':
      r_depth=LstringClassPtr->FinalStringRecLev() - 
	LstringClassPtr->Get_RecursionChar(StrPos);

    case '~':
      if (LstringClassPtr->Get_value(StrPos,val)) {
	r = deg2rad(val);
	if (last_recur)
	  r *= fraction; //only draw a fraction
      } else
	r = deg2rad(UIClassPtr->rand_amount());
      
      a = (Rnd(r_depth) * r * (r32) 2.0) - r;
      Set_rot(a, upp);
      fow=fow.Util_t(C1, C2, C3);
      lef=lef.Util_t(C1, C2, C3);
      fow.normalize();
      lef.normalize();
      a = (Rnd(r_depth) * r * (r32) 2.0) - r;
      Set_rot(a, lef);
      fow=fow.Util_t(C1, C2, C3);
      upp=upp.Util_t(C1, C2, C3);
      fow.normalize();
      upp.normalize();
      a = (Rnd(r_depth) * r * (r32) 2.0) - r;
      Set_rot(a, fow);
      lef=lef.Util_t( C1, C2, C3);
      upp=upp.Util_t(C1, C2, C3);
      lef.normalize();
      upp.normalize();
      break;
      
    case 't':
      if ((fow[_x] == (r32) 0.0) && (fow[_y] == (r32) 0.0))
	break;
      save.tr = tr;
      if (trope_set)
	tr = trope_amount;
      if (LstringClassPtr->Get_value(StrPos,val)) {
	tr = val;
	if (last_recur)
	  tr *= fraction * fraction; //why
      }
      trope=fow;
      trope[_x] = -trope[_x];
      trope[_y] = -trope[_y];
      trope[_z] = (r32) 0.0;
      trope.normalize();
      r = tr * (fow%trope);
      Set_rot(-r, lef);
      fow=fow.Util_t(C1, C2, C3);
      upp=upp.Util_t(C1, C2, C3);
      fow.normalize();
      upp.normalize();
      tr = save.tr;
      break;
      
    case '$':
      if ((fow-sky).length() == (r32) 0.0)
	break;
      lef=fow*sky;
      upp=fow*lef;
      if (upp[_z] < (r32) 0.0) {
	upp = -upp;
	lef = -lef;
      }
      break;
      
    case '&':
      save.ang = ang;
      if (LstringClassPtr->Get_value(StrPos,val)) {
	ang = deg2rad(val);
	if (last_recur)
	  ang *= fraction * fraction;
      }
      Set_rot(ang, lef);
      fow=fow.Util_t(C1, C2, C3);
      upp=upp.Util_t(C1, C2, C3);
      fow.normalize();
      upp.normalize();
      ang = save.ang;
      break;
      
    case '^':
      save.ang = ang;
      if (LstringClassPtr->Get_value(StrPos,val)){
	ang = deg2rad(val);
	if (last_recur)
	  ang *= fraction * fraction;
      }
      Set_rot(-ang, lef);
      fow=fow.Util_t(C1, C2, C3);
      upp=upp.Util_t(C1, C2, C3);
      fow.normalize();
      upp.normalize();
      ang = save.ang;
      break;
      
    case '<':
      save.ang = ang;
      if (LstringClassPtr->Get_value(StrPos,val)) {
	ang = deg2rad(val);
	if (last_recur)
	  ang *= fraction * fraction;
      }
      Set_rot(-ang, fow);
      lef=lef.Util_t(C1, C2, C3);
      upp=upp.Util_t(C1, C2, C3);
      lef.normalize();
      upp.normalize();
      ang = save.ang;
      break;
      
    case '>':
      save.ang = ang;
      if (LstringClassPtr->Get_value(StrPos,val)) {
	ang = deg2rad(val);
	if (last_recur)
	  ang *= fraction * fraction;
      }
      Set_rot(ang, fow);
      lef=lef.Util_t(C1, C2, C3);
      upp=upp.Util_t(C1, C2, C3);
      lef.normalize();
      upp.normalize();
      ang = save.ang;
      break;
      
    case '%':
      Set_rot(3.141592654f, fow);
      lef=lef.Util_t(C1, C2, C3);
      upp=upp.Util_t(C1, C2, C3);
      lef.normalize();
      upp.normalize();
      break;
	  
    case '|':
      Set_rot(3.141592654f, upp);
      fow=fow.Util_t(C1, C2, C3);
      lef=lef.Util_t(C1, C2, C3);
      fow.normalize();
      lef.normalize();
      break;
      
    case '!':
      if (LstringClassPtr->Get_value(StrPos,val)) {
	if (last_recur)
	  thick *= one + fraction * (val - one);
	else
	  thick *= val;
      } else {
	if (last_recur)
	  thick *= one + fraction * ((r32) THICK_DECR - one);
	else
	  thick *= (r32) THICK_DECR;
      }
      break;
      
    case '?':
      if (LstringClassPtr->Get_value(StrPos,val)) {
	if (last_recur)
	  thick *= one + fraction * (val - one);
	else
	  thick *= val;
      } else {
	if (last_recur)
	  thick /= one + fraction * ((r32) THICK_DECR - one);
	else
	  thick /= (r32) THICK_DECR;
      }
      break;
      
    case ':':
      if (LstringClassPtr->Get_value(StrPos,val)) {
	if (last_recur)
	  ang *= one + fraction * (val - one);
	else
	  ang *= val;
      } else {
	if (last_recur)
	  ang *= one + fraction * ((r32) ANGLE_DECR - one);
	else
	  ang *= (r32) ANGLE_DECR;
      }
      break;
      
    case ';':
      if (LstringClassPtr->Get_value(StrPos,val)) {
	if (last_recur)
	  ang *= one + fraction * (val - one);
	else
	  ang *= val;
      } else {
	if (last_recur)
	  ang /= one + fraction * ((r32) ANGLE_DECR - one);
	else
	  ang /= (r32) ANGLE_DECR;
	  }
      break;
      
    case '\'':
      if (LstringClassPtr->Get_value(StrPos,val)) {
	r = val;
	if (last_recur) {
	  dis *= one + fraction * (r - one);
	  dis2 *= one + fraction * (r - one);
	} else {
	  dis *= r;
	  dis2 *= r;
	}
      } else {
	if (last_recur) {
	  dis *= one + fraction * ((r32) LENGTH_DECR - one);
	  dis2 *= one + fraction * ((r32) LENGTH_DECR - one);
	} else {
	  dis *= (r32) LENGTH_DECR;
	  dis2 *= (r32) LENGTH_DECR;
	}
      }
      break;
      
    case '\"':
      if (LstringClassPtr->Get_value(StrPos,val)) {
	r = val;
	if (last_recur) {
	  dis *= one + fraction * (r - one);
	  dis2 *= one + fraction * (r - one);
	} else {
	  dis *= r;
	  dis2 *= r;
	}
      } else {
	if (last_recur) {
	  dis /= one + fraction * ((r32) LENGTH_DECR - one);
	  dis2 /= one + fraction * ((r32) LENGTH_DECR - one);
	} else {
	  dis /= (r32) LENGTH_DECR;
	  dis2 /= (r32) LENGTH_DECR;
                }
      }
      break;
	  
    case 'Z':
      save.dis2 = dis2;
      if (LstringClassPtr->Get_value(StrPos,val)) {
	dis2 = percent2unit(val);
	if (last_recur)
	  dis2 *= fraction;
      }
      end.plus_fac(pos, fow, dis2);
      min.SetToMinOf(end);
      max.SetToMaxOf(end);
      if (user_form)
	Define_form(pos, end, upp, col);
      else if (closed_form)
	Define_closed(pos, end, upp, col);
      else
	Define_block(pos, end, upp, thick, min_thick, col);
      pos=end;
      dis2 = save.dis2;
      break;
      
    case 'F':
      save.dis = dis;
      if (LstringClassPtr->Get_value(StrPos,val)) {
	dis = percent2unit(val);
	if (last_recur)
	  dis *= fraction;
      }
      end.plus_fac(pos, fow, dis);
      min.SetToMinOf(end);
      max.SetToMaxOf(end);
      if (user_form)
	Define_form(pos, end, upp, col);
      else if (closed_form)
	Define_closed(pos, end, upp, col);
      else
	Define_block(pos, end, upp, thick, min_thick, col);
      pos=end;
      dis = save.dis;
      break;
      
    case '[':
      s_stack.push(current_s);
      //not last
      if (closed_form) {
	// Vector_copy_r32(last, stack[scount].last);
	// stack[scount].last_col = last_col;
	// for (j = 1; j <= 8; j++)
	//    Vector_copy_r32(last_v[j], stack[scount].last_v[j]);
      };
      break;
      
    case ']':
      current_s=s_stack.top();
      s_stack.pop();
      //
      if (closed_form) {
	// Vector_copy_r32(stack[scount].last, last);
	// last_col = stack[scount].last_col;
	// for (j = 1; j <= 8; j++)
	//   Vector_copy_r32(stack[scount].last_v[j], last_v[j]);
      };
      break;
      
    case '{': //start a new polygon structure
      if (poly_on) //push the old polygon structure onto the stack
	p_stack.push(current_p);
      poly_on = true;
      current_p.clear();
      current_p.push_back(pos);
      min.SetToMinOf(pos);
      max.SetToMaxOf(pos);
      break;
      
    case 'f':
      save.dis = dis;
      if (LstringClassPtr->Get_value(StrPos,val)) {
	dis = percent2unit(val);
	if (last_recur)
	  dis *= fraction;
      }
      pos.plus_fac(pos, fow, dis);
      if (poly_on) {
	current_p.push_back(pos);
	min.SetToMinOf(pos);
	max.SetToMaxOf(pos);
      };
      dis = save.dis;
      break;
      
    case '.':
      if (poly_on) {
	current_p.push_back(pos);
	min.SetToMinOf(pos);
	max.SetToMaxOf(pos);
      };
      break;
      
    case 'g':
      save.dis = dis;
      if (LstringClassPtr->Get_value(StrPos,val)) {
	dis = percent2unit(val);
	if (last_recur)
	  dis *= fraction;
      }
      pos.plus_fac(pos, fow, dis);
      dis = save.dis;
      break;
      
    case 'z':
      save.dis2 = dis2;
      if (LstringClassPtr->Get_value(StrPos,val)) {
	dis2 = percent2unit(val);
	if (last_recur)
	  dis2 *= fraction;
      }
      pos.plus_fac(pos, fow, dis2);
      if (poly_on) {
	current_p.push_back(pos);
	min.SetToMinOf(pos);
	max.SetToMaxOf(pos);
      };
      dis2 = save.dis2;
      break;
      
    case '}':
      Define_triangles(&current_p, col);
      current_p.clear();
      if (p_stack.empty()) {
	poly_on = false;
      } else {
	current_p= p_stack.top();
	p_stack.pop();
	poly_on = true;
      }
      break;
      
    case 'c':
      if (LstringClassPtr->Get_value(StrPos,val)) {
	col = (u16) val;
      } else {
	col++;
      }
      break;
  };
};

  centre =  (max+min)/2;
  max   -=  min;
  if      ((max[_x]>max[_y]) && (max[_x]>max[_z]))
    max_length=max[_x];
  else if ((max[_y]>max[_x]) && (max[_y]>max[_z]))
    max_length=max[_y];
  else
    max_length=max[_z];

}

void Lturtle::Define_closed(vect3<float> p1, vect3<float> p2, vect3<float> up, u16 c)
{
  //not used yet
}
void Lturtle::Define_form(vect3<float> p1, vect3<float> p2, vect3<float> up, u16 c)
{
  //not used yet
}

void Lturtle::Define_block(vect3<float> p1, vect3<float> p2, vect3<float> up, r32 thick, r32 min_thick, u16 c)
{                                           /* Insert basic block. Here we
                                             * build a cube shape directly on
                                             * the input vects. */
  vect3<float>          dis, d1, d2, d3;
  static p_rec          ver(8);
  r32                   s, d;
  
  if (zmin > p1[_z]) zmin=p1[_z];
  if (zmin > p2[_z]) zmin=p1[_z];

  /* setup */
  dis=p2-p1;
  d = dis.length();
  if (d == (r32) 0.0)
    return;
  s = d * thick;
  s = (s < (r32) min_thick) ? min_thick : s;
  s *= 0.5;  

  /* d1 */
  d1=dis;
  d1.normalize();
  
  /* d2 */
  d2=up;
  d2.normalize();
  
  /* d3 */
  d3 = d1 * d2;
  d3.normalize();
  
  /* base 1, 3 */
  d1 = d2 + d3;
  d1.normalize();
  ver[0].plus_fac(p1, d1, s);
  ver[2].plus_fac(p1, d1, -s);
  
  /* base 2, 4 */
  d1=d2-d3;
  d1.normalize();
  ver[1].plus_fac(p1, d1,  s);
  ver[3].plus_fac(p1, d1, -s);
  
  /* end */
  for (s16 i = 0; i < 4; i++){
    ver[i+4] = ver[i] + dis;
  };
  //8 vertices
  LgraphicsClassPtr->StoreHexahedron(&ver, c);
}

void Lturtle::Define_triangles(p_rec *ver, u16 c)
{
  if (ver->size()>=3) {
    LgraphicsClassPtr->StoreTriangleStrip(ver,c);
  };
}


void Lturtle::Set_rot(r32 a, const vect3<float> n)
{                           /* Set up a rotation matrix */
    r32                     n11, n22, n33, nxy, nxz, nyz, sina, cosa;

    cosa = cos(a);
    sina = sin(a);

    n11 = n[_x] * n[_x];
    n22 = n[_y] * n[_y];
    n33 = n[_z] * n[_z];

    nxy = n[_x] * n[_y];
    nxz = n[_x] * n[_z];
    nyz = n[_y] * n[_z];

    C1[_x] = n11 + (one - n11) * cosa;
    C1[_y] = nxy * (one - cosa) - n[_z] * sina;
    C1[_z] = nxz * (one - cosa) + n[_y] * sina;

    C2[_x] = nxy * (one - cosa) + n[_z] * sina;
    C2[_y] = n22 + (one - n22) * cosa;
    C2[_z] = nyz * (one - cosa) - n[_x] * sina;

    C3[_x] = nxz * (one - cosa) - n[_y] * sina;
    C3[_y] = nyz * (one - cosa) + n[_x] * sina;
    C3[_z] = n33 + (one - n33) * cosa;
}

