#ifdef __GNUG__
#pragma implementation
#pragma interface
#endif

#define EDGE_PIXEL 1

#ifdef WIN32
#undef EDGE_PIXEL
#define EDGE_PIXEL 0
#include <windows.h>
#endif

#include <stdio.h>
#include <string>
#include <GL/glu.h>
#include <GL/gl.h>
#include "LsysGLCanvas.h"

// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif

extern "C" {
#include "trackball.h"
}

using namespace std;
/****** strings for error messages **********/
const wxString prog_error_title="Error in ";
const wxString prog_error="Error in method ";
const wxString errors[]={
  "Empty line found. This line has been removed from the L string",
  "L-string has invalid recursion parameter. A default line has been substituted",
  "L-string has invalid default angle parameter. A default line has been substituted",
  "L-string has invalid starting thickness parameter. A default line has been substituted",
  "The program has run out of new rule characters. No new rules can be added",
  "An attempt to substitute two rules with the same name has been detected",
  "Out of memory"
};

/***** viewpoint class *******/

viewpoint_info viewpoint_info::operator =(const viewpoint_info &vi)
{
	int i;
	this->zoom   = vi.zoom;
	for (i=0;i<4;i++)
		this->quat[i]=vi.quat[i];
	for (i=0;i<3;i++)
		this->centre[i]=vi.centre[i];
	return *this;
}

/*********************** LsysGLCanvas Class ********************
 ****(this also provides the user interface for the Lsystem ****/

BEGIN_EVENT_TABLE(LsysGLCanvas, wxGLCanvas)
  EVT_SIZE(LsysGLCanvas::OnSize)
  EVT_PAINT(LsysGLCanvas::OnPaint)
  EVT_ERASE_BACKGROUND(LsysGLCanvas::OnEraseBackground)
  EVT_MOUSE_EVENTS(LsysGLCanvas::OnMouse)
  EVT_KEY_DOWN(LsysGLCanvas::OnKeyDown)
END_EVENT_TABLE()

LsysGLCanvas::LsysGLCanvas(wxWindow *parent, 
			   float *min_thick_value,
			   vector<vect3<GLfloat> > *C_arr, 
			   vect3<GLfloat>          *BackgroundColour,
			   vector<LightClass>      *Lights,
			   MutationParameters      *mut_param,
			   LsysGLCanvas            **s,
			   vect3<GLfloat> *BorderCol,
			   vect3<GLfloat> *HighlightBorderCol, 
			   const wxPoint& pos, 
			   const wxSize&  size):
  wxGLCanvas(parent, -1, pos, size, 0 , "")
{
  int width, height;
  static vect3<GLfloat> def_b_col(0.0,0.0,0.0);
  static vect3<GLfloat> def_h_col(1.0,0.0,0.0);
  GetClientSize(& width, & height);
  AspectRatio=(double) width/height;
  sizing=CENTRE_AND_SCALE;
  reset_viewpoint();
  
  the_Selected = s;
  
  if (BorderCol==NULL)          b_col=&def_b_col;
  else                          b_col=BorderCol;
  if (HighlightBorderCol==NULL) h_col=&def_h_col;
  else                          h_col=HighlightBorderCol;

  min_thick    = min_thick_value;
  the_Lsystem  = new GL_Lsystem(this, C_arr, BackgroundColour, Lights, mut_param);
}

LsysGLCanvas::~LsysGLCanvas(void)
{
  delete the_Lsystem;
}

void LsysGLCanvas::OnKeyDown( wxKeyEvent& event )
{
  //event.Skip();
  GetParent()->ProcessEvent(event); //work around bug in wxGLCanvas
}

inline void LsysGLCanvas::Paint()
{
  GLfloat m[4][4];

#ifndef __WXMOTIF__
  if (!GetContext()) return;
#endif
  
  SetCurrent();
    
  build_rotmatrix( m, info.quat);

  /* draw the Lsystem */ 
  glLineWidth(1.0);
  the_Lsystem->GL_draw(info.zoom, info.centre, &m[0][0], AspectRatio, sizing);
  sizing=CENTRE;   //don't auto scale, to allow zooming


  /* draw the borders */
  int height, width;
  GetClientSize(& width, & height);
  glDisable(GL_LIGHTING);
  //set 1:1 mapping
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluOrtho2D(0.0, (GLfloat) width, 0.0, (GLfloat) height);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  //begin drawing
  glBegin(GL_LINE_LOOP);
    glLineWidth(1.0);
    if (this->Selected())
      glColor3f((*h_col)[0],(*h_col)[1],(*h_col)[2]);
    else
      glColor3f((*b_col)[0],(*b_col)[1],(*b_col)[2]);
    glVertex2s(0, 0);
    glVertex2s(0, height-EDGE_PIXEL);
    glVertex2s(width-EDGE_PIXEL,height-EDGE_PIXEL);
    glVertex2s(width-EDGE_PIXEL,0);
  glEnd();
  glEnable(GL_LIGHTING);

  /* swap */
  SwapBuffers();

}

void LsysGLCanvas::OnPaint( wxPaintEvent& event )
{
  wxPaintDC dc(this); //must always be here
  Paint();
}


void LsysGLCanvas::OnSize(wxSizeEvent& event)
{
    int width, height;
    GetClientSize(& width, & height);
    
#ifndef __WXMOTIF__
    if (GetContext())
#endif
    {
        SetCurrent();
        glViewport(0, 0, width, height);
	AspectRatio=((GLfloat) width)/height;
    };
}

void LsysGLCanvas::OnEraseBackground(wxEraseEvent& event)
{
    /* Do nothing, to avoid flashing on MSW */
}

void LsysGLCanvas::OnMouse( wxMouseEvent& event )
{
    GetParent()->ProcessEvent(event); //work around bug in wxGLCanvas meaning that Skip doesn't work;  
}

void LsysGLCanvas::SetNormalSize(int x, int y, int width, int height)
{
  normal_size_x=x;
  normal_size_y=y;
  normal_size_w=width;
  normal_size_h=height;
}

void LsysGLCanvas::SetToNormalSize()
{
  this->SetSize(normal_size_x, normal_size_y, normal_size_w, normal_size_h);
}




void LsysGLCanvas::display_Lstring_growing()
{
  int i=0;
  wxClientDC dc(this);  
  while (the_Lsystem->ReparseString(i++)) {
    sizing=DO_NOTHING;
    this->Paint();       //force paint
  };
}

void LsysGLCanvas::zoom(float amount)
{
  info.zoom+=amount;
  Refresh(FALSE);
}

void LsysGLCanvas::spin(float *spin_quat)
{
  add_quats( spin_quat, info.quat, info.quat );
  Refresh(FALSE);
}

void LsysGLCanvas::scale_to_fit()
{
  sizing=CENTRE_AND_SCALE;
  Refresh(FALSE);
}

void LsysGLCanvas::set_viewpoint_to(LsysGLCanvas &c)
{
  sizing=CENTRE;
  this->info=c.info;
  Refresh(FALSE);
}

void LsysGLCanvas::reset_viewpoint()
{
  sizing=CENTRE_AND_SCALE;
  info.zoom   = 1000;
  trackball( info.quat, 0.0, 0.0, 0.0, 0.0 );
}

void LsysGLCanvas::take_Picture(GLint x, GLint y, GLsizei width, GLsizei height, GLvoid *storage)
{
  glReadPixels(x,y,width,height,GL_RGB, GL_UNSIGNED_INT, storage); 
}

void   LsysGLCanvas::copy(LsysGLCanvas &c)
{
  this->the_Lsystem->set_Lstring_to(c.the_Lsystem);
  this->info=c.info;
  Refresh(FALSE);
}

void  LsysGLCanvas::mutate(mutations mut)
{
  this->the_Lsystem->mutate(mut);
  Refresh(FALSE);
}

void  LsysGLCanvas::copy_with_mutation(LsysGLCanvas &c, mutations mut)
{
  this->the_Lsystem->mut_Lstring_from(c.the_Lsystem, mut);
  this->info=c.info;
  Refresh(FALSE);
}

/* general */
bool LsysGLCanvas::Selected()
{
  if      (the_Selected==NULL)  return false;
  else if (*the_Selected==this) return true;
  else                          return false;
}


/* UI Class derivatives */
unsigned int LsysGLCanvas::get_MaxStringSize()
{
  return 100000;
}

unsigned int LsysGLCanvas::get_MaxLineLength()
{
  return 1000;
}

r32  LsysGLCanvas::get_min_thick()
{
  return (r32) (*min_thick);
}

bool LsysGLCanvas::is_rand_set()
{
   return false;
}

bool LsysGLCanvas::is_trope_set()
{
  return false;
}

r32  LsysGLCanvas::get_trope_amount()
{
return (r32) 0.0;
}

r32  LsysGLCanvas::get_tr()
{
return (r32) 0.2;
}

r32  LsysGLCanvas::get_dis()
{
return (r32) 100;
}

r32  LsysGLCanvas::rand_amount()
{
return (r32) 100;
}
 
//error messages
void LsysGLCanvas::ProgrammingError(char *method, char *text)
{
  wxString str1=prog_error_title;
  wxString str2=prog_error;
  str1+=method;
  str2+=method;
  str2+="\n";
  str2+=text;
  str2+="\nAborting...";
  wxMessageDialog td(this, str2, str1,  wxOK);
  td.ShowModal();
  abort();
}

void LsysGLCanvas::Error(ERRORS err)
{
  wxString str;
  str+=errors[err];
  wxMessageDialog td(this, str, "Error",  wxOK);
  td.ShowModal();
}
