#include <math.h>
#include "qwt3d_color.h"
#include "qwt3d_plot.h"
#include "qwt3d_enrichment_std.h"

using namespace Qwt3D;


/////////////////////////////////////////////////////////////////
//
//   CrossHair
//
/////////////////////////////////////////////////////////////////

CrossHair::CrossHair()
{
  configure(0, 1, false, false);
}

CrossHair::CrossHair(double rad, double linewidth, bool smooth, bool boxed)
{
  configure(rad, linewidth, smooth, boxed);
}

void CrossHair::configure(double rad, double linewidth, bool smooth, bool boxed)
{
  plot = 0;
  radius_ = rad;
  linewidth_ = linewidth;
  smooth_ = smooth;
  boxed_ = boxed;
}

void CrossHair::drawBegin()
{
  setDeviceLineWidth( linewidth_ );
  oldstate_ = glIsEnabled(GL_LINE_SMOOTH);
  if (smooth_)
    glEnable(GL_LINE_SMOOTH);
  else
    glDisable(GL_LINE_SMOOTH);
	glBegin( GL_LINES );
}

void CrossHair::drawEnd()
{
  glEnd();

  if (oldstate_)
    glEnable(GL_LINE_SMOOTH);
  else
    glDisable(GL_LINE_SMOOTH);
}

void CrossHair::draw(Qwt3D::Triple const& pos)
{
	RGBA rgba = (*plot->dataColor())(pos);
	glColor4d(rgba.r,rgba.g,rgba.b,rgba.a);

  double diag = (plot->hull().maxVertex-plot->hull().minVertex).length() * radius_;

  glVertex3d( pos.x - diag, pos.y, pos.z); 
	glVertex3d( pos.x + diag, pos.y, pos.z); 
	
  glVertex3d( pos.x, pos.y - diag, pos.z); 
	glVertex3d( pos.x, pos.y + diag, pos.z); 
  
  glVertex3d( pos.x, pos.y, pos.z - diag); 
	glVertex3d( pos.x, pos.y, pos.z + diag); 

  // hull
  
  if (!boxed_)
    return;

  glVertex3d( pos.x - diag, pos.y - diag, pos.z + diag); 
	glVertex3d( pos.x + diag, pos.y - diag, pos.z + diag); 
  glVertex3d( pos.x - diag, pos.y - diag, pos.z - diag); 
	glVertex3d( pos.x + diag, pos.y - diag, pos.z - diag); 
  
  glVertex3d( pos.x - diag, pos.y + diag, pos.z + diag); 
	glVertex3d( pos.x + diag, pos.y + diag, pos.z + diag); 
  glVertex3d( pos.x - diag, pos.y + diag, pos.z - diag); 
	glVertex3d( pos.x + diag, pos.y + diag, pos.z - diag); 

  glVertex3d( pos.x - diag, pos.y - diag, pos.z + diag); 
	glVertex3d( pos.x - diag, pos.y + diag, pos.z + diag); 
  glVertex3d( pos.x - diag, pos.y - diag, pos.z - diag); 
	glVertex3d( pos.x - diag, pos.y + diag, pos.z - diag); 
  
  glVertex3d( pos.x + diag, pos.y - diag, pos.z + diag); 
	glVertex3d( pos.x + diag, pos.y + diag, pos.z + diag); 
  glVertex3d( pos.x + diag, pos.y - diag, pos.z - diag); 
	glVertex3d( pos.x + diag, pos.y + diag, pos.z - diag); 

  glVertex3d( pos.x - diag, pos.y - diag, pos.z - diag); 
	glVertex3d( pos.x - diag, pos.y - diag, pos.z + diag); 
  glVertex3d( pos.x + diag, pos.y - diag, pos.z - diag); 
	glVertex3d( pos.x + diag, pos.y - diag, pos.z + diag); 
  
  glVertex3d( pos.x - diag, pos.y + diag, pos.z - diag); 
	glVertex3d( pos.x - diag, pos.y + diag, pos.z + diag); 
  glVertex3d( pos.x + diag, pos.y + diag, pos.z - diag); 
	glVertex3d( pos.x + diag, pos.y + diag, pos.z + diag); 
}

/////////////////////////////////////////////////////////////////
//
//   Dot
//
/////////////////////////////////////////////////////////////////

Dot::Dot()
{
  configure(1, false);
}

Dot::Dot(double pointsize, bool smooth)
{
  configure(pointsize, smooth);
}

void Dot::configure(double pointsize, bool smooth)
{
  plot = 0;
  pointsize_ = pointsize;
  smooth_ = smooth;
}

void Dot::drawBegin()
{
  setDevicePointSize( pointsize_ );
  oldstate_ = glIsEnabled(GL_POINT_SMOOTH);
  if (smooth_)
    glEnable(GL_POINT_SMOOTH);
  else
    glDisable(GL_POINT_SMOOTH);

  //glPointSize(10);
	glBegin( GL_POINTS );
}

void Dot::drawEnd()
{
  glEnd();

  if (oldstate_)
    glEnable(GL_POINT_SMOOTH);
  else
    glDisable(GL_POINT_SMOOTH);
}

void Dot::draw(Qwt3D::Triple const& pos)
{
	RGBA rgba = (*plot->dataColor())(pos);
  glColor4d(rgba.r,rgba.g,rgba.b,rgba.a);
  glVertex3d( pos.x, pos.y, pos.z);   
}


/////////////////////////////////////////////////////////////////
//
//   Cone
//
/////////////////////////////////////////////////////////////////

Cone::Cone()
{
	hat      = gluNewQuadric();
	disk     = gluNewQuadric();

  configure(0, 3);
}

Cone::Cone(double rad, unsigned quality)
{
	hat      = gluNewQuadric();
	disk     = gluNewQuadric();

  configure(rad, quality);
}

Cone::~Cone()
{
	gluDeleteQuadric(hat);
	gluDeleteQuadric(disk);
}

void Cone::configure(double rad, unsigned quality)
{
  plot = 0;
  radius_ = rad;
  quality_ = quality;
  oldstate_ = GL_FALSE;

	gluQuadricDrawStyle(hat,GLU_FILL);
	gluQuadricNormals(hat,GLU_SMOOTH);
	gluQuadricOrientation(hat,GLU_OUTSIDE);
	gluQuadricDrawStyle(disk,GLU_FILL);
	gluQuadricNormals(disk,GLU_SMOOTH);
	gluQuadricOrientation(disk,GLU_OUTSIDE);
}

void Cone::draw(Qwt3D::Triple const& pos)
{  
	RGBA rgba = (*plot->dataColor())(pos);
  glColor4d(rgba.r,rgba.g,rgba.b,rgba.a);

  GLint mode;
	glGetIntegerv(GL_MATRIX_MODE, &mode);
	glMatrixMode( GL_MODELVIEW );
  glPushMatrix();

  glTranslatef(pos.x, pos.y, pos.z);

  gluCylinder(hat, 0.0, radius_, radius_*2, quality_, 1);
  glTranslatef(0, 0, radius_*2);
	gluDisk(disk, 0.0, radius_, quality_, 1);

  glPopMatrix();
	glMatrixMode(mode);
}


/////////////////////////////////////////////////////////////////
//
//   Arrow
//
/////////////////////////////////////////////////////////////////

Arrow::Arrow()
{	
	hat      = gluNewQuadric();
	disk     = gluNewQuadric();
	base    = gluNewQuadric();
	bottom   = gluNewQuadric();

	gluQuadricDrawStyle(hat,GLU_FILL);
	gluQuadricNormals(hat,GLU_SMOOTH);
	gluQuadricOrientation(hat,GLU_OUTSIDE);
	gluQuadricDrawStyle(disk,GLU_FILL);
	gluQuadricNormals(disk,GLU_SMOOTH);
	gluQuadricOrientation(disk,GLU_OUTSIDE);
	gluQuadricDrawStyle(base,GLU_FILL);
	gluQuadricNormals(base,GLU_SMOOTH);
	gluQuadricOrientation(base,GLU_OUTSIDE);
	gluQuadricDrawStyle(bottom,GLU_FILL);
	gluQuadricNormals(bottom,GLU_SMOOTH);
	gluQuadricOrientation(bottom,GLU_OUTSIDE);

	configure(3, 0.4, 0.06, 0.02);
}

Arrow::~Arrow()
{
	gluDeleteQuadric(hat);
	gluDeleteQuadric(disk);
	gluDeleteQuadric(base);
	gluDeleteQuadric(bottom);
}

/**
\param segs number of faces for the fields arrows (see the gallery for examples)
\param relconelength see picture
\param relconerad see picture
\param relstemrad see picture
\image html arrowanatomy.png 
*/
void Arrow::configure(int segs, double relconelength, double relconerad, double relstemrad)
{
	plot = 0;
  segments_ = segs;
  oldstate_ = GL_FALSE;
	rel_cone_length = relconelength;
	rel_cone_radius = relconerad;
	rel_stem_radius = relstemrad;	
}

void Arrow::draw(Qwt3D::Triple const& pos)
{	
	Triple end = top_;
	Triple beg = pos;
	Triple vdiff = end-beg;
	double length = vdiff.length();
	glColor4d(rgba_.r,rgba_.g,rgba_.b,rgba_.a);

	double radius[2];
	radius[0] = rel_cone_radius * length;
	radius[1] = rel_stem_radius * length;
	
	GLint mode;
	glGetIntegerv(GL_MATRIX_MODE, &mode);

	glMatrixMode( GL_MODELVIEW );
  glPushMatrix();


	Triple axis;
	double phi = calcRotation(axis, FreeVector(beg,end));
	
	glTranslatef(beg.x, beg.y, beg.z);
  glRotatef(phi, axis.x, axis.y, axis.z);

	double baseheight = (1-rel_cone_length) * length;
	
	glTranslatef(0, 0, baseheight);

	gluCylinder(hat, radius[0], 0.0, rel_cone_length * length, segments_,1);
	gluDisk(disk,radius[1],radius[0], segments_,1);
	
	glTranslatef(0, 0, -baseheight);
	
	gluCylinder(base, radius[1],radius[1], baseheight,segments_,1);
	gluDisk(disk,0,radius[1],segments_,1);

  glPopMatrix();
	glMatrixMode(mode);
}


//! transform a vector on the z axis with length |beg-end|, to get them in coincidence with the vector(beg,end)
/**
	\return Angle in degree to rotate
	\param axis   The axis to rotate around
	\param beg    result vector base point
	\param end    result vector top point
*/ 
double Arrow::calcRotation(Triple& axis, FreeVector const& vec)
{
	
	Triple end = vec.top;
	Triple beg = vec.base;

	Triple firstbeg(0.0,0.0,0.0);
	Triple firstend(0.0,0.0,(end-beg).length());
	
	Triple first = firstend - firstbeg;
	first.normalize();

	Triple second = end-beg;
	second.normalize();

	axis = normalizedcross(first,second);
	double cosphi = dotProduct(first,second);
	
	return 180 * acos(cosphi) / Qwt3D::PI;
}
