/* -*- 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 <viewit/arrow.hh>
#include <list>
#include <GL/gl.h>
#include <cmath>
#include <errno.h>
#include <mia.hh>
using namespace mia; 


#include <assert.h>
#include <stdio.h>
#include <iostream>

using namespace std; 


C3DFVector operator - (const TArrow& a, const TArrow& b) 
{
	return a.start - b.start; 
}

struct  Flipper{
        void operator () (TArrow& a){
                a.flip();
        }
};


struct arrow_coll_t{
	TArrow a; 
	double dist; 
};


bool  TArrowListData::do_export(FILE *f) const
{
	assert(f);
	errno = 0;  
	list<TArrow>::const_iterator i = arrow_list.begin();
	list<TArrow>::const_iterator end = arrow_list.end();
	fprintf(f,"%d\n", (unsigned int)arrow_list.size());
	bool success = true; 
	while (i != end && success) {
		success = (*i).do_export(f);
		++i; 
	};
	return errno==0; 
}


bool TArrowListData::do_handle_key_event(int key)
{
	switch (key) {
	case 'n': n_draw_arrow_step *= 2; break; 
	case 'N': n_draw_arrow_step /= 2; 
		if (n_draw_arrow_step < 1 )
			n_draw_arrow_step = 1; 
		break; 
	case 'm': if (magnify < 5.0) magnify += 1.0; break;
	case 'M': if (magnify > 1.0) magnify -= 1.0; break;
	case 'f': for_each(arrow_list.begin(), arrow_list.end(), Flipper()); 
		break;
	default:return false;
	}
	return true; 
}
	
bool TArrowListData::do_handle_command_event(PEventInfo info)
{
	switch (info->get_command()) {
	case EV_ARROWLIST_INCREASE_ARROW_NUMBER:
		do_handle_key_event('N');
		return true;
	case EV_ARROWLIST_DECREASE_ARROW_NUMBER:
		do_handle_key_event('n');
		return true;
	case EV_ARROWLIST_INCREASE_ARROW_LENGTH:
		do_handle_key_event('m');
		return true;
	case EV_ARROWLIST_DECREASE_ARROW_LENGTH:
		do_handle_key_event('M');
		return true;
	case EV_ARROWLIST_FLIP_ARROWS:
		do_handle_key_event('f');
		return true;
	}

	return false;
}

void TArrowListData::add_arrow(const TArrow& arrow)
{
	arrow_list.push_back(arrow);
}

void TArrowListData::clear()
{
	arrow_list.clear();
}

TColor bottom_color(.5f,0.0f,.5f,0.3f);

TArrowList::TArrowList(const string& name):
	TDrawable(name),
	data(new TArrowListData())
{
	light_on();
}

TArrowList::TArrowList(const string& name,const TColor& shaft_color, const TColor& hat_color):
	TDrawable(name),
	data(new TArrowListData())
{
	set_color1(shaft_color);
	set_color2(hat_color);
	set_color3(bottom_color);
	light_on();
}


TArrowList::TArrowList(const string& name,const TColor& shaft_color, const TColor& hat_color,
		       const TNormalVertexList& vl, const C3DTransformation&  deform):
	TDrawable(name),
	data(new TArrowListData())
{
	set_color1(shaft_color);
	set_color2(hat_color);
	set_color3(bottom_color);
	
	TNormalVertexList::const_iterator end = vl.end();
		
	for (TNormalVertexList::const_iterator i = vl.begin(); i != end; ++i) {
		C3DFVector v = deform.apply( (*i).vertex);
		v -= (v * (*i).normal) * (*i).normal; 
		
		if (v.norm2() > 0.5) {
			data->add_arrow( TArrow((*i).vertex + v, (*i).vertex )); 
		}
	}
	light_on();
}

TArrowList::TArrowList(const string& name,const TColor& shaft_color, const TColor& hat_color,
		       const TVertexList& vl, const C3DTransformation& deform):
	TDrawable(name),
	data(new TArrowListData())
{
	
	set_color1(shaft_color);
	set_color2(hat_color);
	set_color3(bottom_color);
	
	TVertexList::const_iterator end = vl.end();
		
	for (TVertexList::const_iterator i = vl.begin(); i != end; ++i) {
		C3DFVector v = deform.apply( *i);
		if (v.norm2() > 0.5) {
			data->add_arrow( TArrow(*i + v, *i )); 
		}
	}
	light_on();
}



TArrowList::TArrowList(const string& name,const TColor& shaft_color, const TColor& hat_color,FILE *f, P3DTransformation deform):
	TDrawable(name),

	data(new TArrowListData())
{
	set_color1(shaft_color);
	set_color2(hat_color);
	set_color3(bottom_color);

	char buf[1024];
	C3DFVector s,d; 
	if (!fgets(buf,1024,f)) {
		delete data; 
		throw "empty file";
	}
	if (strncmp(buf,"ArrowList",9)) {
		delete data; 
		throw "file is not an arrow list"; 
	}

	while(fgets(buf,1024,f)) {
		if (buf[0] == '#') 
			continue; 
		if (sscanf(buf,"< %f, %f, %f > < %f, %f, %f >",&s.x, &s.y, &s.z, &d.x, &d.y, &d.z)!= 6)
			continue;
		if (deform)  // if there is a vectorfield, override the readings
			d = -1.0f * deform->apply(s);
		data->add_arrow(TArrow(s,s+d));
	}
	cerr << name << " got " << data->size() << " arrows" << endl; 
	light_on();
}


const char* TArrowList::get_classname() const 
{
	return ARROWLIST_CLASSNAME;
}

void TArrowList::get_classname_list(list<string> *classlist) const
{
	if (classlist) {
		classlist->push_front(ARROWLIST_CLASSNAME);
		TDrawable::get_classname_list(classlist);
	}
}

void TArrowList::double_arrow_number()
{
	data->do_handle_key_event('N');
}

void TArrowList::half_arrow_number()
{
	data->do_handle_key_event('n');
}

void TArrowList::increase_arrow_size()
{
	data->do_handle_key_event('m');
}

void TArrowList::decrease_arrow_size()
{
	data->do_handle_key_event('M');
}

int TArrowList::size()const
{
	return data->size();
}


TArrowList::~TArrowList() 
{
	delete data; 
}
void TArrowList::gl_attach()
{
	data->gl_attach();

	glNewList(data->get_arrow_disp_list(),GL_COMPILE);
	glColor4fv(&get_color2().x);
	data->draw_hat();
	glEndList();
	
	glNewList(data->get_arrow_disp_list()+1,GL_COMPILE);
	glColor4fv(&get_color1().x);
	data->draw_shaft();

	glColor4fv(&get_color3().x);
	data->draw_bottom();
	
	glEndList();
	
	glDisableClientState(GL_VERTEX_ARRAY);	

}

void TArrowListData::gl_attach()	
{
	list<TArrow>::iterator i = arrow_list.begin();
	list<TArrow>::iterator end = arrow_list.end();
	while ( i != end ) {
		(*i).gl_attach();
		++i; 
	}
	arrow_disp_list = glGenLists(2);
	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, vertices);
}

class TArrowListData *TArrowList::get_data()const
{
	return data; 
}

bool TArrowList::do_handle_key_event(int key)
{
	if (!data->do_handle_key_event(key)) 
		return TDrawable::do_handle_key_event(key);
	return true; 
}

bool TArrowList::do_handle_command_event(PEventInfo info)
{
	if (data->do_handle_command_event(info))
		return true;
	
	return TDrawable::do_handle_command_event(info);
}

void TArrowList::gl_detach()
{
	data->gl_detach();
}

inline void TArrowListData::gl_detach()	
{
	glDeleteLists(arrow_disp_list,2);
}
void TArrowList::do_gl_draw(const TCamera& c) const
{
	glEnable(GL_COLOR_MATERIAL);
	data->do_gl_draw(c);
	glDisable(GL_COLOR_MATERIAL);
}

void TArrowListData::do_gl_draw(const TCamera& c) const
{	
	list<TArrow>::const_iterator i = arrow_list.begin();
	list<TArrow>::const_iterator end = arrow_list.end();
	int draw_idx = 0; 
	
	while ( i != end ) {
		
		++draw_idx; 
		if (draw_idx >= n_draw_arrow_step) {
			
			glPushMatrix();
			//glLoadIdentity();
			(*i).init_position();
			//c.gl_reuse_camera();		
			if (magnify != 1.0f)
				glScalef(1.0,1.0,magnify);
			
			glCallList(arrow_disp_list);
			
			if (!c.low_detail()) 
				glCallList(arrow_disp_list+1);
		
			glPopMatrix();
			draw_idx = 0; 
		}
		++i;
		

	}
}


TArrow::TArrow(const C3DFVector& start_, const C3DFVector& end_):
	start(start_),
	dir(end_ - start)
{
}

void TArrow::gl_attach()
{
	const float umrechen_fak = 180.0f / M_PI;
	float dir_norm = dir.norm();

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	// translation
	glTranslatef(start.x,start.y,start.z);

	C3DFVector axis(dir.y, -dir.x, 0.0f);
	
	float axis_norm = axis.norm();
	if (axis_norm != 0.0) { // rotation necessary
		
		float angle = - umrechen_fak * acos(dir.z / dir_norm); 
		axis /= axis_norm; 
		
		glRotatef(angle,axis.x,axis.y,axis.z);
	}	

	// scaling
	float scale = dir_norm / 5.0; 
	if (scale < 1.0) {
		glScalef(scale,scale,scale);
	}else{
		glScalef(1.0f,1.0f,scale);
	}

	
	glGetFloatv(GL_MODELVIEW_MATRIX,matrix);
}

void TArrow::flip()
{
	start = start + dir;
	dir *= -1; 

	gl_attach();
}


void TArrow::init_position()const
{
	glMultMatrixf(matrix);
}	

bool  TArrow::do_export(FILE *f)const
{
	assert(f);
	
	return (fprintf(f,"< %f, %f, %f > < %f, %f, %f >\n",
			start.x, start.y, start.z,
			dir.x, dir.y, dir.z) > 0); 

}

void TArrowListData::draw_hat()const
{
	
	glBegin(GL_TRIANGLES);
	for (int i = 0; i < 8; i++) {
		glNormal3fv(&top_normal[i].x);
		glVertex3fv(&vertices[3*top[3*i+2]]);
		//glNormal3fv(&top_normal[i].x);
		glVertex3fv(&vertices[3*top[3*i]]);
		glNormal3fv(&top_normal[(i+1) % 8].x);
		glVertex3fv(&vertices[3*top[3*i+1]]);


	}
	glEnd();
}

void TArrowListData::draw_shaft()const
{
	glBegin(GL_QUAD_STRIP);
	for (int i = 0; i < 9; i ++) {
		glNormal3fv(&side_quad_strip_normal[i].x);
		glVertex3fv(&side_quad_strip_vertex[2*i].x);
		glVertex3fv(&side_quad_strip_vertex[2*i+1].x);		
	}
	glEnd();
	//glDrawElements(GL_QUAD_STRIP,18,GL_UNSIGNED_BYTE,side_quad_strip);
}
void TArrowListData::draw_bottom()const
{
	glNormal3fv(bottom_normal);
	glDrawElements(GL_TRIANGLES,24,GL_UNSIGNED_BYTE,bottom);
	glNormal3fv(bottom_normal);
	glDrawElements(GL_QUAD_STRIP,18,GL_UNSIGNED_BYTE,hat_bottom_quad_strip);
}

float TArrowListData::vertices[3*26] = {
	+0.0f,    0.0f,    0.0f, //  0
	+0.5f,    0.0f,    0.0f, //  1
	+0.353f,  0.353f,  0.0f, //  2
	+0.0f,    0.5f,    0.0f, //  3
	-0.353f,  0.353f,  0.0f, //  4	
	-0.5f,    0.0f,    0.0f, //  5 	
	-0.353f, -0.353f,  0.0f, //  6	
	+0.0f,   -0.5f,    0.0f, //  7	
	+0.353f,   -0.353f,  0.0f, //  8	
	
	+0.5f,    0.0f,    3.0f, //  9	
	+0.353f,  0.353f,  3.0f, //  10	
	+0.0f,    0.5f,    3.0f, //  11	
	-0.353f,  0.353f,  3.0f, //  12	
	-0.5f,    0.0f,    3.0f, //  13	
	-0.353f, -0.353f,  3.0f, //  14		
	+0.0f,   -0.5f,    3.0f, //  15	
	+0.353f,   -0.353f,  3.0f, //  16	
	
	+1.0f,    0.0f,    3.0f, //  17	
	+0.707f,  0.707f,  3.0f, //  18	
	+0.0f,    1.0f,    3.0f, //  19	
	-0.707f,  0.707f,  3.0f, //  20	
	-1.0f,    0.0f,    3.0f, //  21	
	-0.707f, -0.707f,  3.0f, //  22			
	+0.0f,   -1.0f,    3.0f, //  23	
	+0.707f, -0.707f,  3.0f, //  24	
	
	+0.0f,    0.0f,    5.0f  //  25	
};

C3DFVector TArrowListData::side_quad_strip_normal[9]= {
	C3DFVector(+1.0f,    0.0f,    0.0f), //  1
	C3DFVector(+0.707f,  0.707f,  0.0f), //  2
	C3DFVector(+0.0f,    1.0f,    0.0f), //  3
	C3DFVector(-0.707f,  0.707f,  0.0f), //  4	
	C3DFVector(-1.0f,    0.0f,    0.0f), //  5 	
	C3DFVector(-0.707f, -0.707f,  0.0f), //  6	
	C3DFVector(+0.0f,   -1.0f,    0.0f), //  7	
	C3DFVector(+0.707f,   -0.707f,  0.0f), //  8 
	C3DFVector(+1.0f,    0.0f,    0.0f) //  1
};

C3DFVector TArrowListData::side_quad_strip_vertex[18]= {
	C3DFVector(+0.5f,    0.0f,    0.0f), //  1
	C3DFVector(+0.5f,    0.0f,    3.0f), //  1
	
	C3DFVector(+0.353f,  0.353f,  0.0f), //  2
	C3DFVector(+0.353f,  0.353f,  3.0f), //  2
	
	C3DFVector(+0.0f,    0.5f,    0.0f), //  3
	C3DFVector(+0.0f,    0.5f,    3.0f), //  3
	
	C3DFVector(-0.353f,  0.353f,  0.0f), //  4	
	C3DFVector(-0.353f,  0.353f,  3.0f), //  4	
	
	C3DFVector(-0.5f,    0.0f,    0.0f), //  5     
	C3DFVector(-0.5f,    0.0f,    3.0f), //  5     
	
	C3DFVector(-0.353f, -0.353f,  0.0f), //  6	
	C3DFVector(-0.353f, -0.353f,  3.0f), //  6	
	
	C3DFVector(+0.0f,   -0.5f,    0.0f), //  7	
	C3DFVector(+0.0f,   -0.5f,    3.0f), //  7
	
	C3DFVector(+0.353f,   -0.353f,  0.0f), //  8 
	C3DFVector(+0.353f,   -0.353f,  3.0f), //  8			

	C3DFVector(+0.5f,    0.0f,    0.0f), //  1
	C3DFVector(+0.5f,    0.0f,    3.0f) //  1

};


bool  TArrowList::do_export(FILE *f)const
{
	fprintf(f,"ArrowList\n");
	return data->do_export(f);
}

TArrowListGrow::TArrowListGrow(const string& name,const TColor& _start_color, const TColor& _end_color,
			       P3DTransformation deform_):
	TArrowList(name,_start_color,_end_color),
	deform(deform_)
{
}

TArrowListGrow::TArrowListGrow(const string& name,const TColor& shaft_color, const TColor& hat_color, 
		       FILE *f,P3DTransformation deform_):
	TArrowList(name,shaft_color,hat_color,f,deform_),
	deform(deform_)
{
}

TArrowListGrow::TArrowListGrow(const string& name,P3DTransformation deform_):
	TArrowList(name),
	deform(deform_)
{
}

void TArrowListGrow::add_vector(const C3DFVector& v)
{
	gl_detach();
	if (deform) {
		cerr << "added vector with deform"<< endl;
		C3DFVector delta = deform->apply(v);
		get_data()->add_arrow(TArrow(v + delta, v ));
	} else {
		cerr << "added vector without deform"<< endl;
		get_data()->add_arrow(TArrow(v, v ));
	}
	gl_attach();
}

void TArrowListGrow::set_vectorfield(P3DTransformation deform_)
{
	gl_detach();
	get_data()->clear();
	deform = deform_;
	gl_attach();
}

void TArrowListGrow::clear()
{
	gl_detach();
	get_data()->clear();
	gl_attach();
}

const char* TArrowListGrow::get_classname() const 
{
	return ARROWLISTGROW_CLASSNAME;
}

void TArrowListGrow::get_classname_list(list<string> *classlist) const
{
	if (classlist) {
		classlist->push_front(ARROWLISTGROW_CLASSNAME);
		TArrowList::get_classname_list(classlist);
	}
}


float TArrowListData::bottom_normal[3] = {0.0f, 0.0f, -1.0f};

unsigned char TArrowListData::bottom[24] = {
	0,1,2,0,2,3,0,3,4,0,4,5,0,5,6,0,6,7,0,7,8,0,8,1
};

unsigned char TArrowListData::side_quad_strip[18]=  {
	1,9,2,10,3,11,4,12,5,13,6,14,7,15,8,16,1,9
};

unsigned char TArrowListData::hat_bottom_quad_strip[18] = {
	9,17,10,18,11,19,12,20,13,21,14,22,15,23,16,24,9,17
};

C3DFVector TArrowListData::top_normal[8] = {
	C3DFVector(+1.0f  /sqrt(1.25f),  0.0f,                 0.5f/sqrt(1.25f)), //  1
	C3DFVector(+0.707f/sqrt(1.25f),  0.707f / sqrt(1.25f), 0.5f/sqrt(1.25f)), //  2
	C3DFVector(+0.0f,                1.0f   / sqrt(1.25f), 0.5f/sqrt(1.25f)), //  3
	C3DFVector(-0.707f/sqrt(1.25f),  0.707f / sqrt(1.25f), 0.5f/sqrt(1.25f)), //  4	
	C3DFVector(-1.0f  /sqrt(1.25f),  0.0f,                 0.5f/sqrt(1.25f)), //  5 	
	C3DFVector(-0.707f/sqrt(1.25f), -0.707f / sqrt(1.25f), 0.5f/sqrt(1.25f)), //  6	
	C3DFVector(+0.0f,               -1.0f   / sqrt(1.25f), 0.5f/sqrt(1.25f)), //  7	
	C3DFVector(+0.707f,             -0.707f / sqrt(1.25f), 0.5f/sqrt(1.25f))  //  8 
};

int TArrowListData::top[24] = {
	17,18,25, 18,19, 25,19,20, 25,20,21, 25,21,22, 25,22,23, 25,23,24, 25,24,17,25
};


TArrowList::TArrowList(const string& name,const TColor& shaft_color, const TColor& hat_color, FILE *f):
	TDrawable(name),
	data(new TArrowListData())
{
	set_color1(shaft_color);
	set_color2(hat_color);
	set_color3(bottom_color);

	char buf[1024];
	C3DFVector s,d; 
	if (!fgets(buf,1024,f)) {
		delete data; 
		throw "empty file";
	}
	if (strncmp(buf,"ArrowList",9)) {
		delete data; 
		throw "file is not an arrow list"; 
	}

	while(fgets(buf,1024,f)) {
		if (buf[0] == '#') 
			continue; 
		if (sscanf(buf,"< %f, %f, %f > < %f, %f, %f >",&s.x, &s.y, &s.z, &d.x, &d.y, &d.z)!= 6)
			continue;
		data->add_arrow(TArrow(s,s+d));
	}
	cerr << name << " got " << data->size() << " arrows" << endl; 
	light_on();
}
