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

#include <viewit/smarty.hh>
#include <viewit/shape.hh>
#include <viewit/shader.hh>
//#include <cp_estimate.hh>
#include <viewit/glimage.hh>
#include <map>

#include <viewit/wireframe.hh>

/********************************************************************************
 *										*
 *				tensorfield loader				*
 *										*
 *******************************************************************************/

class CHelperTensor {
public:
	C3DFMatrix matrix;
	C3DFVector loc;
	C3DFVector evec;
	float eigenvalue;

	CHelperTensor() {;}
	
	CHelperTensor(const C3DFMatrix &m, const C3DFVector &l, const C3DFVector &v, const float &eval) 
		: matrix(m), loc(l), evec(v), eigenvalue(eval)
	{ }
	
	CHelperTensor(const CHelperTensor &pp)
		: matrix(pp.matrix), loc(pp.loc), evec(pp.evec), eigenvalue(pp.eigenvalue)
	{;}		

	bool operator < (const CHelperTensor &pp) const {
		return ( eigenvalue > pp.eigenvalue);
	}
};


TSmarties* create_smarties_from_tensorimage(VistaIOImage image, const std::string &name, unsigned int max_smarties)
{
	TSmarties *retval = NULL;

	// create some helper variables
	C3DBounds bounds(VistaIOImageNColumns(image), VistaIOImageNRows(image), VistaIOImageNFrames(image));
	C3DFMatrix zeromatrix;
	C3DFMatrix matrix;
			
	retval = new TSmarties(name);
	std::priority_queue<CHelperTensor> PPOrder;

	CHelperTensor tensor;
	C3DFVector    evals;
			
	for (unsigned short z=0 ; z<bounds.z ; z++)
		for (unsigned short y=0 ; y<bounds.y ; y++)
			for (unsigned short x=0 ; x<bounds.x ; x++) {
				
				tensor.matrix.x.x = VistaIOGetPixel(image,z*6+0,y,x);
				tensor.matrix.x.y = tensor.matrix.y.x = VistaIOGetPixel(image,z*6+1,y,x);
				tensor.matrix.x.z = tensor.matrix.z.x = VistaIOGetPixel(image,z*6+2,y,x);
				tensor.matrix.y.y = VistaIOGetPixel(image,z*6+3,y,x);
				tensor.matrix.y.z = tensor.matrix.z.y = VistaIOGetPixel(image,z*6+4,y,x);
				tensor.matrix.z.z = VistaIOGetPixel(image,z*6+5,y,x);
				
				if (!(tensor.matrix == zeromatrix)) {
							
					tensor.loc  = C3DFVector(x, y, z);
					C3DFVector evals;
					
					// we don't care about the 2nd and 3rd eigenvalue
					tensor.matrix.get_eigenvalues(evals);
					tensor.eigenvalue = evals.x;

					if (fabs(tensor.eigenvalue) > 1e-2) {
						tensor.evec = tensor.matrix.get_eigenvector(0) / tensor.evec.norm();
						tensor.matrix *= 300.0;
						PPOrder.push(tensor);
						if (PPOrder.size() > max_smarties) 
							PPOrder.pop();
					}
				}
			}

	while (!PPOrder.empty()) {
		shared_ptr<CShape> shape(new CFreeShape(PPOrder.top().matrix));
		shared_ptr<CShading> shade(new CColorShading(PPOrder.top().evec));
		retval->add_smarty(new TSmarty(PPOrder.top().loc, shape, shade));
		PPOrder.pop();
	}
	
	return retval;
}

/** Filter function to avoid the loading of the whole vista file 
    in order to figure out that this is not the file we wanted.
    Used with VistaIOReadFile(FILE *file, VistaIOFilter *filter) it reads
    the attribute list of the file and also the associated data in case 
    the filter function returns true.
    This happens if:	1. repn==VistaIOImageRepn, 2. componentinterp=="tensor6" (if present)
			3. componentrepn of the image is one of the types below 
			4. nComponents==6.
*/
VistaIOBoolean Vista3DTensorFieldFromImageFilter (VistaIOBundle bundle, VistaIORepnKind repn) {
	
	if (repn != VistaIOImageRepn) return false;

	VistaIOAttrListPosn posn;
	if (VistaIOLookupAttr(bundle->list, "component_repn", &posn)) {
		VistaIOStringConst value;
		VistaIOGetAttrValue(&posn, NULL, VistaIOStringRepn, &value);
		std::string component_repn = value;
		if ( component_repn != "tensor6" ) return false;
	}

	
	
	VistaIOStringConst ComponentRepnString;
	if (VistaIOGetAttr(bundle->list, VistaIORepnAttr, NULL, VistaIOStringRepn, &ComponentRepnString) != VistaIOAttrFound) return false;
	VistaIORepnKind ComponentRepn = VistaIOLookupType(ComponentRepnString);
	
	if (!( (ComponentRepn == VistaIOUByteRepn) || 
 	       (ComponentRepn == VistaIOSByteRepn) ||
 	       (ComponentRepn == VistaIOShortRepn) ||
 	       (ComponentRepn == VistaIOLongRepn)  ||
 	       (ComponentRepn == VistaIOFloatRepn) ||
 	       (ComponentRepn == VistaIODoubleRepn) ) ) return false;

	VistaIOLong nComponents = 0;
	if (VistaIOGetAttr(bundle->list, VistaIONComponentsAttr, NULL, VistaIOLongRepn, &nComponents) != VistaIOAttrFound) return false;
	if (nComponents != 6) return false;
	
	return true;
}


TDrawable* load_tensorfield(const std::string& name, const std::string& filename)
{
	TSmarties *result = NULL; 
	
	// open the file
	FILE *inf = fopen(filename.c_str(),"r");
	if (!inf) return result;
	
	// get the attribute list
	VistaIOAttrList attr_list = VistaIOReadFile(inf, Vista3DTensorFieldFromImageFilter);
	if (!attr_list) {
		fclose(inf);
		return result;
	}
	fclose(inf);
	
	VistaIOAttrListPosn posn;
	VistaIOFirstAttr(attr_list, &posn);

	while (VistaIOAttrExists(&posn) && !result) {
		
		switch (VistaIOGetAttrRepn(&posn)) {
			
		case VistaIOGraphRepn: 
			break;
			
		case VistaIOImageRepn: {
			VistaIOImage image = NULL;
			VistaIOGetAttrValue(&posn, 0, VistaIOImageRepn, &image);	
			if (image != NULL) result = create_smarties_from_tensorimage(image, name,1600000);
		} break;
		
		default: break;
		}
			
		VistaIONextAttr(&posn);
	}
	
	VistaIODestroyAttrList(attr_list);
	return result;
}



/********************************************************************************
 *										*
 *				wireframe loader				*
 *										*
 *******************************************************************************/

class TLine {
public:
	unsigned int start;
	unsigned int end;
	float weight;
};


class TNode {
public:
	C3DFVector loc;
	bool connected;
	float weight;
};


template <class NodeRepnType>
TWireFrame* load_vista_wireframe(VistaIOGraph graph, const std::string& name)
{
	int nfields = graph->nfields;	
	if (nfields != 5) return NULL;

	// at first get all nodes 
	
	VistaIONode node_src = static_cast<VistaIONode>(VistaIOGraphFirstNode(graph));
	
	// VistaIOGraph node references starting from 1
	std::vector<TNode> nodes(VistaIOGraphNNodes(graph)+1);

	unsigned int nodeindex = 1;
	// init nodevec
	while (node_src) {
		NodeRepnType *src_data = (NodeRepnType*)(node_src->data);
		nodes[nodeindex].loc = C3DFVector(C3DFVector(src_data[1], src_data[2], src_data[3]));
		nodes[nodeindex++].connected = (node_src->base.head != NULL);
		node_src = static_cast<VistaIONode>(VistaIOGraphNextNode(graph));		
	}

	// thereafter get all connections
	
	node_src = static_cast<VistaIONode>(VistaIOGraphFirstNode(graph));
	nodeindex = 1;

	std::list<TLine> lines;
	// for each node
	while (node_src) {
 		
		// get the first connection
		VistaIOAdjstruct *adjacency = node_src->base.head;
		
		// get all connections
		while (adjacency) {
			
			// if the connection is valid
			if (adjacency->id) {
				
				TLine line;
				line.start	= nodeindex;
				line.end	= adjacency->id;
				line.weight	= adjacency->weight;
				
				lines.push_back(line);
				
				// thereafter mark the connection as invalid (with id=0) if existing 
				VistaIOAdjstruct *rev_adj = VistaIOGraphGetNode(graph,adjacency->id)->base.head;

				while (rev_adj) {
					if (rev_adj->id == nodeindex) {
						rev_adj->id = 0;
						rev_adj = NULL;
					} else
						rev_adj = rev_adj->next;
				}
				// and this direction as well
				adjacency->id = 0;
			}
				
			// get the next connection of the current node
			adjacency = adjacency->next;
		}
		nodeindex++;
		node_src = static_cast<VistaIONode>(VistaIOGraphNextNode(graph));
	}

	// then get all paths
	std::list<unsigned int> *path;
	std::list< std::list<unsigned int>* > all_paths;
	
	nodeindex = 1;
	
	while (!lines.empty()) {
		
		path = new std::list<unsigned int>;
		path->push_back( lines.front().start );
		path->push_back( lines.front().end );
		lines.pop_front();
		
		// attach at the end
		bool found = false;
		do {
			found = false;
			nodeindex = path->back();
			auto ibeg = lines.begin();
			auto iend = lines.end();
			
			while (ibeg != iend) {
				
				if ( (*ibeg).start == nodeindex ) {
					path->push_back( (*ibeg).end );
					lines.erase(ibeg);
					ibeg = iend;
					found = true;
					continue;
				} 
				
				if ( (*ibeg).end == nodeindex ) {
					path->push_back( (*ibeg).start );
					lines.erase(ibeg);
					ibeg = iend;
					found = true;
					continue;
				}
				
				ibeg++;
			}
		} while (found);
		
		// attach at the beginning
		do {
			found = false;
			nodeindex = path->front();
			auto ibeg = lines.begin();
			auto iend = lines.end();
			
			while (ibeg != iend) {
				
				if ( (*ibeg).start == nodeindex ) {
					path->push_front( (*ibeg).end );
					lines.erase(ibeg);
					ibeg = iend;
					found = true;
					continue;
				}
				
				if ( (*ibeg).end == nodeindex ) {
					path->push_front( (*ibeg).start );
					lines.erase(ibeg);
					ibeg = iend;
					found = true;
					continue;
				}
				ibeg++;
			}
		} while (found);
		
		// store the current path
 		all_paths.push_back(path);
	}


	// now insert all available paths into the wireframe object
	
	auto pbeg = all_paths.begin();
	auto pend = all_paths.end();
	
	TWireFrame *wireframe = new TWireFrame(name);
	
	while (pbeg != pend) {

		// The first and last segments serve only to define the angle of the join 
		// at the very ends of the polycone
		TPolyCone *pc = new TPolyCone( (*pbeg)->size() + 2 );
		C3DDVector*	point_array  = pc->get_point_vec(); 
		double*		radius_array = pc->get_radius_vec();
		
		
		std::list< unsigned int >::iterator lbeg = (*pbeg)->begin();
		std::list< unsigned int >::iterator lend = (*pbeg)->end();

		*point_array++ = C3DDVector(0.0, 0.0, 0.0);
		*radius_array++ = 0.3;

		while (lbeg != lend) {
			
			*point_array++  = nodes[*lbeg].loc;
			*radius_array++ = 0.3;
			
			lbeg++;
		}
		
		*point_array  = C3DDVector(0.0, 0.0, 0.0);
		*radius_array = 0.3;
		
		delete *pbeg++;
		
		wireframe->add_polycone(pc);
	}

	// finally, insert all unconnected nodes into the wireframe object
	
	for (unsigned int idx=1 ; idx<nodes.size() ; idx++) {
		if (!nodes[idx].connected) wireframe->add_node(nodes[idx].loc);
	}
	
	return wireframe;
}

/** Filter function to avoid the loading of the whole vista file 
    in order to figure out that this is not the file we wanted.
    Used with VistaIOReadFile(FILE *file, VistaIOFilter *filter) it reads
    the attribute list of the file and also the associated data in case 
    the filter function returns true.
    This happens if:	1. repn==VistaIOGraphRepn, 
			2. componentrepn of the image is one of the types below 
			3. nFields==5
*/

VistaIOBoolean Vista3DWireframeFromGraphFilter (VistaIOBundle bundle, VistaIORepnKind repn) {

	if (repn != VistaIOGraphRepn) return false;
	
	VistaIOStringConst ComponentRepnString;
	VistaIOLong nFields = -1;
	
	if (VistaIOGetAttr(bundle->list, VistaIORepnAttr, NULL, VistaIOStringRepn, &ComponentRepnString) != VistaIOAttrFound) return false;

	VistaIORepnKind ComponentRepn = VistaIOLookupType(ComponentRepnString);
	
	if (!( (ComponentRepn == VistaIOUByteRepn) || 
 	       (ComponentRepn == VistaIOSByteRepn) ||
 	       (ComponentRepn == VistaIOShortRepn) ||
 	       (ComponentRepn == VistaIOLongRepn)  ||
 	       (ComponentRepn == VistaIOFloatRepn) ||
 	       (ComponentRepn == VistaIODoubleRepn) ) ) return false;
	
	if (VistaIOGetAttr(bundle->list, VistaIONNodeFieldsAttr, NULL, VistaIOLongRepn, &nFields) != VistaIOAttrFound) return false;
	if (nFields != 5) return false; 
	return true;
}



TDrawable* load_wireframe(const std::string& name, const std::string& filename) 
{
	TWireFrame *result = NULL; 
	
	// open the file
	FILE *inf = fopen(filename.c_str(),"r");
	if (!inf) return result;
	
	VistaIOAttrList attr_list = VistaIOReadFile(inf, Vista3DWireframeFromGraphFilter);
	if (!attr_list) {
		fclose(inf);
		return result;
	}
	fclose(inf);
	
	VistaIOGraph graph = NULL;
	VistaIOAttrListPosn posn;
	VistaIOFirstAttr(attr_list, &posn);
		
	while ( VistaIOAttrExists(&posn) && (graph==NULL) ) {
	
		if (VistaIOGetAttrRepn(&posn) == VistaIOGraphRepn) 
			VistaIOGetAttrValue(&posn, 0, VistaIOGraphRepn, &graph);
		
		VistaIONextAttr(&posn);
	}
	
	if (!graph) return result;
	
	switch (VistaIONodeRepn(graph)) {
	case VistaIOUByteRepn: 
		result = load_vista_wireframe<VistaIOUByte>(graph, name); 
		break;
	case VistaIOSByteRepn:
		result = load_vista_wireframe<VistaIOSByte>(graph, name); 
		break;
	case VistaIOShortRepn:
		result = load_vista_wireframe<VistaIOShort>(graph, name); 
		break;
	case VistaIOLongRepn:
		result = load_vista_wireframe<VistaIOLong>(graph, name); 
		break;
	case VistaIOFloatRepn:
		result = load_vista_wireframe<VistaIOFloat>(graph, name); 
		break;
	case VistaIODoubleRepn:
		result = load_vista_wireframe<VistaIODouble>(graph, name); 
		break;
	default: std::cout << "unknown node-repn in this graph\n" << endl;
	}

	VistaIODestroyAttrList(attr_list);
	return result;
}


/********************************************************************************
 *										*
 *				textur-image loader				*
 *										*
 *******************************************************************************/

/** Filter function to avoid the loading of the whole vista file 
    in order to figure out that this is not the file we wanted.
    Used with VistaIOReadFile(FILE *file, VistaIOFilter *filter) it reads
    the attribute list of the file and also the associated data in case 
    the filter function returns true.
    This happens if:	1. repn==VistaIOImageRepn, 
			2. pixelrepn of the image is one of the types below 
			3. component_repn=="scalar" (if present)
			4. nComponents==1 (if present).
*/
VistaIOBoolean Vista3DImageFilter (VistaIOBundle bundle, VistaIORepnKind repn) {

	if (repn != VistaIOImageRepn) return false;
	
	VistaIOAttrListPosn posn;
	if (VistaIOLookupAttr(bundle->list, "component_repn", &posn)) {
		VistaIOStringConst value;
		VistaIOGetAttrValue(&posn, NULL, VistaIOStringRepn, &value);
		std::string component_repn = value;
		if ( component_repn != "scalar" ) return false;
	}

	VistaIOStringConst ComponentRepnString;
	if (VistaIOGetAttr(bundle->list, VistaIORepnAttr, NULL, VistaIOStringRepn, &ComponentRepnString) != VistaIOAttrFound) return false;
	VistaIORepnKind ComponentRepn = VistaIOLookupType(ComponentRepnString);
	
	if (!( (ComponentRepn == VistaIOUByteRepn) || 
 	       (ComponentRepn == VistaIOSByteRepn) ||
 	       (ComponentRepn == VistaIOShortRepn) ||
 	       (ComponentRepn == VistaIOLongRepn)  ||
 	       (ComponentRepn == VistaIOFloatRepn) ||
 	       (ComponentRepn == VistaIODoubleRepn) ) ) return false;

	VistaIOLong nComponents = 0;
 	if ( (VistaIOGetAttr(bundle->list, VistaIONComponentsAttr, NULL, VistaIOLongRepn, &nComponents) == VistaIOAttrFound) &&
 	     (nComponents != 1) ) return false; 
	
	return true;
}

template <class PixelPointerRepn>
C3DUBImage load_vista_texture_images(const C3DBounds& size, PixelPointerRepn *Data) {

	C3DUBImage lumin(size);
	
	C3DUBImage::iterator i = lumin.begin();
	C3DUBImage::iterator e = lumin.end();
	
	std::cout << "[Image]\n\tbounds = " << size.x << " , " << size.y << " , " << size.z << std::endl;
	
	while( i != e ) {
		*i++ = static_cast<unsigned char>(*Data++);
	}
	
	return lumin;
}

bool load_texture_images(const std::string& filename, P3DTransformation defo,
			TDrawable **xy_slice, const std::string& xy_slice_name,
			TDrawable **xz_slice, const std::string& xz_slice_name, 
			TDrawable **yz_slice, const std::string& yz_slice_name)
{
	TWireFrame *result = NULL;
	
	// open the file
 	FILE *inf = fopen(filename.c_str(),"r");
 	if (!inf) return result;
 	
	VistaIOAttrList attr_list = VistaIOReadFile(inf, Vista3DImageFilter);
	if (!attr_list) {
		fclose(inf);
		return result;
	}
	fclose(inf);

	
	VistaIOAttrListPosn posn;
	VistaIOFirstAttr(attr_list, &posn);

	VistaIOImage image = NULL;
	while (VistaIOAttrExists(&posn) && (image==NULL)) {
		if (VistaIOGetAttrRepn(&posn) == VistaIOImageRepn) VistaIOGetAttrValue(&posn, 0, VistaIOImageRepn, &image);
		VistaIONextAttr(&posn);		
	}
	
	if (!image) 
		return false; 
	
	
	C3DUBImage lumindata(C3DBounds(0,0,0));
	
	
	C3DBounds imsize(image->ncolumns,image->nrows,image->nbands);
	switch (VistaIOPixelRepn(image)){
	case VistaIOBitRepn:   lumindata = load_vista_texture_images(imsize,&VistaIOPixel(image,0,0,0,VistaIOBit));break;
	case VistaIOUByteRepn: lumindata = load_vista_texture_images(imsize,&VistaIOPixel(image,0,0,0,VistaIOUByte));break;
	case VistaIOSByteRepn: lumindata = load_vista_texture_images(imsize,&VistaIOPixel(image,0,0,0,VistaIOSByte));break;
	case VistaIOShortRepn: lumindata = load_vista_texture_images(imsize,&VistaIOPixel(image,0,0,0,VistaIOShort));break;
	case VistaIOLongRepn:  lumindata = load_vista_texture_images(imsize,&VistaIOPixel(image,0,0,0,VistaIOLong));break;
	case VistaIOFloatRepn: lumindata = load_vista_texture_images(imsize,&VistaIOPixel(image,0,0,0,VistaIOFloat));break;
	case VistaIODoubleRepn:lumindata = load_vista_texture_images(imsize,&VistaIOPixel(image,0,0,0,VistaIODouble));break;
	default: fprintf(stderr,"Unknown pixel format - ignoring\n");
		VistaIODestroyAttrList(attr_list);
		return false; 
	}

	*xy_slice = new TXYGLSlice(xy_slice_name, lumindata, defo);
	*xz_slice = new TXZGLSlice(xz_slice_name, lumindata, defo);
	*yz_slice = new TYZGLSlice(yz_slice_name, lumindata, defo);
	
	return true;
	
}


