#include <amsculib3/amsculib3.hpp>

namespace amscuda
{
namespace cmp
{

    __host__ __device__ cucomp128::cucomp128()
    {
        real = 0.0;
        imag = 0.0;
        return;
    }
    
    __host__ __device__ cucomp128::~cucomp128()
    {
        real = 0.0;
        imag = 0.0;
        return;
    }
    
    __host__ __device__ cucomp128::cucomp128(const cucomp128 &other)
    {
        real = other.real;
        imag = other.imag;
    
        return;
    }
    
    __host__ __device__ cucomp128::cucomp128(const double &other)
    {
        real = other;
        imag = 0.0;
        return;
    }
    
    __host__ __device__ cucomp128::cucomp128(const double &_real, const double &_imag)
    {
        real = _real;
        imag = _imag;
        return;
    }
    
    __host__ __device__ cucomp128& cucomp128::operator=(const cucomp128& other)
    {
        real = other.real;
        imag = other.imag;
        return *this;
    }
    
    
    __host__ __device__ cucomp128& cucomp128::operator=(const double& other)
    {
        real = other;
        imag = 0.0;
        return *this;
    }
    
    __host__ __device__ double& cucomp128::operator[](int& ind)
    {
        if(ind==0)
        {
            return this->real;
        }
        else
        {
            return this->imag;
        }
    }
    
    __host__ __device__ const double& cucomp128::operator[](const int& ind) const
    {
        if(ind==0)
        {
            return this->real;
        }
        else
        {
            return this->imag;
        }
    }
    
    __host__ __device__ cucomp128 cucomp128::operator+(const cucomp128& z) const
    {
      cucomp128 ret;
      ret.real = real + z.real;
      ret.imag = imag + z.imag;
      return ret;  
    }
    
    __host__ __device__ cucomp128 cucomp128::operator-(const cucomp128& z) const
    {
      cucomp128 ret;
      ret.real = real - z.real;
      ret.imag = imag - z.imag;
      return ret;  
    }
    
    __host__ __device__ cucomp128 cucomp128::operator*(const cucomp128& z) const
    {
      cucomp128 ret;
      ret.real = real*z.real - imag*z.imag;
      ret.imag = imag*z.real + real*z.imag;
      return ret;  
    }
    
    __host__ __device__ cucomp128 cucomp128::operator/(const cucomp128& z) const
    {
      cucomp128 ret;
      double zm2 = z.real*z.real+z.imag*z.imag;
      
    //   if(zm2>0.0)
    //   {
          ret.real = (real*z.real+imag*z.imag)/zm2;
          ret.imag = (imag*z.real-real*z.imag)/zm2;
    //   }
    //   else
    //   {
    //       ret.real = (double) finf;
    //       ret.imag = (double) finf;
    //   }
    
      return ret;  
    }
    
    __host__ __device__ cucomp128 operator+(const cucomp128& z1, const double& z2)
    {
        cucomp128 ret;
        ret.real = z1.real+z2;
        ret.imag = z1.imag;
        return ret;
    }
    
    __host__ __device__ cucomp128 operator-(const cucomp128& z1, const double& z2)
    {
        cucomp128 ret;
        ret.real = z1.real-z2;
        ret.imag = z1.imag;
        return ret;
    }
    
    __host__ __device__ cucomp128 operator*(const cucomp128& z1, const double& z2)
    {
        cucomp128 ret;
        ret.real = z1.real*z2;
        ret.imag = z1.imag*z2;
        return ret;
    }
    
    __host__ __device__ cucomp128 operator/(const cucomp128& z1, const double& z2)
    {
        cucomp128 ret;
        ret.real = z1.real/z2;
        ret.imag = z1.imag/z2;
        return ret;
    }
    
    __host__ __device__ cucomp128 operator+(const double& z1, const cucomp128& z2)
    {
        cucomp128 ret;
        ret.real = z1 + z2.real;
        ret.imag = z2.imag;
        return ret;
    }
    
    __host__ __device__ cucomp128 operator-(const double& z1, const cucomp128& z2)
    {
        cucomp128 ret;
        ret.real = z1 - z2.real;
        ret.imag = -z2.imag;
        return ret;
    }
    
    __host__ __device__ cucomp128 operator*(const double& z1, const cucomp128& z2)
    {
        cucomp128 ret;
        ret.real = z1*z2.real;
        ret.imag = z1*z2.imag;
        return ret;
    }
    
    __host__ __device__ cucomp128 operator/(const double& z1, const cucomp128& z2)
    {
        cucomp128 ret;
        double zmg = z2.real*z2.real + z2.imag*z2.imag;
        ret.real = z1*(z2.real)/zmg;
        ret.imag = -z1*(z2.imag)/zmg;
        return ret;
    }
    
    __host__ __device__ bool cucomp128::operator==(const cucomp128& z) const
    {
        bool ret = 0;
        if(z.real == real && z.imag == imag)
        {
            ret = 1;
        }
        return ret;
    }
    
    __host__ __device__ bool cucomp128::operator!=(const cucomp128& z) const
    {
        return !(*this==z);
    }
    
    //sort first by real value, then by imaginary value
    //this is done so that an ordering exists, as long as two values aren't equal
    __host__ __device__ bool cucomp128::operator>(const cucomp128& z) const
    {
        bool ret = 0;
        if(this->real>z.real)
        {
            ret = 1;
        }
        else if(this->real==z.real)
        {
            if(this->imag>z.imag)
            {
                ret = 1;
            }
            else
            {
                ret = 0;
            }
        }
        else
        {
            ret = 0;
        }
        return ret;
    }
    
    __host__ __device__ bool cucomp128::operator<(const cucomp128& z) const
    {
        bool ret = 0;
        if(this->real<z.real)
        {
            ret = 1;
        }
        else if(this->real==z.real)
        {
            if(this->imag<z.imag)
            {
                ret = 1;
            }
            else
            {
                ret = 0;
            }
        }
        else
        {
            ret = 0;
        }
        return ret;
    }
    
    __host__ __device__ cucomp128 operator-(const cucomp128 &z)
    {
        cucomp128 ret;
        ret.real = -z.real;
        ret.imag = -z.imag;
        return ret;
    }
    
    __host__ __device__ bool cucomp128::operator>=(const cucomp128& z) const
    {
        bool ret = (*this==z || *this>z);
        return ret;
    }
    
    __host__ __device__ bool cucomp128::operator<=(const cucomp128& z) const
    {
        bool ret = (*this==z || *this<z);
        return ret;
    }
    
    __host__ __device__ bool cucomp128::isnan() const
    {
      bool ret = 0;
      if(::isnan(this->real) || ::isnan(this->imag))
      {
        ret = 1;
      }
      return ret;
    }
    
    __host__ __device__ bool cucomp128::isinf() const
    {
      bool ret = 0;
      //calls math.h isinf()
      if(::isinf(this->real) || ::isinf(this->imag))
      {
        ret = 1;
      }
      return ret;
    }
    
    __host__ __device__ bool cucomp128::isreal() const
    {
        bool ret = 1;
        if(imag!=0.0)
        {
            ret = 0;
        }
        return ret;
    }
    
    __host__ __device__ bool cucomp128::isimag() const
    {
        bool ret = 1;
        if(real!=0.0)
        {
            ret = 0;
        }
        return ret;
    }
    
    __host__ __device__ bool cucomp128::iszero() const
    {
        bool ret = 1;
        if(real!=0.0 || imag!=0.0)
        {
            ret = 0;
        }
        return ret;
    }
    
    __host__ __device__ double cucomp128::arg() const
    {
        double ret = 0.0;
        ret = (double) amscuda::arg((double)real,(double)imag);
        return ret;
    }
    
    __host__ __device__ double cucomp128::mag() const
    {
        double ret = 0.0;
        ret = ::sqrt(real*real+imag*imag);
        return ret;
    }
    
    __host__ __device__ double cucomp128::abs() const
    {
        double ret = 0.0;
        ret = ::sqrt(real*real+imag*imag);
        return ret;
    }
    
    __host__ __device__ cucomp128 cucomp128::conj() const
    {
        cucomp128 ret;
        ret.real = real;
        ret.imag = -imag;
        return ret;
    }
    
    __host__ __device__ double arg(const cucomp128 &z)
    {
        return z.arg();
    }
    
    __host__ __device__ double abs(const cucomp128 &z)
    {
        return z.mag();
    }
    
    __host__ __device__ double real(const cucomp128 &z)
    {
        return z.real;
    }
    
    __host__ __device__ double imag(const cucomp128 &z)
    {
        return z.imag;
    }
    
    __host__ __device__ cucomp128 sin(const cucomp128 &z)
    {
        cucomp128 ret;
        cucomp128 im1 = cucomp128(0.0,1.0f);
        cucomp128 div = cucomp128(0.0,2.0f);
    
        ret = (exp(im1*z)-exp(-im1*z))/div;
    
        return ret;
    }
    
    __host__ __device__ cucomp128 cos(const cucomp128 &z)
    {
        cucomp128 ret;
        cucomp128 im1 = cucomp128(0.0,1.0f);
        cucomp128 div = cucomp128(2.0f,0.0);
    
        ret = (exp(im1*z)+exp(-im1*z))/div;
    
        return ret;
    }
    
    __host__ __device__ cucomp128 tan(const cucomp128 &z)
    {
        return sin(z)/cos(z);
    }
    
    __host__ __device__ cucomp128 exp(const cucomp128 &z)
    {
        cucomp128 ret;
        ret.real = ::exp(z.real)*::cos(z.imag);
        ret.imag = ::exp(z.real)*::sin(z.imag);
        return ret;
    }
    
    __host__ __device__ cucomp128 log(const cucomp128 &z)
    {
        cucomp128 ret;
        ret.real = ::log(::sqrt(z.real*z.real+z.imag*z.imag));
        ret.imag = amscuda::arg(z.real,z.imag);
        return ret;
    }
    
    __host__ __device__ cucomp128 conj(const cucomp128 &z)
    {
        return z.conj();
    }
    
    __host__ __device__ cucomp128 cosh(const cucomp128 &z)
    {
        cucomp128 ret;
        cucomp128 div = cucomp128(2.0f,0.0);
    
        ret = (exp(z)+exp(-z))/div;
    
        return ret;
    }
    
    __host__ __device__ cucomp128 sinh(const cucomp128 &z)
    {
        cucomp128 ret;
        cucomp128 div = cucomp128(2.0f,0.0);
    
        ret = (exp(z)-exp(-z))/div;
    
        return ret;
    }
    
    __host__ __device__ cucomp128 tanh(const cucomp128 &z)
    {
        return sinh(z)/cosh(z);
    }
    
    __host__ __device__ cucomp128 pow(const cucomp128 &z1, const cucomp128 &z2)
    {
        cucomp128 ret;
        if(z1.mag()>0.0)
            ret = amscuda::cmp::exp(amscuda::cmp::log(z1)*z2);
        else
            ret = cucomp128(0.0,0.0);
        return ret;
    }
    
    // //returns "complex sign" of complex number - 0, or a unit number with same argument
    __host__ __device__ cucomp128 csgn(const cucomp128 &z)
    {
        cucomp128 ret;
        double mag = z.mag();
        if(mag>0.0)
        {
            ret = z;
            ret = ret/mag;
        }
        return ret;
    }
    
    //////////////////////////
    //accumulation operators//
    //////////////////////////
    
    __host__ __device__ cucomp128& cucomp128::operator+=(const cucomp128& z)
    {
        real += z.real;
        imag += z.imag;
        return (*this);
    }
    
    __host__ __device__ cucomp128& cucomp128::operator-=(const cucomp128& z)
    {
        real -= z.real;
        imag -= z.imag;
        return (*this);
    }
    
    __host__ __device__ cucomp128& cucomp128::operator*=(const cucomp128& z)
    {
        cucomp128 t = (*this);
        real = t.real*z.real - t.imag*z.imag;
        imag = t.real*z.imag + t.imag*z.real;
        return (*this);
    }
    
    __host__ __device__ cucomp128& cucomp128::operator/=(const cucomp128& z)
    {
        amscuda::cmp::cucomp128 t = *this;
        *this = t/z;
        return (*this);
    }
    
    __host__ __device__ cucomp128& cucomp128::operator+=(const double& z)
    {
        real += z;
        return (*this);
    }
    
    __host__ __device__ cucomp128& cucomp128::operator-=(const double& z)
    {
        real -= z;
        return (*this);
    }
    
    __host__ __device__ cucomp128& cucomp128::operator*=(const double& z)
    {
        real *= z;
        imag *= z;
        return (*this);
    }
    
    __host__ __device__ cucomp128& cucomp128::operator/=(const double& z)
    {
        real /= z;
        imag /= z;
        return (*this);
    }


void test_cucomp128_1()
{
    cucomp128 z1;
    cucomp128 a,b,c;
    double d1;
    float f1;

    printf("sizeof float=%ld\n",(long)(8*sizeof(f1)));
    printf("sizeof double=%ld\n",(long)(8*sizeof(d1)));
    printf("sizeof complex=%ld\n",(long)(8*sizeof(z1)));
    printf("sizeof cucomp128=%ld\n",(long)(8*sizeof(a)));

    a = cucomp128(1.0,1.0);
    b = cucomp128(1.0,-1.0);

    printf("a=%1.4f + %1.4fi\n",a[0],a[1]);
    printf("b=%1.4f + %1.4fi\n",b[0],b[1]);
    c = a+b;
    printf("c=a+b: c=%1.4f + %1.4fi\n",c[0],c[1]);
    c = a-b;
    printf("c=a-b: c=%1.4f + %1.4fi\n",c[0],c[1]);
    c = a*b;
    printf("c=a*b: c=%1.4f + %1.4fi\n",c[0],c[1]);
    c = a/b;
    printf("c=a/b: c=%1.4f + %1.4fi\n",c[0],c[1]);
    f1 = abs(a);
    printf("abs(a)=%1.4f\n",f1);
    f1 = arg(a);
    printf("abs(a)=%1.4f pi\n",f1/pi);


}

}; //end namespace cmp
}; //end namespace amscuda