#ifdef WIN32
  #include <windows.h>
#endif

#include <GL/glu.h>
#include "Ltypes.h"
#include "extra_math.h"
#include "GL_Lsystem.h"

void LightClass::set_position(GLfloat a, GLfloat b, GLfloat c, GLfloat d)
{
  position[0]=a;
  position[1]=b;
  position[2]=c;
  position[3]=d;
}

void LightClass::set_colour(GLfloat a, GLfloat b, GLfloat c, GLfloat d)
{
  colour[0]=a;
  colour[1]=b;
  colour[2]=c;
  colour[3]=d;
}


GL_Lsystem::GL_Lsystem(UIClass                 *UserInterface, 
		       vector<vect3<GLfloat> > *ColourArray,
		       vect3<GLfloat>          *BackgroundColour,
		       vector<LightClass>      *Lights,
		       MutationParameters      *mut_param)
{
  the_BackgroundColour = BackgroundColour;
  the_Lights  = Lights;
  centre_point[0]=0;
  centre_point[1]=0;
  centre_point[2]=0;
  do_init     = true;
  draw_buffer = true;
 
  the_GLGraphics = new GLGraphics(ColourArray);
  the_Lstring    = new Lstring(UserInterface, mut_param);
  the_Lturtle    = new Lturtle(UserInterface);
  the_Lturtle->set_Lgraphics(the_GLGraphics);
  the_Lturtle->set_Lstring(the_Lstring);
}

GL_Lsystem::~GL_Lsystem()
{
  delete the_Lturtle;
  delete the_Lstring;
  delete the_GLGraphics;
}

//Output brings an Lstring out (which may have been mutated)
void GL_Lsystem::operator>>(string& out)
{
  *the_Lstring >> out;
}

//Input puts an Lstring in
void GL_Lsystem::operator<<(string p)
{
  *the_Lstring << p;
  ReparseString();
}

/***** Mutations *****/

void  GL_Lsystem::mutate(mutations mut)
{
  switch (mut) {
  case MUT_NONE:
    break;
  case MUT_GENERAL_PARAMETER:
    the_Lstring->mutate_General_Parameter();
    break;
  case MUT_GENERAL_ALGORITHM:
    the_Lstring->mutate_General_Algorithm();
    break;
  case MUT_RAT:
    the_Lstring->mutate_Variable();
    break;
  case MUT_PARAM:
    the_Lstring->mutate_ChangeRuleParameter();
    break;
  case MUT_ADD_RULE:
    the_Lstring->mutate_AddRule();
    break;
  case MUT_DEL_RULE:
    the_Lstring->mutate_DeleteRule();
    break;
  case MUT_ADD_TO:
    the_Lstring->mutate_ChangeRuleAdd();
    break;
  case MUT_DEL_FROM:
    the_Lstring->mutate_ChangeRuleDelete();
    break;
  };
  ReparseString();
}

/**********/

const string GL_Lsystem::get_FinalString()
{
  return this->the_Lstring->FormattedFinalString();
}

bool GL_Lsystem::ReparseString(int i)
{
  if (the_Lstring->Parse(i)) {
    draw_buffer = true;
    return (true);
  };
  return false;
};

bool GL_Lsystem::ReparseString()
{
  if (the_Lstring->Parse()) {
    the_Lturtle->SetRandSeed((int) Rnd()*3000);
    draw_buffer = true;
    return (true);
  };
  return false;
};

void GL_Lsystem::Output(Lgraphics *OutputClassType)
{
  double d1,d2,d3,d4;
  the_Lturtle->set_Lgraphics(OutputClassType);
  the_Lturtle->InitDraw(d1,d2,d3,d4);
  the_Lturtle->set_Lgraphics(the_GLGraphics);
}

void GL_Lsystem::mut_Lstring_from(GL_Lsystem *l, mutations mut)
{
  /* set the Lstrings equal, and reset the Lturtle */
  *the_Lstring=*(l->the_Lstring);
  mutate(mut);
}

void GL_Lsystem::set_Lstring_to(GL_Lsystem *l)
{
  mut_Lstring_from(l,MUT_NONE);
}

void GL_Lsystem::GL_draw(GLfloat &ZoomFactor, GLfloat *Centre, GLfloat *RotMatrix, GLfloat AspectRatio, sizing_type how_to_size)
{
  /* initialize OpenGL */
  if (do_init == true) {
    InitGL();
    do_init = false;
  };

  /* initialize Buffer */  
  if (draw_buffer == true) {
    double scale=1;
    the_Lturtle->InitDraw(centre_point[0],centre_point[1],centre_point[2],scale);
    scale_factor=scale/(2.0*sin(VIEW_ANGLE/360.0 * 3.14159));
    draw_buffer = false;
  };

  switch (how_to_size) {
  case CENTRE_AND_SCALE:
    ZoomFactor=scale_factor;
    Centre[0]=centre_point[0];
    Centre[1]=centre_point[1];
    Centre[2]=centre_point[2];
    break;
  case CENTRE:
    Centre[0]=centre_point[0];
    Centre[1]=centre_point[1];
    Centre[2]=centre_point[2];
    break;
  case DO_NOTHING:
    break;
  };

  /* view */
  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  gluPerspective( VIEW_ANGLE, AspectRatio, 10, 100000 );
  glMatrixMode( GL_MODELVIEW );
  
  /* clear */
  if (the_BackgroundColour!=NULL)
    glClearColor( (*the_BackgroundColour)[0], (*the_BackgroundColour)[1], (*the_BackgroundColour)[2], 1 );
  glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  
  /* transformations */
  glLoadIdentity();
  glTranslatef( 0, 0, -ZoomFactor ); //view from ZoomFactor units away
  glMultMatrixf( RotMatrix );

  /* centre */
  glTranslatef(-Centre[0],-Centre[1],-Centre[2]);
  
  /* draw object */
  if (the_Lturtle!=NULL)
    the_Lturtle->Draw();

  /* draw scale bar */
  //not implemented yet...

   /* flush */
  glFlush();
  
}

/*************** Private Methods ****************/

void GL_Lsystem::InitGL(void)
{
    /* remove back faces */
  glDisable(GL_CULL_FACE);
  glEnable(GL_DEPTH_TEST);
  
  /* speedups */
  glEnable(GL_DITHER);
  glShadeModel(GL_SMOOTH);
  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
  glHint(GL_POLYGON_SMOOTH_HINT, GL_FASTEST);
  
  /* light */
  InitLights();


  glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
  glEnable(GL_COLOR_MATERIAL);  
}

void GL_Lsystem::InitLights(void)
{
  if (the_Lights == NULL) {
    GLfloat light_pos[4]   = { -5.0f, 5.0f, 0.0f, 0.0f };
    GLfloat light_color[4] = { .6f, .6f, .6f, 1.0f }; /* white light */
    glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
    glLightfv(GL_LIGHT0, GL_DIFFUSE,  light_color);
    glEnable(GL_LIGHT0);
  } else {
    for (unsigned int i=0;i<the_Lights->size();i++)
      switch (i) {
      case 0:
	glLightfv(GL_LIGHT0, GL_POSITION, (*the_Lights)[i].position);
	glLightfv(GL_LIGHT0, GL_DIFFUSE,  (*the_Lights)[i].colour);
	glEnable(GL_LIGHT0); break;
      case 1:
	glLightfv(GL_LIGHT1, GL_POSITION, (*the_Lights)[i].position);
	glLightfv(GL_LIGHT1, GL_DIFFUSE,  (*the_Lights)[i].colour);
	glEnable(GL_LIGHT1); break;
      case 2:
	glLightfv(GL_LIGHT2, GL_POSITION, (*the_Lights)[i].position);
	glLightfv(GL_LIGHT2, GL_DIFFUSE,  (*the_Lights)[i].colour);
	glEnable(GL_LIGHT2); break;
      case 3:
	glLightfv(GL_LIGHT3, GL_POSITION, (*the_Lights)[i].position);
	glLightfv(GL_LIGHT3, GL_DIFFUSE,  (*the_Lights)[i].colour);
	glEnable(GL_LIGHT3); break;
      case 4:
	glLightfv(GL_LIGHT4, GL_POSITION, (*the_Lights)[i].position);
	glLightfv(GL_LIGHT4, GL_DIFFUSE,  (*the_Lights)[i].colour);
	glEnable(GL_LIGHT4); break;
      case 5: //allow up to 5 lights
	glEnable(GL_LIGHTING);
	return ;
      };
  };
  
  glEnable(GL_LIGHTING);
}
