#include "qwt3d_coordsys.h"

using namespace std;
using namespace Qwt3D;

CoordinateSystem::CoordinateSystem(Triple first, Triple second, COORDSTYLE st)
{
	autodecoration_ = true;
	axes = std::vector<Axis>(12);
	setStyle(st);
	setLineSmooth(true);
	init(first,second);

	setAxesColor(RGBA(0,0,0,1));
	setGridLinesColor(RGBA(0.2,0.2,0.2,1));
	setNumberFont("Courier", 12);
	setNumberColor(RGBA(0,0,0));
	setLabelFont("Courier", 14, QFont::Bold);
	setGridLines(false, false);
}

CoordinateSystem::~CoordinateSystem()
{
	destroy();
}

void CoordinateSystem::destroy()
{
	for (unsigned i=0; i!=axes.size(); ++i)
		axes[i].setLabelString("");

	detachAll();
}

void CoordinateSystem::init(Triple first, Triple second)
{
	destroy();
	
	for (unsigned i = 0; i != axes.size(); ++i)
		axes[i].setScale(LINEARSCALE);

	setPosition(first, second);
	
	double majl = (second - first).length() / 100; // 1 %
	setTicLength(majl, 0.6 * majl);

	axes[X1].setTicOrientation(0,-1,0);
	axes[Y1].setTicOrientation(-1,0,0);
	axes[Z1].setTicOrientation(-1,0,0);

	// remaining x axes 
	axes[X2].setTicOrientation(0,-1,0);
	axes[X3].setTicOrientation(0,1,0);
	axes[X4].setTicOrientation(0,1,0);
	
	// remaining y axes 
	axes[Y2].setTicOrientation(1,0,0);
	axes[Y3].setTicOrientation(1,0,0);
	axes[Y4].setTicOrientation (-1,0,0);

	// remaining z axes 
	axes[Z2].setTicOrientation(-1,0,0);
	axes[Z4].setTicOrientation(1,0,0);
	axes[Z3].setTicOrientation(1,0,0);
	
	setStyle(style_);
}

void CoordinateSystem::draw()
{	
//	saveGLState();
	
	GLStateBewarer sb(GL_LINE_SMOOTH, true);
	
	if (!lineSmooth())
		sb.turnOff();
		

	if (autoDecoration())
		chooseAxes();
	
	Drawable::draw();

	if( style_ == NOCOORD)
		return;

  if (majorgridlines_ || minorgridlines_)
    recalculateAxesTics();
  if (majorgridlines_)
    drawMajorGridLines();
	if (minorgridlines_)
		drawMinorGridLines();
	
	//	restoreGLState();
}


//! build convex hull (6 axes: 2 x, 2 y, 2 z) and choose one of them at a time for scales, labels etc.  
void CoordinateSystem::chooseAxes()
{
	vector<Triple> beg(axes.size());
	vector<Triple> end(axes.size());
	vector<Tuple> src(2*axes.size());

	unsigned i;
	// collect axes viewport coordinates and initialize
	for (i=0; i!=axes.size(); ++i)
	{
		if (style() != NOCOORD)
			attach(&axes[i]);
		
		beg[i] = World2ViewPort(axes[i].begin());
		end[i] = World2ViewPort(axes[i].end());
		src[i] = Tuple(beg[i].x, beg[i].y);
		src[axes.size()+i] = Tuple(end[i].x, end[i].y);

		axes[i].setScaling(false);
		axes[i].setNumbers(false);
		axes[i].setLabel(false);
		axes[i].setDecorate(false);
	}

	vector<unsigned> idx;
	convexhull2d(idx,src);

	int rem_x = -1;
	int rem_y = -1;
	int rem_z = -1;


	bool left;

	int choice_x = -1; 
	int	choice_y = -1;
	int choice_z = -1;

  int other_x = -1;
  int other_y = -1;
  int other_z = -1;

	//traverse convex hull
	for (unsigned k=0; k!=idx.size(); ++k)
	{
		Triple one, two;
		
		if (idx[k] >= axes.size()) // is end point
			one = end[idx[k]-axes.size()];
		else                       // is begin point  
			one = beg[idx[k]];

		unsigned int next = idx[(k+1) % idx.size()];  // next point in cv (considered as ring buffer of points)

		if (next >= axes.size()) 
			two = end[next-axes.size()];
		else
			two = beg[next];
		
		for (i=0; i!=axes.size(); ++i)
		{			
			if (
					(one == beg[i] && two == end[i])
					||
					(two == beg[i] && one == end[i])
				 )
			{
				if (i==X1 || i==X2 || i==X3 || i==X4)  // x Achsen
				{
					if (rem_x>=0) // schon zweite Achse der konvexen Huelle ?
					{
						// untere der beiden x Achsen
						double y = min(min(end[rem_x].y,end[i].y),min(beg[rem_x].y,beg[i].y));
						choice_x = (y == beg[i].y || y == end[i].y) ? i : rem_x;
												
						other_x = (choice_x == (int)i) ? rem_x : (int)i;
						left = (beg[choice_x].x < beg[other_x].x || end[choice_x].x < end[other_x].x) 
							? true
							: false;
						
						autoDecorateExposedAxis(axes[choice_x], left);

						rem_x = -1;
					}
					else
					{
						rem_x = i;
					}
				}
				else if (i==Y1 || i==Y2 || i==Y3 || i==Y4)
				{
					if (rem_y>=0)
					{
						// untere der beiden y Achsen
						double y = min(min(end[rem_y].y,end[i].y),min(beg[rem_y].y,beg[i].y));
						choice_y = (y == beg[i].y || y == end[i].y) ? i : rem_y;
						
						other_y = (choice_y == (int)i) ? rem_y : (int)i;
						left = (beg[choice_y].x < beg[other_y].x || end[choice_y].x < end[other_y].x) 
							? true
							: false;
						autoDecorateExposedAxis(axes[choice_y], left);

						rem_y = -1;
					}
					else
					{
						rem_y = i;
					}
				}
				else if (i==Z1 || i==Z2 || i==Z3 || i==Z4)
				{
					if (rem_z>=0)
					{
						// hintere der beiden z Achsen
						double z = max(max(end[rem_z].z,end[i].z),max(beg[rem_z].z,beg[i].z));
						choice_z = (z == beg[i].z || z == end[i].z) ? i : rem_z;

						other_z = (choice_z == (int)i) ? rem_z : (int)i;
												
						rem_z = -1;

					}
					else
					{
						rem_z = i;
					}
				}
			}
		} // for axes
	} // for idx

	// fit z axis in - the onthewall axis if the decorated axes build a continous line, the opposite else 
	if (choice_x>=0 && choice_y>=0 && choice_z>=0)
	{
		left = (beg[choice_z].x < beg[other_z].x || end[choice_z].x < end[other_z].x) 
			? true
			: false;
		

		if (
					axes[choice_z].begin() == axes[choice_x].begin() 
			||	axes[choice_z].begin() == axes[choice_x].end()
			||	axes[choice_z].begin() == axes[choice_y].begin() 
			||	axes[choice_z].begin() == axes[choice_y].end()
			||	axes[choice_z].end() == axes[choice_x].begin() 
			||	axes[choice_z].end() == axes[choice_x].end()
			||	axes[choice_z].end() == axes[choice_y].begin() 
			||	axes[choice_z].end() == axes[choice_y].end()
			
			)
		{
			autoDecorateExposedAxis(axes[choice_z], left);
		}

		else
		{
			autoDecorateExposedAxis(axes[other_z], !left);
			choice_z = other_z; // for FRAME
		}
	}
	
	if (style() == FRAME)
	{
		for (i=0; i!=axes.size(); ++i)
		{
			if ((int)i!=choice_x && (int)i!=choice_y && (int)i!=choice_z)
				detach(&axes[i]);
		}
	}

}


void CoordinateSystem::autoDecorateExposedAxis(Axis& ax, bool left)
{
	Triple diff = World2ViewPort(ax.end()) - World2ViewPort(ax.begin());

	diff = Triple(diff.x,diff.y,0); // projection
	
	double s = diff.length();
	
	if (!s)
		return;

	ax.setScaling(true);
	ax.setNumbers(true);
	ax.setLabel(true);
	ax.setDecorate(true);

	const double SQRT_2 = 0.7071067;
	double sina = fabs(diff.y / s);


	if (left) // leftmost (compared with antagonist in CV)  axis -> draw decorations on the left side
	{
		if ( diff.x >= 0 && diff.y >= 0 && sina < SQRT_2)          // 0..Pi/4 
		{
			ax.setNumberAnchor(BottomCenter);
		}
		else if ( diff.x >= 0 && diff.y >= 0  && !left)            // octant 2
		{
			ax.setNumberAnchor(CenterRight);
		}
		else if ( diff.x <= 0 && diff.y >= 0  && sina >= SQRT_2)    // octant 3
		{
			ax.setNumberAnchor(CenterRight);
		}
		else if ( diff.x <= 0 && diff.y >= 0 )                      // octant 4
		{
			ax.setNumberAnchor(TopCenter);
		}
		else if ( diff.x <= 0 && diff.y <= 0  && sina <= SQRT_2)    // octant 5
		{
			ax.setNumberAnchor(BottomCenter);
		}
		else if ( diff.x <= 0 && diff.y <= 0)                      // octant 6
		{
			ax.setNumberAnchor(CenterRight);
		}
		else if ( diff.x >= 0 && diff.y <= 0  && sina >= SQRT_2)    // octant 7
		{
			ax.setNumberAnchor(CenterRight);
		}
		else if ( diff.x >= 0 && diff.y <= 0)                      // octant 8
		{
			ax.setNumberAnchor(TopCenter);
		}
	}	
	else // rightmost axis
	{
		if ( diff.x >= 0 && diff.y >= 0 && sina <= SQRT_2)
		{
			ax.setNumberAnchor(TopCenter);
		}
		else if ( diff.x >= 0 && diff.y >= 0  && !left) 
		{
			ax.setNumberAnchor(CenterLeft);
		}
		else if ( diff.x <= 0 && diff.y >= 0  && sina >= SQRT_2) 
		{
			ax.setNumberAnchor(CenterLeft);
		}
		else if ( diff.x <= 0 && diff.y >= 0) 
		{
			ax.setNumberAnchor(BottomCenter);
		}
		else if ( diff.x <= 0 && diff.y <= 0  && sina <= SQRT_2) 
		{
			ax.setNumberAnchor(TopCenter);
		}
		else if ( diff.x <= 0 && diff.y <= 0) 
		{
			ax.setNumberAnchor(CenterLeft);
		}
		else if ( diff.x >= 0 && diff.y <= 0  && sina >= SQRT_2) 
		{
			ax.setNumberAnchor(CenterLeft);
		}
		else if ( diff.x >= 0 && diff.y <= 0) 
		{
			ax.setNumberAnchor(BottomCenter);
		}
	}
}


void CoordinateSystem::setPosition(Triple first, Triple second)
{
	first_ = first;
	second_ = second;

	Triple dv = second - first;

	axes[X1].setPosition(first, first+Triple(dv.x,    0,     0));	// front bottom x
	axes[Y1].setPosition(first, first+Triple(   0, dv.y,     0));	// bottom left  y
	axes[Z1].setPosition (first+Triple(0, dv.y, 0), first+Triple(0, dv.y, dv.z));	// back left z

	axes[X1].setLimits(first.x, second.x);
	axes[X2].setLimits(first.x, second.x);
	axes[X3].setLimits(first.x, second.x);
	axes[X4].setLimits(first.x, second.x);

	axes[Y1].setLimits(first.y, second.y);
	axes[Y2].setLimits(first.y, second.y);
	axes[Y3].setLimits(first.y, second.y);
	axes[Y4].setLimits(first.y, second.y);

	axes[Z1].setLimits(first.z, second.z);
	axes[Z2].setLimits(first.z, second.z);
	axes[Z3].setLimits(first.z, second.z);
	axes[Z4].setLimits(first.z, second.z);

	// remaining x axes
	axes[X2].setPosition(first+Triple( 0,    0, dv.z), first+Triple( dv.x,    0, dv.z));	// front top x
	axes[X3].setPosition(first+Triple( 0, dv.y, dv.z), second);	// back top x
	axes[X4].setPosition(first+Triple( 0, dv.y,    0), first+Triple( dv.x, dv.y,    0));	// back bottom x

	// remaining y axes
	axes[Y2].setPosition(first+Triple(dv.x, 0,    0), first+Triple(dv.x, dv.y,  0)); // bottom right y
	axes[Y3].setPosition(first+Triple(dv.x, 0, dv.z), second); // top right y
	axes[Y4].setPosition(first+Triple(0,    0, dv.z), first+Triple(0,  dv.y, dv.z)); // top left y

	// remaining z axes
	axes[Z2].setPosition(first, first+Triple(   0,    0,  dv.z));	// front left z
	axes[Z4].setPosition(first+Triple(dv.x, dv.y, 0), second );	// back right z
	axes[Z3].setPosition(first+Triple(dv.x,    0, 0), first+Triple(dv.x,    0, dv.z));	// front right z
}

void CoordinateSystem::setTicLength(double major, double minor)
{
	for (unsigned i=0; i!=axes.size(); ++i)
		axes[i].setTicLength(major, minor);
}

void CoordinateSystem::adjustNumbers(int val)
{
	for (unsigned i=0; i!=axes.size(); ++i)
		axes[i].adjustNumbers(val);
}

void CoordinateSystem::adjustLabels(int val)
{
	for (unsigned i=0; i!=axes.size(); ++i)
		axes[i].adjustLabel(val);
}

void CoordinateSystem::setAutoScale(bool val)
{
	for (unsigned i=0; i!=axes.size(); ++i)
		axes[i].setAutoScale(val);
}

void CoordinateSystem::setAxesColor(RGBA val)
{
	for (unsigned i=0; i!=axes.size(); ++i)
		axes[i].setColor(val);
}

void CoordinateSystem::recalculateAxesTics()
{
	for (unsigned i=0; i!=axes.size(); ++i)
		axes[i].recalculateTics();
}

void CoordinateSystem::setNumberFont(QString const& family, int pointSize, int weight, bool italic)
{
	for (unsigned i=0; i!=axes.size(); ++i)
		axes[i].setNumberFont(family,pointSize,weight,italic);
}

void CoordinateSystem::setNumberFont(QFont const& font)
{
	for (unsigned i=0; i!=axes.size(); ++i)
		axes[i].setNumberFont(font);
}

void CoordinateSystem::setNumberColor(RGBA val)
{
	for (unsigned i=0; i!=axes.size(); ++i)
		axes[i].setNumberColor( val);
}

void CoordinateSystem::setStandardScale()
{
	for (unsigned i=0; i!=axes.size(); ++i)
    axes[i].setScale(LINEARSCALE);
}

void CoordinateSystem::setLabelFont(QFont const& font)
{
	for (unsigned i=0; i!=axes.size(); ++i)
		axes[i].setLabelFont(font);
}


void CoordinateSystem::setLabelFont(QString const& family, int pointSize, int weight, bool italic)
{
	setLabelFont(QFont(family,pointSize,weight,italic));
}

void CoordinateSystem::setLabelColor(RGBA val)
{
	for (unsigned i=0; i!=axes.size(); ++i)
		axes[i].setLabelColor(val);
}

void CoordinateSystem::setLineWidth(double val, double majfac, double minfac)
{
	for (unsigned i=0; i!=axes.size(); ++i)
		axes[i].setLineWidth(val, majfac, minfac);
}

void CoordinateSystem::setStyle(COORDSTYLE s, AXIS frame_1, AXIS frame_2, AXIS frame_3) 
{ 
	style_ = s;

	switch (s)
	{
		case NOCOORD:
			{
				for (unsigned i=0; i!=axes.size(); ++i)
					detach (&axes[i]);
			}
			break;
		case BOX:
			{
				for (unsigned i=0; i!=axes.size(); ++i)
					attach (&axes[i]);
			}
			break;
		case FRAME:
			{
				for (unsigned i=0; i!=axes.size(); ++i)
					detach (&axes[i]);
				if (!autoDecoration())
				{
					attach(&axes[frame_1]);
					attach(&axes[frame_2]);
					attach(&axes[frame_3]);
				}
			}
			break;
		default:
			break;
	}
}

/**
The axis used for tic calculation is chosen randomly from the respective pair.
For most cases an identical tic distribution is therefore recommended.
\param majors  Draw grid between major tics
\param minors  Draw grid between minor tics
\param sides   Side(s), where the grid should be drawn
*/
void CoordinateSystem::setGridLines(bool majors, bool minors, int sides) 
{
	sides_ = sides;
	majorgridlines_ = majors; 
	minorgridlines_ = minors;
} 

void CoordinateSystem::drawMajorGridLines()
{
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glColor4d(gridlinecolor_.r,gridlinecolor_.g,gridlinecolor_.b,gridlinecolor_.a);		
	setDeviceLineWidth(axes[X1].majLineWidth());

	std::vector<std::pair<Qwt3D::AXIS, Qwt3D::AXIS> > axis;
	if (sides_ & Qwt3D::FLOOR)  
	{
		axis.push_back(make_pair(X1,X4));
		axis.push_back(make_pair(Y1,Y2));
	}
	if (sides_ & Qwt3D::CEIL)  
	{
		axis.push_back(make_pair(X2,X3));
		axis.push_back(make_pair(Y3,Y4));
	}
	if (sides_ & Qwt3D::LEFT)  
	{
		axis.push_back(make_pair(Y1,Y4));
		axis.push_back(make_pair(Z1,Z2));
	}
	if (sides_ & Qwt3D::RIGHT)  
	{
		axis.push_back(make_pair(Y2,Y3));
		axis.push_back(make_pair(Z3,Z4));
	}
	if (sides_ & Qwt3D::FRONT)  
	{
		axis.push_back(make_pair(X1,X2));
		axis.push_back(make_pair(Z2,Z3));
	}
	if (sides_ & Qwt3D::BACK)  
	{
		axis.push_back(make_pair(X3,X4));
		axis.push_back(make_pair(Z4,Z1));
	}

	for(std::vector<std::pair<Qwt3D::AXIS, Qwt3D::AXIS> >::const_iterator it=axis.begin(); it!=axis.end(); ++it)
	{
		GridLine grid = GridLine(majorgridlines_, gridlinecolor_);
		if(!gridmajors_.value(it->first, grid).visible_)
			continue;

		Qwt3D::RGBA gridcolor = gridmajors_.value(it->first, grid).color_;
		glColor4d(gridcolor.r,gridcolor.g,gridcolor.b,gridcolor.a);

		setDeviceLineWidth(gridmajors_.value(it->first, grid).width_);

		glEnable(GL_LINE_STIPPLE);
		switch(gridmajors_.value(it->first, grid).style_)
		{
		case SOLID:
			glLineStipple(1, 0xFFFF);
			break;
		case DASH:
			glLineStipple(2, 0x7777);
			break;
		case DOT:
			glLineStipple(2, 0x1111);
			break;
		case DASHDOT:
			glLineStipple(2, 0x087F);
			break;
		case DASHDOTDOT:
			glLineStipple(2, 0x24FF);
			break;
		case SHORTDASH:
			glLineStipple(1, 0x7777);
			break;
		case SHORTDOT:
			glLineStipple(1, 0x1111);
			break;
		case SHORTDASHDOT:
			glLineStipple(1, 0x087F);
			break;
		}

		glBegin(GL_LINES);
		drawMajorGridLines(axes[it->first],axes[it->second]);
		glEnd();

		glDisable(GL_LINE_STIPPLE);
	}
}

void CoordinateSystem::drawMinorGridLines()
{
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glColor4d(gridlinecolor_.r,gridlinecolor_.g,gridlinecolor_.b,gridlinecolor_.a);		
	setDeviceLineWidth(axes[X1].minLineWidth());

	std::vector<std::pair<Qwt3D::AXIS, Qwt3D::AXIS> > axis;
	if (sides_ & Qwt3D::FLOOR)  
	{
		axis.push_back(make_pair(X1,X4));
		axis.push_back(make_pair(Y1,Y2));
	}
	if (sides_ & Qwt3D::CEIL)  
	{
		axis.push_back(make_pair(X2,X3));
		axis.push_back(make_pair(Y3,Y4));
	}
	if (sides_ & Qwt3D::LEFT)  
	{
		axis.push_back(make_pair(Y1,Y4));
		axis.push_back(make_pair(Z1,Z2));
	}
	if (sides_ & Qwt3D::RIGHT)  
	{
		axis.push_back(make_pair(Y2,Y3));
		axis.push_back(make_pair(Z3,Z4));
	}
	if (sides_ & Qwt3D::FRONT)  
	{
		axis.push_back(make_pair(X1,X2));
		axis.push_back(make_pair(Z2,Z3));
	}
	if (sides_ & Qwt3D::BACK)  
	{
		axis.push_back(make_pair(X3,X4));
		axis.push_back(make_pair(Z4,Z1));
	}

	for(std::vector<std::pair<Qwt3D::AXIS, Qwt3D::AXIS> >::const_iterator it=axis.begin(); it!=axis.end(); ++it)
	{
		GridLine grid = GridLine(minorgridlines_, gridlinecolor_);
		if(!gridminors_.value(it->first, grid).visible_)
			continue;
	
		Qwt3D::RGBA gridcolor = gridminors_.value(it->first, grid).color_;
		glColor4d(gridcolor.r,gridcolor.g,gridcolor.b,gridcolor.a);

		setDeviceLineWidth(gridminors_.value(it->first, grid).width_);
		
		glEnable(GL_LINE_STIPPLE);
		switch(gridminors_.value(it->first, grid).style_)
		{
		case SOLID:
			glLineStipple(1, 0xFFFF);
			break;
		case DASH:
			glLineStipple(2, 0x7777);
			break;
		case DOT:
			glLineStipple(2, 0x1111);
			break;
		case DASHDOT:
			glLineStipple(2, 0x087F);
			break;
		case DASHDOTDOT:
			glLineStipple(2, 0x24FF);
			break;
		case SHORTDASH:
			glLineStipple(1, 0x7777);
			break;
		case SHORTDOT:
			glLineStipple(1, 0x1111);
			break;
		case SHORTDASHDOT:
			glLineStipple(1, 0x087F);
			break;
		}

		glBegin(GL_LINES);
		drawMinorGridLines(axes[it->first],axes[it->second]);
		glEnd();

		glDisable(GL_LINE_STIPPLE);
	}
}

void CoordinateSystem::drawMajorGridLines(Axis& a0, Axis& a1)
{
  Triple d = a1.begin()-a0.begin();

  for (unsigned int i=0; i!=a0.majorPositions().size(); ++i)
	{
		glVertex3d( a0.majorPositions()[i].x, a0.majorPositions()[i].y, a0.majorPositions()[i].z ); 
		glVertex3d( a0.majorPositions()[i].x + d.x, a0.majorPositions()[i].y + d.y, a0.majorPositions()[i].z +d.z); 
	}
}

void CoordinateSystem::drawMinorGridLines(Axis& a0, Axis& a1)
{
  Triple d = a1.begin()-a0.begin();

  for (unsigned int i=0; i!=a0.minorPositions().size(); ++i)
	{
		glVertex3d( a0.minorPositions()[i].x, a0.minorPositions()[i].y, a0.minorPositions()[i].z ); 
		glVertex3d( a0.minorPositions()[i].x + d.x, a0.minorPositions()[i].y + d.y, a0.minorPositions()[i].z +d.z); 
	}
}
