/*
 *	annotation.cpp - 3D annotations filter for 3depict
 *	Copyright (C) 2015, D Haley 

 *	This program 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.

 *	This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "voxelLoad.h"
#include "../APT/vtk.h"

using std::string;
using std::vector;
using std::pair;
using std::make_pair;

enum
{
	KEY_FILENAME=1,
	KEY_FILETYPE,
	KEY_ENABLE,
	KEY_OVERRIDE_BOUNDS,
	KEY_UPPER_BOUND,
	KEY_LOWER_BOUND,
	KEY_SHOW_BBOX,
	KEY_BBOX_COLOUR,
	KEY_OFFSET_VOXPROP, //This must be last, as it is an offset value
};



const char *FILE_TYPES[]  = { NTRANS("VTK (ascii)"),
				NTRANS("raw binary (float32)"),
				};

VoxelLoadFilter::VoxelLoadFilter()  : voxAppearance(this)
{
	pMin=Point3D(0,0,0);
	pMax=Point3D(10,10,10);

	overrideBounds=false;

	curVoxelFileType=VOXEL_FILETYPE_VTK_ASCII;

	enabled=true;
	cacheOK=false;
	cache=true; //By default, we should cache, but decision is made higher up
}

Filter *VoxelLoadFilter::cloneUncached() const
{
	VoxelLoadFilter *p=new VoxelLoadFilter();

	p->fileName=fileName;

	p->enabled=enabled;
	p->overrideBounds=overrideBounds;
	p->curVoxelFileType=curVoxelFileType;
			
	//We are copying whether to cache or not,
	//not the cache itself
	p->cache=cache;
	p->cacheOK=false;
	p->userString=userString;
	return p;
}

unsigned int VoxelLoadFilter::refresh(const std::vector<const FilterStreamData *> &dataIn,
	std::vector<const FilterStreamData *> &getOut, ProgressData &progress)
{

	//Clear selection devices, first deleting any we have
	clearDevices();

	progress.step=1;
	progress.maxStep=1;
	progress.stepName=TRANS("Load");
	
	//Pipe everything through
	propagateStreams(dataIn,getOut);

	//If we are not enabled, do not draw anything into the output
	if(!enabled  || fileName.empty())
	{
		progress.filterProgress=100;
		return 0;
	}

	cacheOK=false;

	if(!voxelCache.size())
	{
		//Load the voxel cache
		unsigned int errCode;
		switch(curVoxelFileType)
		{
			case VOXEL_FILETYPE_VTK_ASCII:
				//TODO: Improve error reporting
				errCode=vtk_read_legacy(fileName,VTK_ASCII, voxelCache);
				break;
		
			default:
				ASSERT(false);
				errCode=1;
		}

		if(errCode || !voxelCache.size())
			return VOXELLOAD_FILE_READFAIL;
		
	}

	ASSERT(voxelCache.size());
	
	Voxels<float> d;
	voxelCache.clone(d);
	//Override bounds
	if(overrideBounds)
		d.setBounds(pMin,pMax);

	
	if(voxAppearance.refresh(d,getOut,devices))
		return VOXELLOAD_ERR_GENERIC;

	d.getBounds(lastBounds);

	progress.filterProgress=100;
	return 0;
}

size_t VoxelLoadFilter::numBytesForCache(size_t nObjects) const
{
	
	return (voxelCache.size()*sizeof(float));
}

void VoxelLoadFilter::getProperties(FilterPropGroup &propertyList) const
{
	string tmpStr;
	size_t curGroup=0;

	FilterProperty pFilename(KEY_FILENAME,PROPERTY_TYPE_FILE,TRANS("File"),
		 fileName,TRANS("Set the data file to load"));
	
	pFilename.dataSecondary = TRANS("VTK Files (*.vtk)|*.vtk");
	propertyList.addProperty(pFilename,curGroup);


	vector<pair<unsigned int,string> > choices;
	for(unsigned int ui=0;ui<VOXEL_FILETYPE_ENUM_END; ui++)
	{
		string str;
		str=TRANS(FILE_TYPES[ui]);
		choices.emplace_back(ui,str);
	}

	FilterProperty pType(KEY_FILETYPE, TRANS("File Type"),
		 choices,curVoxelFileType,TRANS("Type of data that file contains"));
	propertyList.addProperty(pType,curGroup);
	
	FilterProperty pEnable(KEY_ENABLE, TRANS("Enable"),
		 enabled,TRANS("Enable/disable annotation"));
					
	propertyList.addProperty(pEnable,curGroup);
	propertyList.setGroupTitle(curGroup, TRANS("Data source"));

	curGroup++;
	
	if(enabled)
	{
		FilterProperty pManualBounds(KEY_OVERRIDE_BOUNDS, TRANS("Specify bounds"),
			 overrideBounds,TRANS("Manually specify the bounding box"));
		propertyList.addProperty(pManualBounds,curGroup);

		if(overrideBounds)
		{
			FilterProperty pOrigin(KEY_LOWER_BOUND, TRANS("Lower corner"),pMin,
					TRANS("Position of lower bound of voxels"));
			propertyList.addProperty(pOrigin,curGroup);
			
			FilterProperty pUp(KEY_UPPER_BOUND, TRANS("Upper corner"),pMax,
					TRANS("Position of upper bound of voxels"));
			propertyList.addProperty(pUp,curGroup);
			
			propertyList.setGroupTitle(curGroup,TRANS("Positioning"));
		}

	
	
	
		voxAppearance.getProperties(propertyList,curGroup,KEY_OFFSET_VOXPROP);

	}
	
}

bool VoxelLoadFilter::setProperty(  unsigned int key,
					const std::string &value, bool &needUpdate)
{
	string stripped=stripWhite(value);
	switch(key)
	{
		case KEY_FILENAME:
		{
			if(!applyPropertyNow(fileName,value,needUpdate))
				return false;
			break;
		}
		case KEY_ENABLE:
		{
			if(!applyPropertyNow(enabled,value,needUpdate))
				return false;
			break;
		}
		case KEY_LOWER_BOUND:
		{
			Point3D pTmp;
			if(!applyPropertyNow(pTmp,value,needUpdate))
				return false;
		
			//Check for inversion	
			for(unsigned int ui=0;ui<3;ui++)
			{
				if(pTmp[ui] >=pMax[ui])
					return false;
			}
			pMin=pTmp;
			break;
		}
		case KEY_UPPER_BOUND:
		{
			Point3D pTmp;
			if(!applyPropertyNow(pTmp,value,needUpdate))
				return false;

			for(unsigned int ui=0;ui<3;ui++)
			{
				if(pTmp[ui] <=pMin[ui])
					return false;
			}

			pMax=pTmp;
			break;
		}
		case KEY_OVERRIDE_BOUNDS:
		{
			if(!applyPropertyNow(overrideBounds,value,needUpdate))
				return false;
			break;
		}
		case KEY_FILETYPE:
		{
			if(!applyPropertyNow(curVoxelFileType,value,needUpdate))
				return false;
			break;
		}
		default:
		{
			bool handled;
			if(!voxAppearance.setProperty(KEY_OFFSET_VOXPROP,key,handled,value,
					cacheOK,needUpdate,filterOutputs))
				return false;

			if(!handled)
			{
				ASSERT(false);
			}
		}
	}

	return true;
}

std::string  VoxelLoadFilter::getSpecificErrString(unsigned int code) const
{
	ASSERT(code < VOXELLOAD_ERRS_ENUM_END);
	const char *errStrings[] = { "", 
					"Voxel file load failed",
					"FIXME: Set error string",
			};
	COMPILE_ASSERT(THREEDEP_ARRAYSIZE(errStrings)  == VOXELLOAD_ERRS_ENUM_END);

	return errStrings[code];
}

bool VoxelLoadFilter::writeState(std::ostream &f,unsigned int format, unsigned int depth) const
{
	using std::endl;
	switch(format)
	{
		case STATE_FORMAT_XML:
		{	
			f << tabs(depth) <<  "<" << trueName() << ">" << endl;
			f << tabs(depth+1) << "<userstring value=\""<< escapeXML(userString) << "\"/>"  << endl;
			
			f << tabs(depth+1) << "<filename value=\""<< escapeXML(fileName) << "\"/>" << endl; 
			f << tabs(depth+1) << "<enabled value=\""<< boolStrEnc(enabled) << "\"/>" << endl; 
			f << tabs(depth+1) << "<bounds min=\""<< 
				pMin << "\" max=\"" << pMax << "\"/>" << endl;  

			voxAppearance.writeState(f,format,depth);

			f << tabs(depth) << "</" <<trueName()<< ">" << endl;
			break;
		}
		default:
			ASSERT(false);
			return false;
	}

	return true;
}

bool VoxelLoadFilter::readState(xmlNodePtr &nodePtr, const std::string &stateFileDir)
{
	using std::string;
	string tmpStr;

	xmlChar *xmlString;

	//Retrieve user string
	//===
	if(XMLHelpFwdToElem(nodePtr,"userstring"))
		return false;

	xmlString=xmlGetProp(nodePtr,(const xmlChar *)"value");
	if(!xmlString)
		return false;
	userString=(char *)xmlString;
	xmlFree(xmlString);
	//===

	//FIXME: Implement me
	if(voxAppearance.readState(nodePtr))
		return false;

	return true;
}

unsigned int VoxelLoadFilter::getRefreshBlockMask() const
{
	return 0;
}

void VoxelLoadFilter::setPropFromBinding(const SelectionBinding &b)
{
	//TODO: Refactor - This shares code with VoxeliseFilter
	switch(b.getID())
	{
		case BINDING_PLANE_ORIGIN:
		{
			switch(voxAppearance.representation )
			{
				case VOXEL_REPRESENT_AXIAL_SLICE:
				{
					ASSERT(lastBounds.isValid());
				
					//Convert the world coordinate value into a
					// fractional value of voxel bounds
					Point3D p;
					float f;
					b.getValue(p);
					f=p[voxAppearance.sliceAxis];

					float minB,maxB;
					minB = lastBounds.getBound(voxAppearance.sliceAxis,0);
					maxB = lastBounds.getBound(voxAppearance.sliceAxis,1);
					voxAppearance.sliceOffset= (f -minB)/(maxB-minB);
					
					voxAppearance.sliceOffset=std::min(
							voxAppearance.sliceOffset,1.0f);
					voxAppearance.sliceOffset=std::max(
							voxAppearance.sliceOffset,0.0f);
					ASSERT(voxAppearance.sliceOffset<=1 && voxAppearance.sliceOffset>=0);
					break;
				}
				case VOXEL_REPRESENT_SLICE:
				{
					Point3D p;
					b.getValue(p);
					Point3D pb1,pb2;
					lastBounds.getBounds(pb1,pb2);
					//The slice plane is defiend using fractional
					// coordinates
					voxAppearance.freeSlicePlane = (p- pb1)/(pb2-pb1);
					break;
				}
				default:

					ASSERT(false);
			}
			break;
		}
		case BINDING_PLANE_DIRECTION:
		{
			//Normal change should only be set for free-slice
			ASSERT(voxAppearance.representation == VOXEL_REPRESENT_SLICE);
			Point3D p;
			b.getValue(p);
			p.normalise();

			voxAppearance.freeSliceNormal =p;
			break;
		}
		default:
			ASSERT(false);
	}
	return;
}

unsigned int VoxelLoadFilter::getRefreshEmitMask() const
{
	return  STREAM_TYPE_VOXEL | STREAM_TYPE_DRAW;
}

unsigned int VoxelLoadFilter::getRefreshUseMask() const
{
	//annotate only adds to the ignore mask, so 
	// we now essentially ignore all inputs, other than pass-through 
	return 0;
}


#ifdef DEBUG
#include "../APT/vtk.h"

bool roundTripVTKTest();

bool VoxelLoadFilter::runUnitTests()
{
	return roundTripVTKTest();
}

//FIXME: Move to VTK header ?
bool roundTripVTKTest()
{
	//Create a voxel dataset with a fixed size
	// and fixed set of values
	const unsigned int NX=10,NY=20, NZ=30;
	Voxels<float> vox;
	vox.resize(NX,NY,NZ);
	unsigned int nTotal=NX*NY*NZ;
	for(unsigned int ui=0;ui<nTotal;ui++)
		vox.setData(ui,ui);

	//FIXME: create temp file	
	string fName="tmp.vox";
	if(vtk_write_legacy(fName,VTK_ASCII,vox))
	{
		WARN(false,"Test inconclusive, unable to write VTK file");
		return true;
	}
	vox.clear();
	unsigned int errCode;
	Voxels<float> &vRef2=vox;
	errCode=vtk_read_legacy(fName,VTK_ASCII,vRef2);

	TEST(errCode==0,"VTK readback");
	TEST(vox.size() == nTotal,"VTK Voxel size");
	for(unsigned int ui=0;ui<nTotal;ui++)
	{
		TEST(EQ_TOL(vox.getData(ui),ui), "Voxel value OK");
	}
	TEST(vox.max() == nTotal-1, "VTK Max/min");
	TEST(vox.min() == 0, "VTK max/min");
	return true;
}

#endif
