#ifndef __CUVECT3_HPP__
#define __CUVECT3_HPP__

namespace amscuda
{

    class cuvect3
    {
        public:
        double x;
        double y;
        double z;

        __host__ __device__ cuvect3();
        __host__ __device__ ~cuvect3();
        __host__ __device__ cuvect3(const double &_x, const double &_y, const double &_z);
        
        
        __host__ __device__ double& operator[](const int &I);
        __host__ __device__ const double& operator[](const int &I) const;

        __host__ __device__ cuvect3 operator+(const cuvect3 &rhs);
        __host__ __device__ cuvect3 operator-(const cuvect3 &rhs);
        __host__ __device__ cuvect3 operator*(const double &rhs);
        __host__ __device__ cuvect3 operator/(const double &rhs);
        __host__ __device__ friend cuvect3 operator-(const cuvect3 &rhs);

        __host__ __device__ cuvect3& operator+=(const cuvect3 &rhs);
        __host__ __device__ cuvect3& operator-=(const cuvect3 &rhs);
        __host__ __device__ cuvect3& operator/=(const double &rhs);
        __host__ __device__ cuvect3& operator*=(const double &rhs);

    };

    class cumat3
    {
        public:
        double m00,m10,m20; //named references to force register use?
        double m01,m11,m21; //switched to column-major-order to match GLSL/lapack
        double m02,m12,m22;

        __host__ __device__ cumat3();
        __host__ __device__ ~cumat3();
        __host__ __device__ cumat3(
            const double & _m00, const double & _m01, const double & _m02,
            const double & _m10, const double & _m11, const double & _m12,
            const double & _m20, const double & _m21, const double & _m22
        );

        __host__ __device__ double& operator[](const int &I);
        __host__ __device__ double& operator()(const int &I, const int &J);
        __host__ __device__ double& at(const int &I, const int &J);

        __host__ __device__ const double& operator[](const int &I) const;
        __host__ __device__ const double& operator()(const int &I, const int &J) const;
        __host__ __device__ const double& at(const int &I, const int &J) const;

        __host__ __device__ cumat3 operator+(const cumat3 &rhs);
        __host__ __device__ cumat3 operator-(const cumat3 &rhs);
        __host__ __device__ cumat3 operator*(const double &rhs);
        __host__ __device__ cumat3 operator/(const double &rhs);
        __host__ __device__ cuvect3 operator*(const cuvect3 &rhs);
        __host__ __device__ cumat3 operator*(const cumat3 &rhs);
        __host__ __device__ friend cumat3 operator-(const cumat3 &rhs);
        
        __host__ __device__ double det();
        __host__ __device__ cumat3 transpose();
        __host__ __device__ cumat3 inverse();

        __host__ __device__ double* data(); //pointer to double[9] representation of matrix
        __host__ __device__ const double* data() const; //pointer to double[9] representation of matrix

        //In place operations (to save GPU register use)
        __host__ __device__ cumat3& operator+=(const cumat3 &rhs);
        __host__ __device__ cumat3& operator-=(const cumat3 &rhs);
        __host__ __device__ cumat3& operator/=(const double &rhs);
        __host__ __device__ cumat3& operator*=(const double &rhs);
        __host__ __device__ cumat3& operator*=(const cumat3 &rhs);
        
    };

    __host__ __device__ double cuvect3_dot(const cuvect3 &a,const cuvect3 &b);
    __host__ __device__ cuvect3 cuvect3_cross(const cuvect3 &a, const cuvect3 &b);
    __host__ __device__ double cuvect3_norm(const cuvect3 &a);
    __host__ __device__ cuvect3 cuvect3_normalize(const cuvect3 &a);
    __host__ __device__ cuvect3 cuvect3_proj(const cuvect3 &a, const cuvect3 &b);

    __host__ __device__ cumat3 hodge_dual(const cuvect3 &vin);
    __host__ __device__ cuvect3 hodge_dual(const cumat3 &min);
    __host__ __device__ cumat3 rotmat_from_axisangle(const cuvect3 &axis, const double &angle);


    //3x3 matrix operations
    //matrix order is assumed to be mat[I,J] = mat[I+3*J]
    
    //transposes a 3x3 (9 element) matrix
    __host__ __device__ void mat3f_transpose(double *mat3inout);

    //copies src to dest
    __host__ __device__ void mat3f_copy(double *mat3f_dest, const double *mat3f_src);

    //returns determinant of 3x3 matrix
    __host__ __device__ double mat3f_det(double *mat3in);

    //inverts a 3x3 (9 element) matrix
    __host__ __device__ void mat3f_inverse(double *mat3inout);

    __host__ __device__ cuvect3 mat3f_mult(double *mat3in, const cuvect3 &cvin);
    __host__ __device__ void mat3f_mult(double *matina, double *matinb, double *matout);

    __host__ __device__ void mat3f_hodgedual(const cuvect3 &vecin, double *matout);
    __host__ __device__ void mat3f_hodgedual(double *matin, cuvect3 &vecout);

    //returns direction cosine rotation matrix from axis and angle
    __host__ __device__ void mat3f_rot_from_axisangle(cuvect3 axis, double angle, double *matout);

    __host__ void test_cudavect_logic1();

}; //end namespace amscuda

#endif

