#include <amsculib2/amsculib2.hpp>

namespace amscuda
{

    __host__ __device__ cuvect2::cuvect2()
    {
        x = 0.0; y = 0.0;
        return;
    }

    __host__ __device__ cuvect2::~cuvect2()
    {
        x = 0.0; y = 0.0;
        return;
    }

    __host__ __device__ cuvect2::cuvect2(double _x, double _y)
    {
        x = _x; y = _y;
        return;
    }

    __host__ __device__ double& cuvect2::operator[](const int I)
    {
        if(I==0) return x;
        if(I==1) return y;
        return x;
    }

    __host__ __device__ const double& cuvect2::operator[](const int I) const
    {
        if(I==0) return x;
        if(I==1) return y;
        return x;
    }

    __host__ __device__ cuvect2 cuvect2::operator+(cuvect2 lhs)
    {
        cuvect2 ret;
        ret.x = x + lhs.x;
        ret.y = y + lhs.y;
        return ret;
    }

    __host__ __device__ cuvect2 cuvect2::operator-(cuvect2 lhs)
    {
        cuvect2 ret;
        ret.x = x - lhs.x;
        ret.y = y - lhs.y;
        return ret;
    }
    __host__ __device__ cuvect2 cuvect2::operator*(double lhs)
    {
        cuvect2 ret;
        ret.x = x*lhs;
        ret.y = y*lhs;
        return ret;
    }

    __host__ __device__ cuvect2 cuvect2::operator/(double lhs)
    {
        cuvect2 ret;
        ret.x = x/lhs;
        ret.y = y/lhs;
        return ret;
    }

    __host__ __device__ double cuvect2_dot(cuvect2 a, cuvect2 b)
    {
        double ret = a.x*b.x+a.y*b.y;
        return ret;
    }

    __host__ __device__ double cuvect2_cross(cuvect2 a, cuvect2 b)
    {
        double ret = a.x*b.y-a.y*b.x;
        return ret;
    }

    __host__ __device__ double cuvect2_norm(cuvect2 a)
    {
        double ret = ::sqrt(a.x*a.x+a.y*a.y);
        return ret;
    }

    __host__ __device__ cuvect2 cuvect2_normalize(cuvect2 a)
    {
        cuvect2 ret;
        double m = cuvect2_norm(a);
        if(m>0.0)
        {
            ret.x = a.x/m; ret.y = a.y/m;
        }
        else
        {
            ret.x = 0.0; ret.y = 0.0;
        }
        return ret;
    }

    __host__ __device__ cuvect2 cuvect2_proj(cuvect2 a, cuvect2 b)
    {
        cuvect2 ret;
        cuvect2 bn = cuvect2_normalize(b);
        double m = cuvect2_dot(a,bn);
        ret = bn*m;
        return ret;
    }


__host__ __device__ cumat2::cumat2()
{
    int I;
    for(I=0;I<4;I++)
    {
        dat[I] = 0.0;
    }
    return;
}

__host__ __device__ cumat2::~cumat2()
{
    int I;
    for(I=0;I<4;I++)
    {
        dat[I] = 0.0;
    }
    return;
}

__host__ __device__ double& cumat2::operator[](const int I)
{
    return dat[I];
}

__host__ __device__ double& cumat2::operator()(const int I, const int J)
{
    return dat[I+2*J];
}

__host__ __device__ cumat2 cumat2::operator+(cumat2 lhs)
{
    int I;
    cumat2 ret;
    for(I=0;I<4;I++)
    {
        ret.dat[I] = this->dat[I] + lhs.dat[I];
    }
    return ret;
}

__host__ __device__ cumat2 cumat2::operator-(cumat2 lhs)
{
    int I;
    cumat2 ret;
    for(I=0;I<4;I++)
    {
        ret.dat[I] = this->dat[I] - lhs.dat[I];
    }
    return ret;
}

__host__ __device__ cumat2 cumat2::operator*(double lhs)
{
    cumat2 ret;
    int I;
    for(I=0;I<4;I++)
    {
        ret.dat[I] = this->dat[I]*lhs;
    }
    return ret;
}

__host__ __device__ cumat2 cumat2::operator/(double lhs)
{
    cumat2 ret;
    int I;
    for(I=0;I<4;I++)
    {
        ret.dat[I] = this->dat[I]/lhs;
    }
    return ret;
}

__host__ __device__ double& cumat2::at(const int I, const int J)
{
    return dat[I+2*J];
}
        

__host__ __device__ cuvect2 cumat2::operator*(cuvect2 lhs)
{
    cuvect2 ret = cuvect2(0.0,0.0);
    int I,J;
    for(I=0;I<2;I++)
    {
        for(J=0;J<2;J++)
        {
            ret[I] = ret[I] + this->at(I,J)*lhs[J];
        }
    }
    return ret;
}

__host__ __device__ cumat2 cumat2::operator*(cumat2 lhs)
{
    cumat2 ret;
    int I,J,K;

    for(I=0;I<2;I++)
    {
        for(J=0;J<2;J++)
        {
            ret(I,J) = 0.0;
            for(K=0;K<2;K++)
            {
                ret(I,J) = ret(I,J) + this->at(I,K)*lhs(K,J);
            }
        }
    }

    return ret;
}

__host__ __device__ double cumat2::det()
{
    double ret = 0.0;
    ret = ret + this->at(0,0)*this->at(1,1);
    ret = ret - this->at(1,0)*this->at(0,1);
    return ret;
}

__host__ __device__ cumat2 cumat2::transpose()
{
    cumat2 q;
    int I,J;
    for(I=0;I<2;I++)
    {
        for(J=0;J<2;J++)
        {
            q.at(I,J) = this->at(J,I);
        }
    }
    return q;
}

__host__ __device__ cumat2 cumat2::inverse()
{
    cumat2 q;
    double dt = q.det();
    if(dt!=0.0)
    {
        q(0,0) = this->at(1,1)/dt;
        q(0,1) = -this->at(0,1)/dt;
        q(1,0) = -this->at(1,0)/dt;
        q(1,1) = this->at(0,0)/dt;
    }
    else
    {
        q(0,0) = inf;
        q(0,1) = inf;
        q(1,0) = inf;
        q(1,1) = inf;
    }

    return q;
}

//2x2 matrix operations
//matrix order is assumed to be mat[I,J] = mat[I+3*J]

//transpose a 2x2 matrix in place
__host__ __device__ void mat2_transpose(double *mat2inout)
{
    double mat2_in[4];

    mat2_copy(mat2_in,mat2inout);
    mat2inout[0+0*2] = mat2_in[0+0*2];
    mat2inout[1+0*2] = mat2_in[0+1*2];
    mat2inout[0+1*2] = mat2_in[1+0*2];
    mat2inout[1+1*2] = mat2_in[1+1*2];
    
    return;
}

//copies src to dest
__host__ __device__ void mat2_copy(double *mat2_dest, const double *mat2_src)
{
    mat2_dest[0+0*2] = mat2_src[0+0*2];
    mat2_dest[1+0*2] = mat2_src[1+0*2];
    mat2_dest[0+1*2] = mat2_src[0+1*2];
    mat2_dest[1+1*2] = mat2_src[1+1*2];
    
    return;
}

//inverts mat?inout[4]
__host__ __device__ void mat2_inverse(double *mat2inout)
{
    double det = mat2inout[0+0*2]*mat2inout[1+1*2]-mat2inout[0+1*2]*mat2inout[1+0*2];
    double mat2in[4];

    mat2_copy(mat2in,mat2inout);
    mat2inout[0+0*2] = mat2inout[1+1*2]/det;
    mat2inout[1+0*2] = -mat2inout[1+0*2]/det;
    mat2inout[0+1*2] = -mat2inout[0+1*2]/det;
    mat2inout[1+1*2] = mat2inout[0+0*2]/det;
    
    return;
}

//rotatin matrix from angle
__host__ __device__ void mat2_rot_from_angle(double angle, double *mat2)
{
    mat2[0+0*2] = ::cos(angle);
    mat2[1+0*2] = ::sin(angle);
    mat2[0+1*2] = -::sin(angle);
    mat2[1+1*2] = ::cos(angle);
    return;
}

//multiplies c = a*b
__host__ __device__ void mat2_mult(double *mat2a, double *mat2b, double *mat2c)
{
    double mat2a_in[4];
    double mat2b_in[4];

    if(mat2a==NULL || mat2b==NULL || mat2c==NULL)
    {
        return;
    }

    mat2_copy(mat2a_in,mat2a);
    mat2_copy(mat2b_in,mat2b);
    
    mat2c[0+0*2] = mat2a_in[0+0*2]*mat2b_in[0+0*2] + mat2a_in[1+0*2]*mat2b_in[0+1*2];
    mat2c[1+0*2] = mat2a_in[0+0*2]*mat2b_in[1+0*2] + mat2a_in[1+0*2]*mat2b_in[1+1*2];
    mat2c[0+1*2] = mat2a_in[0+1*2]*mat2b_in[0+0*2] + mat2a_in[1+1*2]*mat2b_in[0+1*2];
    mat2c[1+1*2] = mat2a_in[0+1*2]*mat2b_in[1+0*2] + mat2a_in[1+1*2]*mat2b_in[1+1*2];

    return;
}

// ret = a*b
__host__ __device__ cuvect2 mat2_mult(double *mat2a, cuvect2 b)
{
    cuvect2 ret;
    ret.x = b.x*mat2a[0+0*2] + b.y*mat2a[1+0*2];
    ret.y = b.x*mat2a[0+1*2] + b.y*mat2a[1+1*2];
    return ret;
}

__host__ __device__ cuvect2 operator-(cuvect2 rhs)
{
    cuvect2 ret;
    ret[0] = -rhs[0];
    ret[1] = -rhs[1];
    return ret;
}

__host__ __device__ cumat2 operator-(cumat2 rhs)
{
    cumat2 ret;
    int I;
    for(I=0;I<4;I++) ret[I] = -rhs[I];
    return ret;
}

void test_cuvect2_1()
{


    return;
}

};