/* -*- mia-c++  -*-
 *
 * This file is part of MIA - a toolbox for medical image analysis 
 * Copyright (c) Leipzig, Madrid 1999-2017 Gert Wollny
 *
 * MIA 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 MIA; if not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifndef MIA_2D_VECTOR_HH
#define MIA_2D_VECTOR_HH
 
#include <cmath>
#include <cassert>
#include <stdexcept>
#include <ostream>
#include <istream>
#include <iomanip>
#include <type_traits>

// MIA specific
#include <mia/core/type_traits.hh>
#include <mia/core/errormacro.hh>
#include <mia/core/attributetype.hh>

NS_MIA_BEGIN

/**
   \ingroup basic
   \brief a 2D vector 
   
   A 2D vector that provides the usual set of operations required to handle such objects. 
   \tparam type of the elements 
 */
template <class T > class T2DVector {
public:	

	/// typedef for generic access to the element type 
	typedef T value_type; 
	
	/// first element 
	T x;
	
	/// second element 
	T y;
	
	/// a static for the value <1,1>. 
	static const T2DVector<T> _1; 

	/// a static for the value <0,0>. 
	static const T2DVector<T> _0; 
		
	T2DVector():x(T()),y(T()){}

	/** this constructor is required for some generic code 
	    It is explicit to avoid some problems with automatic assigment 
	 */ 
	explicit T2DVector(int dim):x(T()),y(T()){
		assert(dim ==2);
	}

	/**
	   Contruct the vector with the according elements 
	   \param _x 
	   \param _y 
	 */
	T2DVector(T _x, T _y):x(_x),y(_y){};

	
	/**
	   Automatically convert a 2D vector from another element type 
	 */
	template <typename In>
	T2DVector(const T2DVector<In>& in):
		x(in.x), 
		y(in.y)
		{
		}
	
	// Functions


	///@returns square norm of the vector 
	T norm2() const {
		return T(x * x + y * y);
	}

	///@returns norm of the vector 
	T norm() const {
		return T(sqrt(norm2()));
	}
	
	///@returns product of the elements of the vector  
	double product() const {
		return x * y; 
	}

	// Operators 

	/// in place addition 
	T2DVector& operator += (const T2DVector& a){
		x += a.x; y += a.y;
		return *this;
	}

	/// in place subtraction 
	T2DVector& operator -= (const T2DVector& a){
		x -= a.x; y -= a.y;
		return *this;
	}

	/// in place multiplication with a scalar 
	T2DVector& operator *= (double a){
		x *= a; y *= a;
		return *this;
	}

	/// in place element wise multiplication of two 2D vectors 
	T2DVector& operator *= (const T2DVector& a){
		x *= a.x; y *= a.y;
		return *this;
	}

	/// in place element wise division of two 2D vectors 
	T2DVector& operator /= (const T2DVector& a){
		if ( a.x == 0.0 || a.y == 0.0)
			throw std::invalid_argument("T2DVector<T>::operator /=: division by zero not allowed"); 
		x /= a.x; y /= a.y;
		return *this;
	}

	/// in place division by a scalar  
	T2DVector& operator /= (double a){
		if ( a == 0.0 )
			throw std::invalid_argument("T2DVector<T>::operator /=: division by zero not allowed"); 
		x /= a; y /= a;
		return *this;
	}

	T2DVector operator -() const {
		return 	T2DVector<T>(-x, -y); 
	}

	/// returns the size of this vector, always 2
	size_t size() const {
		return 2;
	}
	
	/** Array access operator for some generic code 
	    One should have a look how expensive this function is 
	 */ 
	T& operator [](int i) {
		switch (i) {
		case 0:return x; 
		case 1:return y; 
		default: {
			DEBUG_ASSERT_RELEASE_THROW(false, "access to value at (", i, ") outside of range (0-1)"); 
		}
		}
	}
	
	/** Array access operator for some generic code 
	    One should have a look how expensive this function is 
	 */ 
	const T& operator [](int i) const {
		switch (i) {
		case 0:return x; 
		case 1:return y; 
		default: {
			DEBUG_ASSERT_RELEASE_THROW(false, "access to value at (", i, ") outside of range (0-1)");
		}
		}
	}
	
	/// fill all the elements with the given value
	void fill(T v) {
		x = y = v; 
	}
	
	/// Equal operator 
	bool operator == (const T2DVector& a)const{
		return (x == a.x && y == a.y);
	}
	
	/// not equal operator 
	bool operator != (const T2DVector& a)const{
		return (! (*this == a));
	}

	/// print the vector to a stream with special formatting 
	void print(std::ostream& os) const {
		os  << x << "," << y; 
	}
	
	/// read the properly formatted 2D vector from a stream 
	void read(std::istream& is) {
		char c; 
		
		T r,s; 
		is >> c;
		// if we get the opening delimiter '<' then we also expect the closing '>'
		// otherwise just read two coma separated values. 
		// could use the BOOST lexicel cast for better error handling
		if (c == '<') {
			is >> r;
			is >> c; 
			if (c != ',') {
				is.clear(std::ios::badbit);
				return; 
			}
			is >> s; 
			is >> c; 
			if (c != '>') {
				is.clear(std::ios::badbit);
				return; 
			}
			x = r; 
			y = s; 
		}else {
			is.putback(c); 
			is >> r;
			is >> c; 
			if (c != ',') {
				is.clear(std::ios::badbit);
				return; 
			}
			is >> s; 
			x = r; 
			y = s; 
		}
		
	}

};


struct EAttributeType_2d : public EAttributeType {
	
	static const int vector_2d_bit = 0x20000; 
	
	static bool is_vector2d(int type) {
		return type & vector_2d_bit; 
        }
}; 

template <typename T> 
struct attribute_type<T2DVector<T>> : public EAttributeType_2d {
        static const int value = attribute_type<T>::value | vector_2d_bit;
}; 


/// @cond NEVER  

template <typename T> 
struct atomic_data<T2DVector<T> > {
	typedef T type;
	static const int size; 
}; 

template <typename T> 
const int atomic_data<T2DVector<T> >::size = 2; 

/// @endcond 

template <typename T> 
const T2DVector<T> T2DVector<T>::_1 = T2DVector<T>(1,1); 

template <typename T> 
const T2DVector<T> T2DVector<T>::_0 = T2DVector<T>(0,0); 

/**
   operator to write a 2D vector to a stream 
   \tparam type of the vector values 
   \param os output stream 
   \param a vector 
   \returns reference to stream to allow chaining of the operator 
 */
template <typename T>
std::ostream& operator << (std::ostream& os, const T2DVector<T>& a)
{
	a.print(os); 
	return os; 
}

/**
   operator to read a 2D vector from a stream 
   \tparam type of the vector values 
   \param is input stream 
   \param a vector 
   \returns reference to stream to allow chaining of the operator 
 */
template <typename T>
std::istream& operator >> (std::istream& is, T2DVector<T>& a)
{
	a.read(is); 
	return is; 
}

/**
   Add operator for two 2D vectors that hold the same data type 
   \tparam type of the vector values 
   \param a 
   \param b
   \returns a+b
 */
template <typename  T>
T2DVector<T> operator +(const T2DVector<T>& a, const T2DVector<T>& b)
{
	T2DVector<T> r(a); 
	r += b; 
	return r; 
}

/**
   Add operator for two 2D vectors that hold different data types 
   Target type is taken from the lhs operator 
   \tparam type of the vector values 
   \param a 
   \param b
   \returns a+b
 */
template <typename  T, typename  S>
T2DVector<T> operator +(const T2DVector<T>& a, const T2DVector<S>& b)
{
	return T2DVector<T>(a.x + b.x, a.y + b.y); 
}

/**
   Element wise multiplication  operator for two 2D vectors that hold the same data type 
   \tparam type of the vector values 
   \param a 
   \param b
   \returns <a.x*b.x, a.y*b.y>
 */
template <typename  T>
T2DVector<T> operator *(const T2DVector<T>& a, const T2DVector<T>& b)
{
	T2DVector<T> r(a); 
	r *= b; 
	return r; 
}

/**
   Element wise division  operator for two 2D vectors that hold the same data type. 
   Throws a std::invalid_argument exception if b.x=== or b.y==0. 
   \tparam type of the vector values 
   \param a 
   \param b
   \returns <a.x/b.x, a.y/b.y>
 */
template <typename  T>
T2DVector<T> operator /(const T2DVector<T>& a, const T2DVector<T>& b)
{
	T2DVector<T> r(a); 
	r /= b; 
	return r; 
}

/**
   Element wise subtraction operator for two 2D vectors that hold the same data type. 
   \tparam type of the vector values 
   \param a 
   \param b
   \returns a-b
 */

template <typename  T>
T2DVector<T> operator -(const T2DVector<T>& a, const T2DVector<T>& b)
{
	T2DVector<T> r(a); 
	r -= b; 
	return r; 
}

/**
   dot product for two 2D vectors that hold the same data type. 
   \tparam type of the vector values 
   \param a 
   \param b
   \returns a.x*b.x + a.y*b.y 
*/

template <typename  T>
T dot(const T2DVector<T>& a, const T2DVector<T>& b)
{
	return b.x * a.x + b.y * a.y; 
}

/**
   Division of a vector by a scalar. 
   Throws a std::invalid_argument exception if f==0. 
   \tparam type of the vector values 
   \param a 
   \param f
   \returns <a.x/f, b.y/f>
*/

template <typename  T>
T2DVector<T> operator / (const T2DVector<T>& a, double f)
{
	T2DVector<T> r(a); 
	r /= f; 
	return r; 
}

/**
   Multiplication of a vector with a scalar. 
   \tparam type of the vector values 
   \param a  vector 
   \param f scalar 
   \returns <a.x*f, b.y*f>
*/
template <typename  T>
T2DVector<T> operator * (const T2DVector<T>& a, double f)
{
	T2DVector<T> r(a); 
	r *= f; 
	return r; 
}

/**
   Multiplication of a scalar with a vector. 
   \tparam type of the vector values 
   \param f scalar 
   \param a vector 
   \returns <a.x*f, b.y*f>
*/
template <typename  T>
T2DVector<T> operator * (double f, const T2DVector<T>& a)
{
	return a * f; 
}

/**
   Comparison of a vector. Not the this less operator does not define 
   an order, since it is possible that a =/= b and !a<b && !b<a. 
   \param a 
   \param b 
   \returns true if both elements in a are less the the corresponding elements in b. 
 */
template <typename T, typename S>
bool operator < (const T2DVector<T>& a, const T2DVector<S>& b) 
{
	return a.x < b.x && a.y < b.y; 
}

template <typename T, template <typename> class Vector> 
struct cross_product {
	typedef T return_type; 
	static return_type apply(const Vector<T>& a, const Vector<T>& b) {
		return a.x * b.y - a.y * b.x;
	}; 
}; 

/**
   Cross product of two 2D vectors. Technically it's the 3D cross product with 
   the z-elements set to zero and ignoring all zero elements of the output vector 
   \param a
   \param b 
   \returns 2D cross product 
   \todo Check why does it use indirection to the apply-function? 
 */
template <typename  T>
T cross(const T2DVector<T>& a, const T2DVector<T>& b)
{
	return cross_product<T, T2DVector>::apply(a,b); 
}


template <typename T>
struct less_then<T2DVector<T> > {
	bool operator() (const T2DVector<T>& a, const T2DVector<T>& b) const {
		return a.y < b.y || (a.y == b.y && a.x < b.x);
	}
}; 


/// float valued 2D vector
typedef T2DVector<float>    C2DFVector;

/// double valued 2D vector
typedef T2DVector<double>   C2DDVector;

/// unsigned int valued 2D vector - used as 2D size parameter
typedef T2DVector<unsigned int>   C2DBounds;


NS_MIA_END

#endif

