/* -*- 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_3DVECTOR_HH
#define __MIA_3DVECTOR_HH 1

#include <typeinfo>
#include <assert.h>
#include <stdexcept>
#include <math.h>
#include <complex>
#include <iostream>
#include <type_traits>

#include <mia/core/defines.hh>
#include <mia/core/type_traits.hh>
#include <mia/core/attributetype.hh>
#include <mia/3d/defines3d.hh>

NS_MIA_BEGIN

/**
   @ingroup basic
   \brief A simple 3D vector type.

   This class is the template for a 3D vector that provides support for some common operators.
   \tparam T element type
*/

template < class T >
class T3DVector
{
public:
       /// vector element
       T x;
       /// vector element
       T y;
       /// vector element
       T z;

       /// typedef for generic programming
       typedef T value_type;

       /// standart constructor
       T3DVector(): x(T()), y(T()), z(T()) {};

       /// create a zero-vector, \a dim must be 3
       explicit T3DVector(int dim): x(T()), y(T()), z(T())
       {
              assert(dim == 3);
       }

       /// we provide the default copy mechanisms
       T3DVector(const T3DVector<T>& other) = default;

       /// we provide the default copy mechanisms
       T3DVector<T>& operator = (const T3DVector<T>& other) = default;

       /// constructor to construct vector from values
       T3DVector(const T& x_, const T& y_, const T& z_):
              x(x_), y(y_), z(z_)
       {
       }

       /// type casting copy constructor
       template <class in> explicit T3DVector(const T3DVector<in>& org):
              x(T(org.x)), y(T(org.y)), z(T(org.z))
       {
       }

       /// assignment from different vector type
       template <class in>
       T3DVector<T>& operator =(const T3DVector<in>& org)
       {
              x = org.x;
              y = org.y;
              z = org.z;
              return *this;
       }

       //! square of Euclidian norm of the vector
       double norm2()const
       {
              return x * x + y * y + z * z;
       }

       /// \returns the product of the vector components
       double product() const
       {
              return x * y * z;
       }
       /// \returns the Euclidian norm of the vector
       double norm()const
       {
              return sqrt(norm2());
       }

       /// \returns the dimension of vector (always 3)
       int size() const
       {
              return 3;
       }

       /// Fill the vector elements with value v
       void fill(T v)
       {
              x = y = z = v;
       }

       /**
          Implement the const operator [] for this type of vector
          \param i index
          \returns value at index
          \remark the performance of this needs to be analyzed. The compiler should be able
          to translate this to a simple memory access.
        */

       const T operator [](size_t i) const
       {
              assert(i < 3);

              switch (i) {
              case 0:
                     return x;

              case 1:
                     return y;

              case 2:
                     return z;

              default:
                     throw std::logic_error("Access to vectorelement out of range");
              }
       }

       /**
          Implement the writable operator [] for this type of vector
          \param i index
          \returns reference value at index
          \remark the performance of this needs to be analyzed. The compiler should be able
          to translate this to a simple memory access.
        */

       T& operator [](size_t i)
       {
              assert(i < 3);

              switch (i) {
              case 0:
                     return x;

              case 1:
                     return y;

              case 2:
                     return z;

              default:
                     throw std::logic_error("Access to vectorelement out of range");
              }
       }

       /// inplace addition
       T3DVector<T>& operator +=(const T3DVector<T>& a)
       {
              x += a.x;
              y += a.y;
              z += a.z;
              return *this;
       }

       /// inplace subtraction
       T3DVector<T>& operator -=(const T3DVector<T>& a)
       {
              x -= a.x;
              y -= a.y;
              z -= a.z;
              return *this;
       }

       /// inplace multiplication
       T3DVector<T>& operator *=(const double a)
       {
              x = T(x * a);
              y = T(y * a);
              z = T(z * a);
              return *this;
       }

       /// inplace component wise multiplication
       T3DVector<T>& operator *=(const T3DVector<T>& a)
       {
              x = T(x * a.x);
              y = T(y * a.y);
              z = T(z * a.z);
              return *this;
       }


       /// inplace divisison by a scalar
       T3DVector<T>& operator /=(const double a)
       {
              assert(a != 0.0);
              x = T(x / a);
              y = T (y / a);
              z = T(z / a);
              return *this;
       }

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

       /// print out the formatted vector to the stream
       void write(std::ostream& os)const
       {
              os  << x << "," << y << "," << z;
       }

       /// read the vector from a formatted string
       void read(std::istream& is)
       {
              char c;
              T r, s, t;
              is >> c;

              // if we get the opening delimiter '<' then we also expect the closing '>'
              // otherwise just read three 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;
                     }

                     is >> t;
                     is >> c;

                     if (c != '>') {
                            is.clear(std::ios::badbit);
                            return;
                     }

                     x = r;
                     y = s;
                     z = t;
              } else {
                     is.putback(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;
                     }

                     is >> t;
                     x = r;
                     y = s;
                     z = t;
              }
       }

       /// swizzle operator
       const T3DVector<T>&  xyz()const
       {
              return *this;
       }

       /// swizzle operator
       const T3DVector<T> xzy()const
       {
              return T3DVector<T>(x, z, y);
       }

       /// swizzle operator
       const T3DVector<T> yxz()const
       {
              return T3DVector<T>(y, x, z);
       }

       /// swizzle operator
       const T3DVector<T> yzx()const
       {
              return T3DVector<T>(y, z, x);
       }

       /// swizzle operator
       const T3DVector<T> zyx()const
       {
              return T3DVector<T>(z, y, x);
       }

       /// swizzle operator
       const T3DVector<T> zxy()const
       {
              return T3DVector<T>(z, x, y);
       }

       /// declare the vector (1,1,1)
       static T3DVector<T> _1;

       /// declare the vector (0,0,0)
       static T3DVector<T> _0;

       /// the number of elements this vector holds (=3)
       static const unsigned int  elements;
};


struct EAttributeType_3d : public EAttributeType {

       static const int vector_3d_bit = 0x40000;

       static bool is_vector3d(int type)
       {
              return type & vector_3d_bit;
       }
};

template <typename T>
struct attribute_type<T3DVector<T>> : public EAttributeType_3d {
       static const int value = attribute_type<T>::value | vector_3d_bit;
};


/// @cond NEVER
template <typename T>
struct atomic_data<T3DVector<T>> {
       typedef T type;
       static const int size;
};

template <typename T>
const int atomic_data<T3DVector<T>>::size = 3;
/// @endcond


/**
   Cross product of two 3D vectors
   \param a
   \param b
   \returns cross product a x b
 */

template <typename T>
T3DVector<T> cross(const T3DVector<T>& a, const T3DVector<T>& b)
{
       return T3DVector<T>(
                     a.y * b.z - b.y * a.z,
                     a.z * b.x - b.z * a.x,
                     a.x * b.y - b.x * a.y
              );
}


/// A way to get the norm of a T3DVector using \a faba syntax
template <class T> double fabs(const T3DVector<T>& t)
{
       return t.norm();
}

/// \returns the dot product of input vectors a and b
template <class T> double dot(const T3DVector<T>& a, const T3DVector<T>& b)
{
       return a.x * b.x + a. y * b.y + a.z * b.z;
}


/// A float 3D Vector
typedef T3DVector<float>    C3DFVector;

/// A double 3D Vector
typedef T3DVector<double>   C3DDVector;

/// A unsinged int 3D Vector (used for 3D field sizes)
typedef T3DVector<unsigned int>   C3DBounds;



/// stream output operator for 3DVector
template <class T>
std::ostream& operator << (std::ostream& os, const T3DVector<T>& v)
{
       v.write(os);
       return os;
}

/// stream input operator for 3DVector
template <class T>
std::istream& operator >> (std::istream& is, T3DVector<T>& v)
{
       v.read(is);
       return is;
}


/// vector addition
template <class T>
inline const T3DVector<T> operator +(const T3DVector<T>& a, const T3DVector<T>& b)
{
       T3DVector<T> tmp(a);
       tmp += b;
       return tmp;
}

/**
   Add operator for two 3D 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>
T3DVector<T> operator +(const T3DVector<T>& a, const T3DVector<S>& b)
{
       return T3DVector<T>(a.x + b.x, a.y + b.y, a.z + b.z);
}


/// vector subtraction
template <class T>
inline const T3DVector<T> operator -(const T3DVector<T>& a, const T3DVector<T>& b)
{
       T3DVector<T> tmp(a);
       tmp -= b;
       return tmp;
}

/// vector scalar product
template <class T>
inline const T3DVector<T> operator *(const T3DVector<T>& a, const T3DVector<T>& b)
{
       return T3DVector<T>(b.x * a.x, b.y * a.y, b.z * a.z);
}

/// vector division by scalar
template <class T>
inline const T3DVector<T> operator /(const T3DVector<T>& a, double f)
{
       assert(f != T());
       T3DVector<T> tmp (a);
       tmp /= f;
       return tmp;
}

/**
   component wise division of two vectors
 */

template <class T>
inline const T3DVector<T> operator / (const T3DVector<T>& a, const T3DVector<T>& b)
{
       assert(b.x != 0.0 && b.y != 0.0 && b.z != 0.0);
       return T3DVector<T>(a.x / b.x, a.y / b.y, a.z / b.z);
}


/// vector multiplication with scalar
template <class T>
inline const T3DVector<T> operator *(const T3DVector<T>& a, double f)
{
       T3DVector<T> tmp (a);
       tmp *= f;
       return tmp;
}


/// vector multiplication with scalar reverse order
template <class T>
inline const T3DVector<T> operator *(double f, const T3DVector<T>& a)
{
       return a * f;
}


/// 3D vector cross product
template <class T>
inline const T3DVector<T> operator ^(const T3DVector<T>& a, const T3DVector<T>& b)
{
       return T3DVector<T>( a.y * b.z - b.y * a.z,
                            a.z * b.x - b.z * a.x,
                            a.x * b.y - b.x * a.y);
}

/// comparison operator equal
template <class T>
inline bool operator == (const T3DVector<T>& a, const T3DVector<T>& b)
{
       return (b.x == a.x && b.y == a.y && b.z == a.z);
}

/// comparison operator not equal
template <class T>
inline bool operator != (const T3DVector<T>& a, const T3DVector<T>& b)
{
       return ! (a == b);
}

/// comparison less, returns true if all components of \a a are less then those of  \a b
template <class T>
bool operator <  (const T3DVector<T>& a, const T3DVector<T>& b)
{
       return (a.x < b.x && a.y < b.y && a.z < b.z);
}

/// comparison less or equal, returns true if all components of \a a are less or equal then those of  \a b
template <class T>
bool operator <= (const T3DVector<T>& b, const T3DVector<T>& a)
{
       return (b.x <= a.x && b.y <= a.y && b.z <= a.z);
}

/// comparison greater, returns true if all components of \a a are greater then those of  \a b
template <class T>
bool operator >  (const T3DVector<T>& b, const T3DVector<T>& a)
{
       return (b.x > a.x && b.y > a.y && b.z > a.z);
}

/// comparison greater or equal, returns true if all components of \a a are greater or equal then those of  \a b
template <class T>
bool operator >= (const T3DVector<T>& b, const T3DVector<T>& a)
{
       return (b.x >= a.x && b.y >= a.y && b.z >= a.z);
}
template <typename T >
T3DVector<T> T3DVector<T>::_1 = T3DVector<T>(1, 1, 1);

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

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



NS_MIA_END


#endif
