/******************************************************************************
 *
 * MantaFlow fluid solver framework
 * Copyright 2011-2016 Tobias Pfaff, Nils Thuerey 
 *
 * This program is free software, distributed under the terms of the
 * GNU General Public License (GPL) 
 * http://www.gnu.org/licenses
 *
 * Basic vector class
 *
 ******************************************************************************/

#ifndef _VECTORBASE_H
#define _VECTORBASE_H

// get rid of windos min/max defines
#if defined(WIN32) || defined(_WIN32)
#	define NOMINMAX
#endif

#include <stdio.h>
#include <string>
#include <limits>
#include <iostream>
#include "general.h"

// if min/max are still around...
#if defined(WIN32) || defined(_WIN32)
#   undef min
#   undef max
#endif

// redefine usage of some windows functions
#if defined(WIN32) || defined(_WIN32)
#	ifndef snprintf 
#	define snprintf _snprintf
#	endif
#endif

// use which fp-precision? 1=float, 2=double
#ifndef FLOATINGPOINT_PRECISION
#   define FLOATINGPOINT_PRECISION 1
#endif

// VECTOR_EPSILON is the minimal vector length
// In order to be able to discriminate floating point values near zero, and
// to be sure not to fail a comparison because of roundoff errors, use this
// value as a threshold.
#if FLOATINGPOINT_PRECISION==1
	typedef float Real;
#   define VECTOR_EPSILON (1e-6f)
#else
	typedef double Real;
#   define VECTOR_EPSILON (1e-10)
#endif

#ifndef M_PI
#   define M_PI 3.1415926536
#endif
#ifndef M_E
#   define M_E  2.7182818284
#endif

namespace Manta
{

//! Basic inlined vector class
template<class S>
class Vector3D
{
public:
	//! Constructor
	inline Vector3D() : x(0),y(0),z(0) {}
	
	//! Copy-Constructor
	inline Vector3D ( const Vector3D<S> &v ) : x(v.x), y(v.y), z(v.z) {}

	//! Copy-Constructor
	inline Vector3D ( const float * v) : x((S)v[0]), y((S)v[1]), z((S)v[2]) {}

	//! Copy-Constructor
	inline Vector3D ( const double * v) : x((S)v[0]), y((S)v[1]), z((S)v[2]) {}
	
	//! Construct a vector from one S
	inline Vector3D ( S v) : x(v), y(v), z(v) {}
		
	//! Construct a vector from three Ss
	inline Vector3D ( S vx, S vy, S vz) : x(vx), y(vy), z(vz) {}

	// Operators
	
	//! Assignment operator
	inline const Vector3D<S>& operator= ( const Vector3D<S>& v ) {
		x = v.x;
		y = v.y;
		z = v.z;
		return *this;
	}
	//! Assignment operator
	inline const Vector3D<S>& operator= ( S s ) {
		x = y = z = s;
		return *this;
	}
	//! Assign and add operator
	inline const Vector3D<S>& operator+= ( const Vector3D<S>& v ) {
		x += v.x;
		y += v.y;
		z += v.z;
		return *this;
	}
	//! Assign and add operator
	inline const Vector3D<S>& operator+= ( S s ) {
		x += s;
		y += s;
		z += s;
		return *this;
	}
	//! Assign and sub operator
	inline const Vector3D<S>& operator-= ( const Vector3D<S>& v ) {
		x -= v.x;
		y -= v.y;
		z -= v.z;
		return *this;
	}
	//! Assign and sub operator
	inline const Vector3D<S>& operator-= ( S s ) {
		x -= s;
		y -= s;
		z -= s;
		return *this;
	}
	//! Assign and mult operator
	inline const Vector3D<S>& operator*= ( const Vector3D<S>& v ) {
		x *= v.x;
		y *= v.y;
		z *= v.z;
		return *this;
	}
	//! Assign and mult operator
	inline const Vector3D<S>& operator*= ( S s ) {
		x *= s;
		y *= s;
		z *= s;
		return *this;
	}
	//! Assign and div operator
	inline const Vector3D<S>& operator/= ( const Vector3D<S>& v ) {
		x /= v.x;
		y /= v.y;
		z /= v.z;
		return *this;        
	}
	//! Assign and div operator
	inline const Vector3D<S>& operator/= ( S s ) {        
		x /= s;
		y /= s;
		z /= s;
		return *this;        
	}
	//! Negation operator
	inline Vector3D<S> operator- () const {
		return Vector3D<S> (-x, -y, -z);        
	}
	
	//! Get smallest component 
	inline S min() const {
	return ( x<y ) ? ( ( x<z ) ? x:z ) : ( ( y<z ) ? y:z );
	}
	//! Get biggest component
	inline S max() const {
		return ( x>y ) ? ( ( x>z ) ? x:z ) : ( ( y>z ) ? y:z );
	}
		
	//! Test if all components are zero
	inline bool empty() {
		return x==0 && y==0 && z==0;
	}

	//! access operator
	inline S& operator[] ( unsigned int i ) { 
		return value[i];
	}
	//! constant access operator
	inline const S& operator[] ( unsigned int i ) const {
		return value[i];
	}

	//! debug output vector to a string
	std::string toString() const;
	
	//! test if nans are present
	bool isValid() const; 

	//! actual values
	union {
		S value[3];
		struct {
			S x;
			S y;
			S z;
		};
		struct {
			S X;
			S Y;
			S Z;
		};
	};

	//! zero element
	static const Vector3D<S> Zero, Invalid;
		
	//! For compatibility with 4d vectors (discards 4th comp)
	inline Vector3D ( S vx, S vy, S vz, S vDummy) : x(vx), y(vy), z(vz) {}
	
protected:

};

//************************************************************************
// Additional operators
//************************************************************************

//! Addition operator
template<class S> 
inline Vector3D<S> operator+ ( const Vector3D<S> &v1, const Vector3D<S> &v2 ) {
	return Vector3D<S> ( v1.x+v2.x, v1.y+v2.y, v1.z+v2.z );
}
//! Addition operator
template<class S, class S2> 
inline Vector3D<S> operator+ ( const Vector3D<S>& v, S2 s ) {
	return Vector3D<S> ( v.x+s, v.y+s, v.z+s );
}
//! Addition operator
template<class S, class S2> 
inline Vector3D<S> operator+ ( S2 s, const Vector3D<S>& v ) {
	return Vector3D<S> ( v.x+s, v.y+s, v.z+s );
}

//! Subtraction operator
template<class S> 
inline Vector3D<S> operator- ( const Vector3D<S> &v1, const Vector3D<S> &v2 ) {
	return Vector3D<S> ( v1.x-v2.x, v1.y-v2.y, v1.z-v2.z );
}
//! Subtraction operator
template<class S, class S2> 
inline Vector3D<S> operator- ( const Vector3D<S>& v, S2 s ) {
	return Vector3D<S> ( v.x-s, v.y-s, v.z-s );
}
//! Subtraction operator
template<class S, class S2>
inline Vector3D<S> operator- ( S2 s, const Vector3D<S>& v ) {
	return Vector3D<S> ( s-v.x, s-v.y, s-v.z );
}

//! Multiplication operator
template<class S>
inline Vector3D<S> operator* ( const Vector3D<S> &v1, const Vector3D<S> &v2 ) {
	return Vector3D<S> ( v1.x*v2.x, v1.y*v2.y, v1.z*v2.z );
}
//! Multiplication operator
template<class S, class S2>
inline Vector3D<S> operator* ( const Vector3D<S>& v, S2 s ) {
	return Vector3D<S> ( v.x*s, v.y*s, v.z*s );
}
//! Multiplication operator
template<class S, class S2> 
inline Vector3D<S> operator* ( S2 s, const Vector3D<S>& v ) {
	return Vector3D<S> ( s*v.x, s*v.y, s*v.z );
}

//! Division operator
template<class S>
inline Vector3D<S> operator/ ( const Vector3D<S> &v1, const Vector3D<S> &v2 ) {
	return Vector3D<S> ( v1.x/v2.x, v1.y/v2.y, v1.z/v2.z );
}
//! Division operator
template<class S, class S2>
inline Vector3D<S> operator/ ( const Vector3D<S>& v, S2 s ) {
	return Vector3D<S> ( v.x/s, v.y/s, v.z/s );
}
//! Division operator
template<class S, class S2> 
inline Vector3D<S> operator/ ( S2 s, const Vector3D<S>& v ) {
	return Vector3D<S> ( s/v.x, s/v.y, s/v.z );
}

//! Comparison operator
template<class S>
inline bool operator== (const Vector3D<S>& s1, const Vector3D<S>& s2) {
	return s1.x == s2.x && s1.y == s2.y && s1.z == s2.z;
}

//! Comparison operator
template<class S>
inline bool operator!= (const Vector3D<S>& s1, const Vector3D<S>& s2) {
	return s1.x != s2.x || s1.y != s2.y || s1.z != s2.z;
}

//************************************************************************
// External functions
//************************************************************************

//! Min operator
template<class S>
inline Vector3D<S> vmin (const Vector3D<S>& s1, const Vector3D<S>& s2) {
	return Vector3D<S>(std::min(s1.x,s2.x), std::min(s1.y,s2.y), std::min(s1.z,s2.z));
}

//! Min operator
template<class S, class S2>
inline Vector3D<S> vmin (const Vector3D<S>& s1, S2 s2) {
	return Vector3D<S>(std::min(s1.x,s2), std::min(s1.y,s2), std::min(s1.z,s2));
}

//! Min operator
template<class S1, class S>
inline Vector3D<S> vmin (S1 s1, const Vector3D<S>& s2) {
	return Vector3D<S>(std::min(s1,s2.x), std::min(s1,s2.y), std::min(s1,s2.z));
}

//! Max operator
template<class S>
inline Vector3D<S> vmax (const Vector3D<S>& s1, const Vector3D<S>& s2) {
	return Vector3D<S>(std::max(s1.x,s2.x), std::max(s1.y,s2.y), std::max(s1.z,s2.z));
}

//! Max operator
template<class S, class S2>
inline Vector3D<S> vmax (const Vector3D<S>& s1, S2 s2) {
	return Vector3D<S>(std::max(s1.x,s2), std::max(s1.y,s2), std::max(s1.z,s2));
}

//! Max operator
template<class S1, class S>
inline Vector3D<S> vmax (S1 s1, const Vector3D<S>& s2) {
	return Vector3D<S>(std::max(s1,s2.x), std::max(s1,s2.y), std::max(s1,s2.z));
}

//! Dot product
template<class S>
inline S dot ( const Vector3D<S> &t, const Vector3D<S> &v ) {
	return t.x*v.x + t.y*v.y + t.z*v.z;    
}

//! Cross product
template<class S>
inline Vector3D<S> cross ( const Vector3D<S> &t, const Vector3D<S> &v ) {
	Vector3D<S> cp (
		( ( t.y*v.z ) - ( t.z*v.y ) ),
		( ( t.z*v.x ) - ( t.x*v.z ) ),
		( ( t.x*v.y ) - ( t.y*v.x ) ) );
	return cp;
}

//! Project a vector into a plane, defined by its normal
/*! Projects a vector into a plane normal to the given vector, which must
  have unit length. Self is modified.
  \param v The vector to project
  \param n The plane normal
  \return The projected vector */
template<class S>
inline const Vector3D<S>& projectNormalTo ( const Vector3D<S>& v, const Vector3D<S> &n) {
	S sprod = dot (v, n);
	return v - n * dot(v, n);
}

//! Compute the magnitude (length) of the vector
//! (clamps to 0 and 1 with VECTOR_EPSILON)
template<class S>
inline S norm ( const Vector3D<S>& v ) {
	S l = v.x*v.x + v.y*v.y + v.z*v.z;
	if     (        l      <= VECTOR_EPSILON*VECTOR_EPSILON ) return(0.);
	return ( fabs ( l-1. ) <  VECTOR_EPSILON*VECTOR_EPSILON ) ? 1. : sqrt ( l );
}

//! Compute squared magnitude
template<class S>
inline S normSquare ( const Vector3D<S>& v ) {
	return v.x*v.x + v.y*v.y + v.z*v.z;
}

//! compatibility, allow use of int, Real and Vec inputs with norm/normSquare
inline Real norm(const Real v) { return fabs(v); }
inline Real normSquare(const Real v) { return square(v); }
inline Real norm(const int v) { return abs(v); }
inline Real normSquare(const int v) { return square(v); }

//! Returns a normalized vector
template<class S>
inline Vector3D<S> getNormalized ( const Vector3D<S>& v ) {
	S l = v.x*v.x + v.y*v.y + v.z*v.z;
	if ( fabs ( l-1. ) < VECTOR_EPSILON*VECTOR_EPSILON )
		return v; /* normalized "enough"... */
	else if ( l > VECTOR_EPSILON*VECTOR_EPSILON )
	{
		S fac = 1./sqrt ( l );
		return Vector3D<S> ( v.x*fac, v.y*fac, v.z*fac );
	}
	else
		return Vector3D<S> ( ( S ) 0 );
}

//! Compute the norm of the vector and normalize it.
/*! \return The value of the norm */
template<class S>
inline S normalize ( Vector3D<S> &v ) {
	S norm;
	S l = v.x*v.x + v.y*v.y + v.z*v.z;
	if ( fabs ( l-1. ) < VECTOR_EPSILON*VECTOR_EPSILON ) {
		norm = 1.;
	} else if ( l > VECTOR_EPSILON*VECTOR_EPSILON ) {
		norm = sqrt ( l );
		v *= 1./norm;        
	} else {
		v = Vector3D<S>::Zero;
		norm = 0.;
	}
	return ( S ) norm;
}

//! Obtain an orthogonal vector
/*! Compute a vector that is orthonormal to the given vector.
 *  Nothing else can be assumed for the direction of the new vector.
 *  \return The orthonormal vector */
template<class S> 
Vector3D<S> getOrthogonalVector(const Vector3D<S>& v) {
	// Determine the  component with max. absolute value
	int maxIndex= ( fabs ( v.x ) > fabs ( v.y ) ) ? 0 : 1;
	maxIndex= ( fabs ( v[maxIndex] ) > fabs ( v.z ) ) ? maxIndex : 2;

	// Choose another axis than the one with max. component and project
	// orthogonal to self
	Vector3D<S> o ( 0.0 );
	o[ ( maxIndex+1 ) %3]= 1;
	
	Vector3D<S> c = cross(v, o);
	normalize(c);
	return c;    
}

//! Convert vector to polar coordinates
/*! Stable vector to angle conversion
 *\param v vector to convert
  \param phi unique angle [0,2PI]
  \param theta unique angle [0,PI]
 */
template<class S>
inline void vecToAngle ( const Vector3D<S>& v, S& phi, S& theta )
{
	if ( fabs ( v.y ) < VECTOR_EPSILON )
		theta = M_PI/2;
	else if ( fabs ( v.x ) < VECTOR_EPSILON && fabs ( v.z ) < VECTOR_EPSILON )
		theta = ( v.y>=0 ) ? 0:M_PI;
	else
		theta = atan ( sqrt ( v.x*v.x+v.z*v.z ) /v.y );
	if ( theta<0 ) theta+=M_PI;

	if ( fabs ( v.x ) < VECTOR_EPSILON )
		phi = M_PI/2;
	else
		phi = atan ( v.z/v.x );
	if ( phi<0 ) phi+=M_PI;
	if ( fabs ( v.z ) < VECTOR_EPSILON )
		phi = ( v.x>=0 ) ? 0 : M_PI;
	else if ( v.z < 0 )
		phi += M_PI;
}

//! Compute vector reflected at a surface
/*! Compute a vector, that is self (as an incoming vector) 
 * reflected at a surface with a distinct normal vector. 
 * Note that the normal is reversed, if the scalar product with it is positive.
  \param t The incoming vector
  \param n The surface normal
  \return The new reflected vector
  */
template<class S>
inline Vector3D<S> reflectVector ( const Vector3D<S>& t, const Vector3D<S>& n ) {
	Vector3D<S> nn= ( dot ( t, n ) > 0.0 ) ? ( n*-1.0 ) : n;
	return ( t - nn * ( 2.0 * dot ( nn, t ) ) );
}

//! Compute vector refracted at a surface
/*! \param t The incoming vector
 *  \param n The surface normal
 *  \param nt The "inside" refraction index
 *  \param nair The "outside" refraction index
 *  \param refRefl Set to 1 on total reflection
 *  \return The refracted vector
*/
template<class S>
inline Vector3D<S> refractVector ( const Vector3D<S> &t, const Vector3D<S> &normal, S nt, S nair, int &refRefl ) {
	// from Glassner's book, section 5.2 (Heckberts method)
	S eta = nair / nt;
	S n = -dot ( t, normal );
	S tt = 1.0 + eta*eta* ( n*n-1.0 );
	if ( tt<0.0 ) {
		// we have total reflection!
		refRefl = 1;
	} else {
		// normal reflection
		tt = eta*n - sqrt ( tt );
		return ( t*eta + normal*tt );
	}
	return t;
}

//! Outputs the object in human readable form as string
template<class S> std::string Vector3D<S>::toString() const {
	char buf[256];
	snprintf ( buf,256,"[%+4.6f,%+4.6f,%+4.6f]", ( double ) ( *this ) [0], ( double ) ( *this ) [1], ( double ) ( *this ) [2] );
	// for debugging, optionally increase precision:
	//snprintf ( buf,256,"[%+4.16f,%+4.16f,%+4.16f]", ( double ) ( *this ) [0], ( double ) ( *this ) [1], ( double ) ( *this ) [2] );
	return std::string ( buf );
}

template<> std::string Vector3D<int>::toString() const;


//! Outputs the object in human readable form to stream
/*! Output format [x,y,z] */
template<class S>
std::ostream& operator<< ( std::ostream& os, const Vector3D<S>& i ) {
	os << i.toString();
	return os;
}

//! Reads the contents of the object from a stream 
/*! Input format [x,y,z] */
template<class S>
std::istream& operator>> ( std::istream& is, Vector3D<S>& i ) {
	char c;
	char dummy[3];
	is >> c >> i[0] >> dummy >> i[1] >> dummy >> i[2] >> c;
	return is;
}

/**************************************************************************/
// Define default vector alias
/**************************************************************************/

//! 3D vector class of type Real (typically float)
typedef Vector3D<Real>  Vec3;

//! 3D vector class of type int
typedef Vector3D<int>  Vec3i;

//! convert to Real Vector
template<class T> inline Vec3 toVec3 ( T v ) {
	return Vec3 ( v[0],v[1],v[2] );
}

//! convert to int Vector
template<class T> inline Vec3i toVec3i ( T v ) {
	return Vec3i ( ( int ) v[0], ( int ) v[1], ( int ) v[2] );
}

//! convert to int Vector
template<class T> inline Vec3i toVec3i ( T v0, T v1, T v2 ) {
	return Vec3i ( ( int ) v0, ( int ) v1, ( int ) v2 );
}

//! round, and convert to int Vector
template<class T> inline Vec3i toVec3iRound ( T v ) {
	return Vec3i ( ( int ) round ( v[0] ), ( int ) round ( v[1] ), ( int ) round ( v[2] ) );
}

//! convert to int Vector if values are close enough to an int
template<class T> inline Vec3i toVec3iChecked ( T v ) {    
	Vec3i ret;
	for (size_t i=0; i<3; i++) {
		Real a = v[i];
		if (fabs(a-floor(a+0.5)) > 1e-5)
			errMsg("argument is not an int, cannot convert");    
		ret[i] = (int) (a+0.5);
	}
	return ret;
}

//! convert to double Vector
template<class T> inline Vector3D<double> toVec3d ( T v ) {
	return Vector3D<double> ( v[0], v[1], v[2] );
}

//! convert to float Vector
template<class T> inline Vector3D<float> toVec3f ( T v ) {
	return Vector3D<float> ( v[0], v[1], v[2] );
}


/**************************************************************************/
// Specializations for common math functions
/**************************************************************************/

template<> inline Vec3 clamp<Vec3>(const Vec3& a, const Vec3& b, const Vec3& c) {
	return Vec3 ( clamp(a.x, b.x, c.x),
				  clamp(a.y, b.y, c.y),
				  clamp(a.z, b.z, c.z) );    
}
template<> inline Vec3 safeDivide<Vec3>(const Vec3 &a, const Vec3& b) { 
	return Vec3(safeDivide(a.x,b.x), safeDivide(a.y,b.y), safeDivide(a.z,b.z));
}
template<> inline Vec3 nmod<Vec3>(const Vec3& a, const Vec3& b) {
	return Vec3(nmod(a.x,b.x),nmod(a.y,b.y),nmod(a.z,b.z));
}

}; // namespace


#endif
