/*
 * Description  : Class to hold point data
 * Author       : Krystof Valenta <mail: valenkry@fel.cvut.cz> 19.5.2011
 *
 * TO DO		: Triangulate method
 *				  Computation of representant points for SPT - average color / normal
 *				  ?? Outer octree hierarchy level - view frustum culling ??
 *
 * Purpose      : GeometryPoints stores points in sequential point trees ( SPTs )
 */

#ifndef __GEOMETRY_POINTS__H__
#define __GEOMETRY_POINTS__H__

#include <wx/sstream.h>
#include "flexilog.h"
#include "geometry.h"
#include <wx/txtstrm.h>
#include "VrutApi.h"

namespace VRUT
{
	///Structure for one 3D point
	struct VRUT_CORE_API Point
	{
		// point position		
		Vector3 position;
		// point normal
		Vector3 normal;
		// point color			
		Vector3 color;			
		// point size
		float size;
		// this variable holds information used to delete points, it can hold time of points creation, or video frame or ...
		float pointOrigin;	
	};

	///Sequential point tree, stores sequence o points allowing LOD drawing
	struct SequentialPointTree
	{
		///sequence of points, coarse level at the beginning, finest level at the end
		std::vector<Point> points;

		///number of LOD levels
		unsigned int levels;

		///pointer to array where number of points in individual levels
		unsigned int * levelPointCounts;

		///AABB of sequential point tree
		AABB SPTaabb;
	};

	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	///Geometry node, which stores point geometry, point are organized in sequential point trees allowing LOD
	class VRUT_CORE_API GeometryPoints : public Geometry
	{
	public:
		static const GEOMETRY_TYPE ClassType = GEOMETRY_POINT;

		///Octree node structure ( octree is built over points, then its levels are stored in onedimensional array along with number of points in each level - this allows LOD)
		struct OctreeNode
		{
			std::vector<Point*> points;
			AABB aabb;

			unsigned int nodeDepth;

			OctreeNode * parent;			
			OctreeNode * children[8];			
		};

		///Vector of sequential point trees
		///at [0] is actual sequential point tree, history of older SPTs follows if historySize > 0
		std::vector<SequentialPointTree *> SPTS;

		///Camera frame counter
		unsigned long frameCounter;

		///Size of history - how many SPTs are stored in history (probably camera frames)
		unsigned int historySize;

		///Draw whole history[true], draw only first SPT[false]
		bool drawHistory;

		///Mutex - to prevent data from being changed during rendering
		mutable wxCriticalSection mutexPoints;

		//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		///Class constructor
		GeometryPoints(const GEOMETRY_ID & _gid) : Geometry(_gid, ClassType), SPTS()
		{
			frameCounter = 0;
			historySize = 0;
			drawHistory = false;
		}

		///Copy constructor - copy including history
		GeometryPoints(const GeometryPoints & g) : Geometry(g) {
			for(size_t i = 0; i < g.SPTS.size(); i++)
			{
#if 0
				// $$ JB just shallow copy, since if allocated by the kernel
				// all deletions/updates must go through the kernel as well
				// which can cause big performance bottleneck for large dynamic point sets 
				SPTS.push_back(g.SPTS[i]);
				SPTS[i]->levels = g.SPTS[i]->levels;
				SPTS[i]->levelPointCounts = g.SPTS[i]->levelPointCounts;
				SPTS[i]->points = g.SPTS[i]->points;
				SPTS[i]->SPTaabb = g.SPTS[i]->SPTaabb;

#else
				// deep copy (original version)
				SPTS.push_back(new SequentialPointTree());
				SPTS[i]->levels = g.SPTS[i]->levels;
				SPTS[i]->levelPointCounts = new unsigned int[SPTS[i]->levels];

				for(unsigned int j = 0; j < SPTS[i]->levels ; j++ ) {
					SPTS[i]->levelPointCounts[j] = g.SPTS[i]->levelPointCounts[j];
				}

				SPTS[i]->points.assign( g.SPTS[i]->points.begin(), g.SPTS[i]->points.end() );
				SPTS[i]->SPTaabb = g.SPTS[i]->SPTaabb;
#endif
			}

			aabb = g.GetAABB();
			frameCounter = 0;
			historySize = g.historySize;
			drawHistory = g.drawHistory;
		}

		virtual ~GeometryPoints() {}

		///Build AABB - Geometry overload - build AABB only from current SPT = SPTS[0]
		virtual void BuildAABB()
		{
			if (!SPTS[0]->points.empty())
			{
				std::vector<Point>::const_iterator v = SPTS[0]->points.begin();
				aabb.MinBound = aabb.MaxBound = v->position;
				Vector3 * minB = &aabb.MinBound;
				Vector3 * maxB = &aabb.MaxBound;
				for (++v; v != SPTS[0]->points.end(); ++v)
				{
					if (v->position.x < minB->x)
						minB->x = v->position.x;
					else if (v->position.x > maxB->x)
						maxB->x = v->position.x;
					if (v->position.y < minB->y)
						minB->y = v->position.y;
					else if (v->position.y > maxB->y)
						maxB->y = v->position.y;
					if (v->position.z < minB->z)
						minB->z = v->position.z;
					else if (v->position.z > maxB->z)
						maxB->z = v->position.z;
				}
				///Fix zero volume AABB
				if (fabs(minB->x - maxB->x) < EPSILON)
				{
					minB->x -= EPSILON;
					maxB->x += EPSILON;
				}
				if (fabs(minB->y - maxB->y) < EPSILON)
				{
					minB->y -= EPSILON;
					maxB->y += EPSILON;
				}
				if (fabs(minB->z - maxB->z) < EPSILON)
				{
					minB->z -= EPSILON;
					maxB->z += EPSILON;
				}
			}
			else
			{
				aabb.SetInvalid();
			}
		}

		///Builds sequential point tree from point cloud
		///\param[in] inputPoints Points to build SPT from
		///\param[in] maxDepth Max SPT depth ( number of LODs )
		///\param[in] changeMainAABB True if AABB of whole Geometry is to be changed
		///\param[in] discardStrategy If false all points are store, otherwise only one point is stored in Octree leaves and other points are discarded
		///\return \c pointer to created SPT
		SequentialPointTree * BuildSPT( const std::vector<Point> &inputPoints, unsigned int maxDepth, bool changeMainAABB, bool discardStrategy);

		///Replaces active SPT with new SPT and pushes, old SPT to SPThistory - used by CameraToPoints
		///\param[in] inputPoints Points to build SPT from
		///\param[in] maxDepth Max SPT depth ( number of LODs )
		///\param[in] discardStrategy If false all points are store, otherwise only one point is stored in Octree leaves and other points are discarded
		void PushNewSPT( const std::vector<Point> &inputPoints, unsigned int maxDepth, bool discardStrategy);

		///Pushes points to current SPT, deletes points with pointOrigin < deleteOlderThan and rebuilds SPT
		///\param[in] inputPoints Points to insert to current SPT
		///\param[in] maxDepth If GeometryPoints does not contain any SPT, maxDepth will be used as parameter for new SPT creation
		///\param[in] deleteOlderThan Points with pointOrigin < than deleteolderThan will be deleted
		///\param[in] forceSPTdepth If true SPT will be rebuilt with maxDepth depth insead of SPTs current depth
		void PushPoints( const std::vector<Point> &inputPoints, unsigned int maxDepth, unsigned int deleteOlderThan, bool forceSPTdepth, bool discardStrategy );

		///Deletes points with pointOrigin < deleteOlderThan
		///\param[in] deleteOlderThan Delete threshold
		void DeletePoints( unsigned int deleteOlderThan );

		///Returns total number of points including history
		long GetTotalPoints() const;

		///Check if ray intersects geometry, get intersection point distance from ray origin if it does - Geometry overload
		///NOTE: everything must be in one space (model space by default), \c dist will be in that space too!
		virtual bool Intersects(const Ray & ray, float * dist = (float *)NULL) const
		{
			float bestDist = FLT_MAX;
			bool found = false;			

			for (unsigned int point = 0; point < SPTS[0]->points.size(); point++)
			{
				//Compute A, B and C coefficients
				float a = ray.direction.Dot(ray.direction);
				float b = 2 * ray.direction.Dot(ray.origin);
				float c = ray.origin.Dot(ray.origin) - (SPTS[0]->points[point].size * SPTS[0]->points[point].size);

				//Find discriminant
				float disc = b * b - 4 * a * c;

				// if discriminant is negative there are no real roots, so continue
				if (disc < 0)
					continue;

				float distSqrt = sqrtf(disc);
				float q;
				if (b < 0)
					q = (-b - distSqrt)/2.0;
				else
					q = (-b + distSqrt)/2.0;

				// compute t0 and t1
				float t0 = q / a;
				float t1 = c / q;

				// make sure t0 is smaller than t1
				if (t0 > t1)
				{
					// if t0 is bigger than t1 swap them around
					float temp = t0;
					t0 = t1;
					t1 = temp;
				}

				// if t1 is less than zero, the object is in the ray's negative direction
				// and consequently the ray misses the sphere
				if (t1 < 0)
					continue;

				// if t0 is less than zero, the intersection point is at t1
				if (t0 < 0)
				{
					if(t1 < bestDist)
					{
						found = true;
						bestDist = t1;
					}
				}
				// else the intersection point is at t0
				else
				{
					if(t0 < bestDist)
					{
						found = true;
						bestDist = t0;
					}
				}				
			}

			if (found && dist)
				*dist = bestDist;

			return found;
		}

		///Casts ray to intersect with current SPT = SPTS[0] ( intersection is check with sphere with diameter equal to poit size )
		virtual bool CastRay(const Ray & ray, RayIntersectionInfo &info) const
		{
			float bestDist = FLT_MAX;
			size_t bestPoint;
			bool found = false;			

			for (unsigned int point = 0; point < SPTS[0]->points.size(); point++)
			{
				//Compute A, B and C coefficients
				float a = ray.direction.Dot(ray.direction);
				float b = 2 * ray.direction.Dot(ray.origin);
				float c = ray.origin.Dot(ray.origin) - (SPTS[0]->points[point].size * SPTS[0]->points[point].size);

				//Find discriminant
				float disc = b * b - 4 * a * c;

				// if discriminant is negative there are no real roots, so continue
				if (disc < 0)
					continue;

				float distSqrt = sqrtf(disc);
				float q;
				if (b < 0)
					q = (-b - distSqrt)/2.0;
				else
					q = (-b + distSqrt)/2.0;

				// compute t0 and t1
				float t0 = q / a;
				float t1 = c / q;

				// make sure t0 is smaller than t1
				if (t0 > t1)
				{
					// if t0 is bigger than t1 swap them around
					float temp = t0;
					t0 = t1;
					t1 = temp;
				}

				// if t1 is less than zero, the object is in the ray's negative direction
				// and consequently the ray misses the sphere
				if (t1 < 0)
					continue;

				// if t0 is less than zero, the intersection point is at t1
				if (t0 < 0)
				{
					if(t1 < bestDist)
					{
						found = true;
						bestPoint = point;
						bestDist = t1;
					}
				}
				// else the intersection point is at t0
				else
				{
					if(t0 < bestDist)
					{
						found = true;
						bestPoint = point;
						bestDist = t0;
					}
				}				
			}

			if (found)
			{
				info.dist = bestDist;						
				info.primitiveIndex = bestPoint;								
				info.geometryID = GetID();
				info.normal = SPTS[0]->points[bestPoint].normal;

				return true;
			}
			return false;
		}

		///TODO: Get geometry as list of triangles
		virtual std::vector<Triangle> Triangulate() const
		{
			// TO DO
			return std::vector<Triangle>();
		}

		///TODO: Get geometry as list of triangles
		virtual std::vector<TriangleData> TriangulateData() const
		{
			// TO DO
			return std::vector<TriangleData>();
		}

		///Add box parallel to the XYZ axes
		virtual void AddBox(const Vector3 &position, const Vector3 &size)
		{
			Point boxPoint;
			boxPoint.color = Vector3(1.0, 1.0, 1.0);
			boxPoint.size = 0.1f;

			float box[6];
			box[0] = position._v[0];
			box[1] = position._v[1];
			box[2] = position._v[2];
			box[3] = position._v[0]+size._v[0];
			box[4] = position._v[1]+size._v[1];
			box[5] = position._v[2]+size._v[2];

			boxPoint.position = Vector3(box[0], box[1], box[2]);
			boxPoint.normal = Vector3(-1.0, -1.0, -1.0).Normalize();
			SPTS[0]->points.push_back(boxPoint);

			boxPoint.position = Vector3(box[3], box[1], box[2]);
			boxPoint.normal = Vector3( 1.0, -1.0, -1.0).Normalize();
			SPTS[0]->points.push_back(boxPoint);

			boxPoint.position = Vector3(box[0], box[4], box[2]);
			boxPoint.normal = Vector3(-1.0,  1.0, -1.0).Normalize();
			SPTS[0]->points.push_back(boxPoint);

			boxPoint.position = Vector3(box[0], box[1], box[5]);
			boxPoint.normal = Vector3(-1.0, -1.0,  1.0).Normalize();
			SPTS[0]->points.push_back(boxPoint);

			///

			boxPoint.position = Vector3(box[3], box[4], box[2]);
			boxPoint.normal = Vector3( 1.0,  1.0, -1.0).Normalize();
			SPTS[0]->points.push_back(boxPoint);

			boxPoint.position = Vector3(box[3], box[1], box[5]);
			boxPoint.normal = Vector3( 1.0, -1.0,  1.0).Normalize();
			SPTS[0]->points.push_back(boxPoint);

			boxPoint.position = Vector3(box[0], box[4], box[5]);
			boxPoint.normal = Vector3(-1.0,  1.0,  1.0).Normalize();
			SPTS[0]->points.push_back(boxPoint);

			boxPoint.position = Vector3(box[3], box[4], box[5]);
			boxPoint.normal = Vector3( 1.0,  1.0,  1.0).Normalize();
			SPTS[0]->points.push_back(boxPoint);

			// Set SPT depth
			unsigned int lvl = 1;
			if( SPTS[0]->points.size() > 8 ) lvl = SPTS[0]->levels;

			// Build/rebuild SPT
			rebuildSPT( SPTS[0], lvl - 1, true, false);
		}

		///Add content of another geometry - Adds only actual SPT, not the whole history
		virtual bool AddGeometry(const Geometry *geom)
		{	
			if (geom==NULL)
				return true;
			if (geom->type != GEOMETRY_POINT)
			{
				LOGERROR(wxString::Format(wxT("Cannot add geometry type %i to GEOMETRY_POINTS."), geom->type));
				return false;
			}
			GeometryPoints *geoPoints = (GeometryPoints *)geom;

			SPTS[0]->points.insert(SPTS[0]->points.end(), geoPoints->SPTS[0]->points.begin(), geoPoints->SPTS[0]->points.end());

			rebuildSPT( SPTS[0], SPTS[0]->levels - 1, true, false );

			return true;
		}

		virtual bool AddGeometries(const std::vector<Geometry *> &geoms)
		{
			size_t pointsCount=SPTS[0]->points.size();
			for (size_t i=0; i<geoms.size(); ++i)
			{
				if (!geoms[i])
					continue;
				if (geoms[i]->type!=Geometry::GEOMETRY_POINT)
				{
					LOGERROR(wxString::Format(wxT("Cannot add geometry type %i to GEOMETRY_POINT."), geoms[i]->type));
					continue;
				}
				GeometryPoints *geoPoints = (GeometryPoints *)geoms[i];
				pointsCount+=geoPoints->SPTS[0]->points.size();
			}
			SPTS[0]->points.resize(pointsCount);
			for (size_t i=0; i<geoms.size(); ++i)
			{
				if (!geoms[i] || (geoms[i]->type!=Geometry::GEOMETRY_POINT))
					continue;
				GeometryPoints *geoPoints = (GeometryPoints *)geoms[i];
				SPTS[0]->points.insert(SPTS[0]->points.end(), geoPoints->SPTS[0]->points.begin(), geoPoints->SPTS[0]->points.end());
			}
			rebuildSPT( SPTS[0], SPTS[0]->levels - 1, true, false );
			return true;
		}

		virtual bool Transform(const Matrix * /*matrix*/)
		{
			LOGWARNING(wxT("GeometryPoints::Transform is not implemented"));
			return false;
		}

		///Equal operator, TODO: implementation needs to be updated to handle the same geometries with different order of vertices or indices or primitives or different primitives with the same result...
		///Checks only actual SPTS not the whole history
		virtual bool IsSameAs(const smart_pointer<Geometry> & geo) const
		{
			if (geo->type != Geometry::GEOMETRY_POINT)
				return false;
			GeometryPoints * geoPoints = (GeometryPoints*)geo.getPtr();
			if ((SPTS[0]->points.size()!=geoPoints->SPTS[0]->points.size()))
				return false;
			for (size_t i=0; i<SPTS[0]->points.size(); i++)
			{
				if (SPTS[0]->points.at(i).position != geoPoints->SPTS[0]->points.at(i).position ||
					SPTS[0]->points.at(i).color != geoPoints->SPTS[0]->points.at(i).color ||
					SPTS[0]->points.at(i).normal != geoPoints->SPTS[0]->points.at(i).normal ||
					SPTS[0]->points.at(i).size != geoPoints->SPTS[0]->points.at(i).size)
				{
					return false;
				}				
			}
			return true;
		}

		virtual void Divide(std::vector<Geometry*> &/*segments*/) const
		{
			LOGERROR(wxT("Not implemented GeometryPoints::Divide"));
		}

		virtual void DivideToPrimitives(std::vector<Geometry*> &/*segments*/) const
		{
			LOGERROR(wxT("Not implemented GeometryPoints::DivideToPrimitives"));
		}

		virtual void FlipNormals()
		{
			LOGERROR(wxT("Not implemented GeometryPoints::FlipNormals"));
		}

		virtual void TextureMappingPlanar(const Matrix &/*texMatrix*/)
		{
			LOGERROR(wxT("Not implemented GeometryPoints::TextureMappingPlanar"));
		}

		virtual void TextureMappingTriplanar(const Matrix &/*texMatrix*/)
		{
			LOGERROR(wxT("Not implemented GeometryPoints::TextureMappingTriplanar"));
		}

		virtual void TextureMappingSpherical(const Matrix &/*texMatrix*/)
		{
			LOGERROR(wxT("Not implemented GeometryPoints::TextureMappingSpherical"));
		}

		virtual void Clear()
		{
			//TODO: Please check it, I don't know what should be done
			for (size_t i=0; i<SPTS.size(); ++i)
				SAFE_DELETE(SPTS[i]);
			SPTS.clear();
		}

		///Get string with  properties - \c Geometry overload
		virtual wxString ToString() const
		{
			wxString cloudStr;
			for (std::vector<Point>::const_iterator pointIt = SPTS[0]->points.begin(); pointIt != SPTS[0]->points.end(); ++pointIt)
			{
				cloudStr+=wxT("Position: ")+pointIt->position.ToString();
				cloudStr+=wxT("Normal: ")+pointIt->normal.ToString();
				cloudStr+=wxT("Color: ")+pointIt->color.ToString();
				cloudStr+=wxT("Size: ")+TextWriter::WriteToWxString(pointIt->size);
				cloudStr+=wxT("\n");
			}
			return Geometry::ToString()+wxT("Point cloud:")+cloudStr;
		}

		///Get copy of instance - \c Geometry overload
		virtual Geometry * Clone() const
		{
			return new GeometryPoints(*this);
		}


		//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	private:
		GeometryPoints &operator =(const GeometryPoints & gp);

		///Rebuils SPT - used, when some points were added
		///\param[in] toRebuild Pointer to SPT which is to rebuild
		///\param[in] maxDepth New max SPT depth ( number of LODs )
		///\param[in] changeMainAABB If true, AABB of whole geometry is changed
		///\param[in] discardStrategy If false all points are store, otherwise only one point is stored in Octree leaves and other points are discarded
		void rebuildSPT( SequentialPointTree * toRebuild, unsigned int maxDepth, bool changeMainAABB, bool discardStrategy );

		///Build AABB from points
		///\param[in] inputPoints Points over which to build AABB
		///\param[in] changeMainAABB If true, AABB of whole geometry is changed
		AABB buildAABBfromPoints( const std::vector<Point> &inputPoints, bool changeMainAABB );

		///Splits node, creating its 8 children
		///\param[in] n Node to split
		void splitNode( OctreeNode * n );

		///Gets child of input octreeNode, where is point p
		///\param[in] n Parent OctreeNode
		///\param[in] p Point to pick
		///\return \c Pointer to child of node 'n', where point 'p' lies
		OctreeNode * pickChild( OctreeNode * n, Point * p);

		///Computes representant point for node n - only from its direct children!
		///\param[in] n Node for which to compute representant point
		void computeRepresentant( OctreeNode * n);
	};
}

#endif
