/* -*- mia-c++ -*-
 *
 * This file is part of viewitgui - a library and program for the
 * visualization of 3D data sets. 
 *
 * Copyright (c) Leipzig, Madrid 1999-2013 Mirco Hellmann, Gert Wollny
 *
 * viewitgui is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * viewitgui is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with viewitgui; if not, see <http://www.gnu.org/licenses/>.
 */


#include <iostream>
#include <viewit/clip_plane.hh>


TClipPlane::TClipPlane(const string& name,int _no):
	TSlice(name),
	enabled(0),
	no(_no),
	eqn(0,0,1,-128),
	flipped(true)
{
	int maxnum; 
	glGetIntegerv(GL_MAX_CLIP_PLANES,&maxnum);
	if (no >= maxnum) 
		throw gl_error("request nonexistent clip plane");
	show();
}

TClipPlane::TClipPlane(const string& name,const C3DFVector& normal, float dist ,int _no):
	TSlice(name),
	enabled(0),
	no(_no),
	eqn(normal.x,normal.y,normal.z,dist),
	flipped(true)
{;}

TClipPlane::~TClipPlane()
{
}

void TClipPlane::use_clip_plane() const  
{
	glClipPlane(GLenum(GL_CLIP_PLANE0+no),&eqn.x);
}


void TClipPlane::enable()
{
	++enabled; 
	if (enabled >= 1)
		glEnable(GLenum(GL_CLIP_PLANE0 + no));
}

void TClipPlane::disable()
{
	--enabled; 
	if (enabled == 0)
		glDisable(GLenum(GL_CLIP_PLANE0+no));
}

bool TClipPlane::do_handle_key_event(int key)
{
	switch (key) {
	case 'v':
	case 't':break; 
	case 'i':flip();   break;
	default: return TSlice::do_handle_key_event(key); 
	}
	return true; 
}

void TClipPlane::inc_slice()
{
	eqn.a -= 1.0;
	calc_strip();
}

void TClipPlane::dec_slice()
{
	eqn.a += 1.0;
	calc_strip();
}

void TClipPlane::set_slice(float new_slice)
{
	eqn.a = flipped ? -new_slice : new_slice;
	calc_strip();
}

float TClipPlane::get_slice()const
{
	return (eqn.a < 0.0 ? (-eqn.a) : eqn.a);
}

void TClipPlane::get_slice_range(float &from, float &to)const
{
	from	= 0.0f;
	to	= 256.0f;
}

const char* TClipPlane::get_classname() const 
{
	return CLIPPLANE_CLASSNAME;
}

void TClipPlane::get_classname_list(list<string> *classlist) const
{
	if (classlist) {
		classlist->push_front(CLIPPLANE_CLASSNAME);
		TSlice::get_classname_list(classlist);
	}
}

bool TClipPlane::do_handle_command_event(PEventInfo info)
{
	switch (info->get_command()) {
	case EV_CLIPPLANE_GET_STATE: {
		TClipPlaneEventInfo & ev = dynamic_cast<TClipPlaneEventInfo&>(*info);
		return true;
	}
		
	case EV_CLIPPLANE_INVERT:  
		flip();
		return true; 
		
	} // end switch
	
	return TSlice::do_handle_command_event(info);	
}


void TClipPlane::do_gl_draw(const TCamera& c) const
{
	// usually clip plane is invisible
	if ( ! is_selected()) {
		return; 
	}

	if (enabled) // we want to see it now!
		glDisable(GLenum(GL_CLIP_PLANE0+no));
	
	glColor3f(1.0f,1.0f,1.0f);
	glVertexPointer(3,GL_FLOAT,0,&strip[0].x);
	glEnableClientState(GL_VERTEX_ARRAY);
	glDrawElements(GL_LINE_STRIP,8,GL_UNSIGNED_BYTE,elements);
	glDisableClientState(GL_VERTEX_ARRAY);	
	
	if (enabled)
		glEnable(GLenum(GL_CLIP_PLANE0+no));
}

TXYClipPlane::TXYClipPlane() :
	TClipPlane("xy_clip_plane",C3DFVector(0,0,1),-128,0)
{
	calc_strip();
	show();
}

void TXYClipPlane::calc_strip()
{
	strip[0] =  C3DFVector(  0.0f,   0.0f, flipped ? -eqn.a : eqn.a);
	strip[1] =  C3DFVector(  0.0f, 256.0f, flipped ? -eqn.a : eqn.a);
	strip[2] =  C3DFVector(256.0f, 256.0f, flipped ? -eqn.a : eqn.a);
	strip[3] =  C3DFVector(256.0f,   0.0f, flipped ? -eqn.a : eqn.a);
}

TXZClipPlane::TXZClipPlane() :
	TClipPlane("xz_clip_plane",C3DFVector(0,1,0),-128,1)
{
	calc_strip();
	show();
}

void TXZClipPlane::calc_strip()
{
	strip[0] =  C3DFVector(  0.0f, flipped ? -eqn.a : eqn.a,   0.0f);
	strip[1] =  C3DFVector(  0.0f, flipped ? -eqn.a : eqn.a, 256.0f);
	strip[2] =  C3DFVector(256.0f, flipped ? -eqn.a : eqn.a, 256.0f);
	strip[3] =  C3DFVector(256.0f, flipped ? -eqn.a : eqn.a,   0.0f);
}

TYZClipPlane::TYZClipPlane() :
	TClipPlane("yz_clip_plane",C3DFVector(1,0,0),-128,2)
{
	calc_strip();
	show();
}

void TYZClipPlane::calc_strip()
{
	strip[0] =  C3DFVector(flipped ? -eqn.a : eqn.a,   0.0f,  0.0f);
	strip[1] =  C3DFVector(flipped ? -eqn.a : eqn.a,   0.0f,256.0f);
	strip[2] =  C3DFVector(flipped ? -eqn.a : eqn.a, 256.0f,256.0f);
	strip[3] =  C3DFVector(flipped ? -eqn.a : eqn.a, 256.0f,  0.0f);		
}



TFreeClipPlane::TFreeClipPlane(TCamera *camera )
	: TClipPlane("free clipplane",3), 
	  __vc(128.0, 128.0, 128.0),
	  __camera(camera),
	  __points_given(3),
	  __loc3d(0.0, 0.0, 0.0)
{
	strip[0] =  C3DFVector( 0.0f, -128.0f, -128.0f);
	strip[1] =  C3DFVector( 0.0f,  128.0f, -128.0f);
	strip[2] =  C3DFVector( 0.0f,  128.0f,  128.0f);
	strip[3] =  C3DFVector( 0.0f,  -128.0f, 128.0f);

	eqn.a = -(eqn.x * __vc.x + eqn.y * __vc.y + eqn.z * __vc.z);
	
	calc_strip();
	show();
}
	
void TFreeClipPlane::rot(const C3DFVector& rot)
{
	double sina, cosa,sinb,cosb,sinc,cosc; 
#ifdef __USE_GNU
	sincos(rot.x, &sina, &cosa);
	sincos(rot.y, &sinb, &cosb);
	sincos(rot.z, &sinc, &cosc);
#else
	sina = sin(rot.x); cosa = cos(rot.x);
	sinb = sin(rot.y); cosb = cos(rot.y);
	sinc = sin(rot.z); cosc = cos(rot.z);	
#endif	
	float x = cosb * (cosc * eqn.x - sinc * eqn.y) + sinb * eqn.z; 
	float help1 = (sinc * eqn.x + cosc * eqn.y);
	float help2 = sinb * ( cosc * eqn.x - sinc * eqn.y);
	float help3 = cosb * eqn.z;
	float y = cosa * help1 + sina * (help2 - help3);
	float z = sina * help1 - cosa * (help2 - help3);
	eqn.x = x; 
	eqn.y = y; 
	eqn.z = z;
	eqn.a = -(eqn.x * __vc.x + eqn.y * __vc.y + eqn.z * __vc.z);
	calc_strip();	
}

void TFreeClipPlane::set_position(const C3DFVector& p1, const C3DFVector& p2, const C3DFVector& p3) {
	
	// take p2 as the new center of the clipplane
	__vc = p2;
	
	// calculate the normal vector
	C3DFVector v1 = p1 - p2;
	C3DFVector v2 = p3 - p2;
	
	C3DFVector nv( (v1.y * v2.z - v1.z * v2.y) ,
		       (v1.z * v2.x - v1.x * v2.z) ,
		       (v1.x * v2.y - v1.y * v2.x) );
	
	nv /= nv.norm();
	
	eqn.x = nv.x;
	eqn.y = nv.y;
	eqn.z = nv.z;
	eqn.a = -(eqn.x * __vc.x + eqn.y * __vc.y + eqn.z * __vc.z);
	calc_strip();
}


const char* TFreeClipPlane::get_classname() const 
{
	return FREECLIPPLANE_CLASSNAME;
}

void TFreeClipPlane::get_classname_list(list<string> *classlist) const
{
	if (classlist) {
		classlist->push_front(FREECLIPPLANE_CLASSNAME);
		TClipPlane::get_classname_list(classlist);
	}
}

bool TFreeClipPlane::do_handle_command_event(PEventInfo info)
{
	switch (info->get_command()) {
	case EV_FREECLIPPLANE_GET_STATE: {
		TFreeClipPlaneEventInfo & ev = dynamic_cast<TFreeClipPlaneEventInfo&>(*info);

		C3DFVector NV(eqn.x, eqn.y, eqn.z);
		ev.set_alpha(acos(NV.x / NV.norm2()));
		ev.set_beta(acos(NV.y / NV.norm2()));
		ev.set_gamma(acos(NV.z / NV.norm2()));
		return true;
	}
		
	case EV_FREECLIPPLANE_SET_ROTATION: {
		TFreeClipPlaneEventInfo & ev = dynamic_cast<TFreeClipPlaneEventInfo&>(*info);
		C3DFVector rotv(ev.get_alpha(), ev.get_beta(), ev.get_gamma());
		rot(rotv);
		return true;
	}
		
	case EV_FREECLIPPLANE_ACTIVATE_TRIPOD: {
		__points_given = 0;
		this->disable();
		return true;
	} // end switch
	
	}
	return TClipPlane::do_handle_command_event(info);
}

void TFreeClipPlane::calc_strip()
{
	C3DFVector v1;
	
	if (eqn.x != 0.0)
		v1 = C3DFVector(-(eqn.y + eqn.z) / eqn.x , 1.0, 1.0);
	else if (eqn.y != 0.0)
		v1 = C3DFVector(1.0, -(eqn.x + eqn.z) / eqn.y, 1.0);
	else if (eqn.z != 0.0)
		v1 = C3DFVector(1.0, 1.0, -(eqn.x + eqn.y) / eqn.z);
			
	C3DFVector v2 = C3DFVector( eqn.y * v1.z - eqn.z * v1.y ,
				    eqn.z * v1.x - eqn.x * v1.z ,
				    eqn.x * v1.y - eqn.y * v1.x );
	
	
	v1 *= (128.0 / v1.norm());
	v2 *= (128.0 / v2.norm());
	
	strip[0] = __vc + v1 + v2;
	strip[1] = __vc + v1 - v2;
	strip[2] = __vc - v1 + v2;
	strip[3] = __vc - v1 - v2;
}


void TFreeClipPlane::do_gl_draw(const TCamera& c) const
{
	switch (__points_given) {
		
	case 0: break;
		
	case 1: // draw selected point
		glBegin(GL_POINTS);
		glColor3f(1.0, 1.0, 0.0);
		glVertex3f(__P1.x,__P1.y,__P1.z);
		glVertex3f(__loc3d.x, __loc3d.y, __loc3d.z);
		glEnd();
		// draw a line from the 1st point to the current mouse position
		glBegin(GL_LINES);
		glColor3f(0.0, 1.0, 0.0);
		glVertex3f(__P1.x,__P1.y,__P1.z);
		glVertex3f(__loc3d.x, __loc3d.y, __loc3d.z);
		glEnd();
		break;
		
	case 2: // draw selected point
		glBegin(GL_POINTS);
		glColor3f(1.0, 1.0, 0.0);
		glVertex3f(__P1.x,__P1.y,__P1.z);
		glVertex3f(__P2.x,__P2.y,__P2.z);
		glVertex3f(__loc3d.x, __loc3d.y, __loc3d.z);
		glEnd();
		// draw a line from the 1st point to the 2nd point and 
		// from the 2nd point to the current mouse position
		glBegin(GL_LINE_STRIP);
		glColor3f(0.0, 1.0, 0.0);
		glVertex3f(__P1.x,__P1.y,__P1.z);
		glVertex3f(__P2.x,__P2.y,__P2.z);
		glVertex3f(__loc3d.x, __loc3d.y, __loc3d.z);
		glEnd();
		break;
		
	case 3: TClipPlane::do_gl_draw(c);
		break;
	} // end switch
}
	

void TFreeClipPlane::do_unselect() {
	if (__points_given != 3) {
		__points_given = 3;
		this->enable();
	}
}
 

bool TFreeClipPlane::do_handle_mouse_event(int button, int x, int y)
{
	//	cout << "TFreeClipPlane::do_handle_mouse_event\n" << flush;
	
	// store the current position
	__loc3d = __camera->get_spacecoord(x,__camera->get_viewport().y - y);
	
	// nothing to do, so return 
	if ( __points_given == 3 ) return false;
	
	
	button = button & EV_MOUSEDOWN_MASK;
	
	switch (button) {
	
	case EV_MOUSE2_DOWN: // store the the selected point (below)
		break;
		
	case EV_MOUSE3_DOWN: // discard the selection
		this->enable();
		__points_given = 3;
		return true;
		
	default: return false;
	} // end switch (button)
		
	
	switch (__points_given) {
		
	case 0: __P1 = __loc3d; 
		break;
		
	case 1: if (__P1 == __loc3d) return true;
		__P2 = __loc3d;
		break;
		
	case 2: if ( (__P1 == __loc3d) || (__P2 == __loc3d) ) return true;
		set_position(__P1,__P2,__loc3d);
		this->enable();
		break;
	} // switch (__points_given)
	__points_given++;
	
	return true;
}



const string type_TClipPlane("TClipPlane");

unsigned char TClipPlane::elements[8] = {0,1,3,0,2,1,3,2};

