/* -*- 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/>.
 */


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <queue>
#include <iostream>
using namespace std; 

#include <viewit/planeexpose.hh>
#include <mia/2d/datafield.cxx>

float BasePlaneExpose::deform_thresh = .0001;
unsigned int BasePlaneExpose::max_objs = 200;

BasePlaneExpose::BasePlaneExpose(const GL2DTexture& _Texture):
	obj_size(0),
	Texture(_Texture),
	ObjIndexArray(0),
	PointArray(0),
	fine_point_array(0),
	fine_display(false)
{
	
}
	
BasePlaneExpose::~BasePlaneExpose() 
{
	delete[] ObjIndexArray;
	if (fine_point_array)
		delete[] fine_point_array;
	delete[] PointArray;
}

inline void BasePlaneExpose::draw_point(const GLTexPoint& pPoint) const
{
	glTexCoord2f(pPoint.tex.x, pPoint.tex.y);
	glNormal3f(pPoint.normal.x,pPoint.normal.y,pPoint.normal.z);
	glVertex3f(pPoint.loc.x,pPoint.loc.y,pPoint.loc.z);
}

void BasePlaneExpose::draw() const
{

	
	if (!fine_display) { // fast display is default
		glBegin(GL_QUADS);
		GLuint *pObjIndexArray = ObjIndexArray;
		for (int i = 0; i < obj_size / 4; i++) {
			draw_point(PointArray[*pObjIndexArray++]);
			draw_point(PointArray[*pObjIndexArray++]);
			draw_point(PointArray[*pObjIndexArray++]);
			draw_point(PointArray[*pObjIndexArray++]);
		}
		glEnd();
	} else {// print every little little quad
		GLTexPoint *p_point_y0 = fine_point_array;
		GLTexPoint *p_point_y1 = &p_point_y0[m_coordinates.get_size().x];
		
		for (size_t y = 0; y < m_coordinates.get_size().y-1; y++){
			glBegin(GL_QUAD_STRIP); 
			for (size_t x =0; x < m_coordinates.get_size().x; x++) {
				draw_point(*p_point_y1++);
				draw_point(*p_point_y0++);
			}
			glEnd();
		}

	}
}


void BasePlaneExpose::set_fine_display(bool fine)
{
	int ya,ye,xa,xe;
	
	fine_display = fine;
	if (!fine_display  || fine_point_array)
		return;
	
	T3DVector2DField normals(m_coordinates.get_size());
	
	fine_point_array = new GLTexPoint[m_coordinates.get_size().y * m_coordinates.get_size().x];
	GLTexPoint *p = fine_point_array;
	
	const float tex_step_x = Texture.get_tex_xcoord(1.0) / (m_coordinates.get_size().x);
	const float tex_step_y = Texture.get_tex_ycoord(1.0) / (m_coordinates.get_size().y);
	
	C2DFVector tex(0.0,0.0);

	
	for (size_t y = 0; y < m_coordinates.get_size().y; y++,tex.y += tex_step_y) {
		if (y > 0) {ya = y-1; ye = y;}
		else       {ya = y; ye = y+1;}
		
		tex.x = 0.0;
		
		for (size_t x = 0; x < m_coordinates.get_size().x; 
		     x++,tex.x += tex_step_x,p++) {
			if (x > 0) {xa = x-1; xe = x;}
			else       {xa = x; xe = x+1;}
			
			p->tex = tex;
			C3DFVector normal = get_quad_normal(xa,ya,xe,ye);
			normals(xa,ya) += normal;
			normals(xa,ye) += normal;
			normals(xe,ye) += normal;
			normals(xe,ya) += normal;
			p->loc = m_coordinates(x,y);
		}
	}
	T3DVector2DField::const_iterator normal_iterator = normals.begin();
	T3DVector2DField::const_iterator ne = normals.end();
	
	p = fine_point_array; 
	
	while (normal_iterator != ne) {
		float help= (*normal_iterator).norm();
		if (help > 0.0) 
			(*p++).normal = *normal_iterator++ / help;
		else
			(*p++).normal  = *normal_iterator++;
	}
}

C3DFVector BasePlaneExpose::get_quad_normal(int x,int y,int xe,int ye)const
{
	C3DFVector result = (m_coordinates(xe,y) - m_coordinates(x,y)) ^
		(m_coordinates(x,ye) - m_coordinates(x,y));
	float norm = result.norm();
	if (norm > 0.0) 
		result /= norm;
	return result;
}

void BasePlaneExpose::create_point_table()
{
	std::list<TQuad> objs;
	std::vector<TPoint> Points(m_coordinates.get_size().x * m_coordinates.get_size().y);
	int num_points = generate_map(&Points,&objs);
	
	if (PointArray) {
		delete PointArray;
	}
	
	if (ObjIndexArray)
		delete ObjIndexArray;

	obj_size = 4 * objs.size();
	ObjIndexArray = new GLuint[obj_size];
	GLuint *pObjIndexArray = ObjIndexArray;
	
	for(std::list<TQuad>::const_iterator i= objs.begin(); 
	    i !=  objs.end(); i++) {
		C3DFVector normal = ((Points[(*i).xa_ya_idx].GetDeform() - 
				      Points[(*i).xe_ya_idx].GetDeform()) ^   
				     (Points[(*i).xe_ye_idx].GetDeform() - 
				      Points[(*i).xe_ya_idx].GetDeform()));
		
		Points[(*i).xa_ya_idx].normal_ref() -= normal;
		Points[(*i).xe_ya_idx].normal_ref() -= normal;
		Points[(*i).xe_ye_idx].normal_ref() -= normal;
		Points[(*i).xa_ye_idx].normal_ref() -= normal;
		
		*pObjIndexArray++ = (*i).xa_ya_idx;
		*pObjIndexArray++ = (*i).xe_ya_idx;
		*pObjIndexArray++ = (*i).xe_ye_idx;
		*pObjIndexArray++ = (*i).xa_ye_idx;
	}
	
	PointArray = new GLTexPoint[num_points];
	GLTexPoint *pPointArray = PointArray;
	for  (int i = 0; i < num_points; i++,pPointArray++) {
		Points[i].GetTexPoint(pPointArray);
	}
}


float BasePlaneExpose::get_deform(const std::vector<TPoint>& Points,const TWorkObject& wo)
{

	if (wo.xe - wo.xa < 2 || wo.ye - wo.ya < 2) {
		return 0.0;
	}
	
	float max_deform = 0.0;
	
	// Get deformed corners of the input quad
	const C3DFVector& xa_ya_deform = Points[wo.quad.xa_ya_idx].GetDeform();
	const C3DFVector& xe_ya_deform = Points[wo.quad.xe_ya_idx].GetDeform();
	const C3DFVector& xa_ye_deform = Points[wo.quad.xa_ye_idx].GetDeform();
	const C3DFVector& xe_ye_deform = Points[wo.quad.xe_ye_idx].GetDeform();
	
	int nx = m_coordinates.get_size().x;
	
	// generate the y - interpolation factors
	float y_step = 1.0 / (wo.ye - wo.ya);
	float dy = 0.0;float my = 1.0;
	for (int y = wo.ya; y < wo.ye; y++,dy += y_step,my -= y_step) {
		
		// goto start of line
		T3DVector2DField::iterator d_help = m_coordinates.begin();
		advance(d_help, wo.xa + y * nx);
		
		// generate the x-interpolation factors
		float x_step = 1.0 / (wo.xe - wo.xa);
		float dx = 0.0;float mx = 1.0;
		for (int x = wo.xa; x < wo.xe; x++,d_help++,dx += x_step,mx -= x_step) {
			
			// get the deformation 
			C3DFVector v_delta = *d_help - 
				bilin_interpol(xa_ya_deform,xe_ya_deform,xa_ye_deform,xe_ye_deform,
					       dx,mx,dy,my);
			float new_deform = v_delta.norm2();
			if ( new_deform > max_deform) {
				max_deform = new_deform;
			}
		}
	}
	

	return max_deform;
}

int  BasePlaneExpose::generate_map(std::vector<TPoint> *Points,std::list<TQuad> *objs)
{
	int nx = m_coordinates.get_size().x-1;
	int ny = m_coordinates.get_size().y-1;

	std::priority_queue<TWorkObject> WorkObjects;
	
	const float end_xtex = Texture.get_tex_xcoord(1.0);
	const float end_ytex = Texture.get_tex_ycoord(1.0);
	const float tex_step_x = Texture.get_tex_xcoord(1.0) / (nx+1);
	const float tex_step_y = Texture.get_tex_ycoord(1.0) / (ny+1);
	
	int index = 0;

	// Create the initial square
	
	(*Points)[index++] = TPoint(m_coordinates(0,0),0,0);
	(*Points)[index++] = TPoint(m_coordinates(nx,0),end_xtex,0);
	(*Points)[index++] = TPoint(m_coordinates(nx,ny),end_xtex,end_ytex);
	(*Points)[index++] = TPoint(m_coordinates(0,ny),0,end_ytex);
	
	TWorkObject wo = {1.0,0,nx,0,ny,{0,1,2,3}};

	wo.priority = get_deform(*Points,wo);
	
	// check the deformation 
	if ( wo.priority > deform_thresh) {
		// needs further work
		WorkObjects.push(wo);
	}else{
		// ready to go
		objs->push_back(wo.quad);
	}
	
	while (!WorkObjects.empty() && max_objs > objs->size() + WorkObjects.size()) {
		TWorkObject co = WorkObjects.top();
		WorkObjects.pop();


		// new version: divide into four new rectangles
		int xn = (co.xe - co.xa) / 2 + co.xa;
		int yn = (co.ye - co.ya) / 2 + co.ya;
		
		int idx_xn_ya = index;
		(*Points)[index++] = TPoint(m_coordinates(xn,co.ya),
					    xn * tex_step_x,co.ya * tex_step_y);
		int idx_xn_ye = index;
		(*Points)[index++] = TPoint(m_coordinates(xn,co.ye),
					    xn * tex_step_x,co.ye * tex_step_y);

		int idx_xa_yn = index;
		(*Points)[index++] = TPoint(m_coordinates(co.xa,yn),
					    co.xa * tex_step_x ,yn * tex_step_y);
		int idx_xe_yn = index;
		(*Points)[index++] = TPoint(m_coordinates(co.xe,yn),
					    co.xe * tex_step_x ,yn * tex_step_y);

		int idx_xn_yn = index;
		(*Points)[index++] = TPoint(m_coordinates(xn,yn),
					    xn * tex_step_x ,yn * tex_step_y);

		
		TWorkObject no = co;

		no.xe = xn;
		no.ye = yn;
		
		no.quad.xa_ya_idx = co.quad.xa_ya_idx;
		no.quad.xe_ya_idx = idx_xn_ya;
		no.quad.xa_ye_idx = idx_xa_yn;
		no.quad.xe_ye_idx = idx_xn_yn;
		
		no.priority = get_deform(*Points,no);
		if ( no.priority > deform_thresh)
			WorkObjects.push(no);
		else
			objs->push_back(no.quad);
		
		
		no = co;
		no.xa = xn;
		no.ye = yn;

		no.quad.xa_ya_idx = idx_xn_ya;
		no.quad.xe_ya_idx = co.quad.xe_ya_idx;
		no.quad.xa_ye_idx = idx_xn_yn;
		no.quad.xe_ye_idx = idx_xe_yn;
		
		no.priority = get_deform(*Points,no);
		if ( no.priority > deform_thresh)
			WorkObjects.push(no);
		else
			objs->push_back(no.quad);

		
		no = co;
		no.xe = xn;
		no.ya = yn;
		
		no.quad.xa_ya_idx = idx_xa_yn;
		no.quad.xe_ya_idx = idx_xn_yn;
		no.quad.xa_ye_idx = co.quad.xa_ye_idx;
		no.quad.xe_ye_idx = idx_xn_ye;
		
		no.priority = get_deform(*Points,no);
		if ( no.priority > deform_thresh)
			WorkObjects.push(no);
		else
			objs->push_back(no.quad);

		
		no = co;
		no.xa = xn;
		no.ya = yn;
		
		no.quad.xa_ya_idx = idx_xn_yn;
		no.quad.xe_ya_idx = idx_xe_yn;
		no.quad.xa_ye_idx = idx_xn_ye;
		no.quad.xe_ye_idx = co.quad.xe_ye_idx;
		
		no.priority = get_deform(*Points,no);
		if ( no.priority > deform_thresh)
			WorkObjects.push(no);
		else
			objs->push_back(no.quad);

	}
	// if we stopped, since maximal divisio of tiles reached
	// collect latest ones
	while (!WorkObjects.empty()) {
		objs->push_back(WorkObjects.top().quad);
		WorkObjects.pop();
	}
	return index;
}

XYPlaneExpose::XYPlaneExpose(const GL2DTexture& _Texture,const C3DTransformation& Deform,int z):
	BasePlaneExpose(_Texture)
{
	T3DVector2DField c(C2DBounds(Deform.get_size().x, Deform.get_size().y)); 
	C3DBounds b(0,0,z); 
	C3DBounds e(Deform.get_size().x, Deform.get_size().y, z+1); 
	copy(Deform.begin_range(b, e), Deform.end_range(b, e), c.begin()); 
	
	set_coordinates(c);
	T3DVector2DField::iterator i =  c.begin();

	for (size_t y = 0; y < c.get_size().y; y++) {
		for (size_t x = 0; x < c.get_size().x; x++,i++) {
			(*i) = C3DFVector(x,y,z) - (*i); 				}
	}
	create_point_table();
}

XZPlaneExpose::XZPlaneExpose(const GL2DTexture& _Texture,const C3DTransformation& Deform,int y):
	BasePlaneExpose(_Texture)
{
	T3DVector2DField c(C2DBounds(Deform.get_size().x, Deform.get_size().z)); 
	C3DBounds b(0,y,0); 
	C3DBounds e(Deform.get_size().x, y+1, Deform.get_size().z); 
	copy(Deform.begin_range(b, e), Deform.end_range(b, e), c.begin()); 
	     
	set_coordinates(c);

	T3DVector2DField::iterator i =  c.begin();
	
	
	for (size_t z = 0; z < c.get_size().y; z++) {
		for (size_t x = 0; x < c.get_size().x; x++,i++) {
			(*i) = C3DFVector(x,y,z) - (*i); 
		}
	}
	create_point_table();
}

YZPlaneExpose::YZPlaneExpose(const GL2DTexture& _Texture,const C3DTransformation& Deform,int x):
	BasePlaneExpose(_Texture)
{
	T3DVector2DField c(C2DBounds(Deform.get_size().y, Deform.get_size().z)); 
	C3DBounds b(x,0,0); 
	C3DBounds e(x+1, Deform.get_size().y, Deform.get_size().z);
	copy(Deform.begin_range(b, e), Deform.end_range(b, e), c.begin()); 
	
	set_coordinates(c);
	     
	T3DVector2DField::iterator i =  c.begin();
	for (size_t z = 0; z < c.get_size().y; z++) {
		for (size_t y = 0; y < c.get_size().x; y++,i++) {
			(*i) = C3DFVector(x,y,z) - (*i);
		}
	}
	create_point_table();
}


namespace mia {
template class T2DDatafield<C3DFVector>;
}




