#include <amsculib3/amsculib3.hpp>

namespace amscuda
{
namespace cmp
{

__host__ __device__ cucomp64::cucomp64()
{
    real = 0.0f;
    imag = 0.0f;
    return;
}

__host__ __device__ cucomp64::~cucomp64()
{
    real = 0.0f;
    imag = 0.0f;
    return;
}

__host__ __device__ cucomp64::cucomp64(const cucomp64 &other)
{
    real = other.real;
    imag = other.imag;

    return;
}

__host__ __device__ cucomp64::cucomp64(const float &other)
{
    real = other;
    imag = 0.0f;
    return;
}

__host__ __device__ cucomp64::cucomp64(const float &_real, const float &_imag)
{
    real = _real;
    imag = _imag;
    return;
}

__host__ __device__ cucomp64& cucomp64::operator=(const cucomp64& other)
{
    real = other.real;
    imag = other.imag;
    return *this;
}


__host__ __device__ cucomp64& cucomp64::operator=(const float& other)
{
    real = other;
    imag = 0.0f;
    return *this;
}

__host__ __device__ float& cucomp64::operator[](int& ind)
{
    if(ind==0)
    {
        return this->real;
    }
    else
    {
        return this->imag;
    }
}

__host__ __device__ const float& cucomp64::operator[](const int& ind) const
{
    if(ind==0)
    {
        return this->real;
    }
    else
    {
        return this->imag;
    }
}

__host__ __device__ cucomp64 cucomp64::operator+(const cucomp64& z) const
{
  cucomp64 ret;
  ret.real = real + z.real;
  ret.imag = imag + z.imag;
  return ret;  
}

__host__ __device__ cucomp64 cucomp64::operator-(const cucomp64& z) const
{
  cucomp64 ret;
  ret.real = real - z.real;
  ret.imag = imag - z.imag;
  return ret;  
}

__host__ __device__ cucomp64 cucomp64::operator*(const cucomp64& z) const
{
  cucomp64 ret;
  ret.real = real*z.real - imag*z.imag;
  ret.imag = imag*z.real + real*z.imag;
  return ret;  
}

__host__ __device__ cucomp64 cucomp64::operator/(const cucomp64& z) const
{
  cucomp64 ret;
  float 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 = (float) finf;
//       ret.imag = (float) finf;
//   }

  return ret;  
}

__host__ __device__ cucomp64 operator+(const cucomp64& z1, const float& z2)
{
    cucomp64 ret;
    ret.real = z1.real+z2;
    ret.imag = z1.imag;
    return ret;
}

__host__ __device__ cucomp64 operator-(const cucomp64& z1, const float& z2)
{
    cucomp64 ret;
    ret.real = z1.real-z2;
    ret.imag = z1.imag;
    return ret;
}

__host__ __device__ cucomp64 operator*(const cucomp64& z1, const float& z2)
{
    cucomp64 ret;
    ret.real = z1.real*z2;
    ret.imag = z1.imag*z2;
    return ret;
}

__host__ __device__ cucomp64 operator/(const cucomp64& z1, const float& z2)
{
    cucomp64 ret;
    ret.real = z1.real/z2;
    ret.imag = z1.imag/z2;
    return ret;
}

__host__ __device__ cucomp64 operator+(const float& z1, const cucomp64& z2)
{
    cucomp64 ret;
    ret.real = z1 + z2.real;
    ret.imag = z2.imag;
    return ret;
}

__host__ __device__ cucomp64 operator-(const float& z1, const cucomp64& z2)
{
    cucomp64 ret;
    ret.real = z1 - z2.real;
    ret.imag = -z2.imag;
    return ret;
}

__host__ __device__ cucomp64 operator*(const float& z1, const cucomp64& z2)
{
    cucomp64 ret;
    ret.real = z1*z2.real;
    ret.imag = z1*z2.imag;
    return ret;
}

__host__ __device__ cucomp64 operator/(const float& z1, const cucomp64& z2)
{
    cucomp64 ret;
    float 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 cucomp64::operator==(const cucomp64& z) const
{
    bool ret = 0;
    if(z.real == real && z.imag == imag)
    {
        ret = 1;
    }
    return ret;
}

__host__ __device__ bool cucomp64::operator!=(const cucomp64& 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 cucomp64::operator>(const cucomp64& 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 cucomp64::operator<(const cucomp64& 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__ cucomp64 operator-(const cucomp64 &z)
{
	cucomp64 ret;
	ret.real = -z.real;
	ret.imag = -z.imag;
	return ret;
}

__host__ __device__ bool cucomp64::operator>=(const cucomp64& z) const
{
    bool ret = (*this==z || *this>z);
    return ret;
}

__host__ __device__ bool cucomp64::operator<=(const cucomp64& z) const
{
    bool ret = (*this==z || *this<z);
    return ret;
}

__host__ __device__ bool cucomp64::isnan() const
{
  bool ret = 0;
  if(::isnan(this->real) || ::isnan(this->imag))
  {
    ret = 1;
  }
  return ret;
}

__host__ __device__ bool cucomp64::isinf() const
{
  bool ret = 0;
  //calls math.h isinf()
  if(::isinf(this->real) || ::isinf(this->imag))
  {
    ret = 1;
  }
  return ret;
}

__host__ __device__ bool cucomp64::isreal() const
{
    bool ret = 1;
    if(imag!=0.0f)
    {
        ret = 0;
    }
    return ret;
}

__host__ __device__ bool cucomp64::isimag() const
{
    bool ret = 1;
    if(real!=0.0f)
    {
        ret = 0;
    }
    return ret;
}

__host__ __device__ bool cucomp64::iszero() const
{
    bool ret = 1;
    if(real!=0.0f || imag!=0.0f)
    {
        ret = 0;
    }
    return ret;
}

__host__ __device__ float cucomp64::arg() const
{
    float ret = 0.0f;
    ret = (float) amscuda::argf(real,imag);
    return ret;
}

__host__ __device__ float cucomp64::mag() const
{
    float ret = 0.0f;
    ret = ::sqrt(real*real+imag*imag);
    return ret;
}

__host__ __device__ float cucomp64::abs() const
{
    float ret = 0.0f;
    ret = ::sqrt(real*real+imag*imag);
    return ret;
}

__host__ __device__ cucomp64 cucomp64::conj() const
{
    cucomp64 ret;
    ret.real = real;
    ret.imag = -imag;
    return ret;
}

__host__ __device__ float arg(const cucomp64 &z)
{
    return z.arg();
}

__host__ __device__ float abs(const cucomp64 &z)
{
    return z.mag();
}

__host__ __device__ float real(const cucomp64 &z)
{
    return z.real;
}

__host__ __device__ float imag(const cucomp64 &z)
{
    return z.imag;
}

__host__ __device__ cucomp64 sin(const cucomp64 &z)
{
    cucomp64 ret;
    cucomp64 im1 = cucomp64(0.0f,1.0f);
    cucomp64 div = cucomp64(0.0f,2.0f);

    ret = (exp(im1*z)-exp(-im1*z))/div;

    return ret;
}

__host__ __device__ cucomp64 cos(const cucomp64 &z)
{
    cucomp64 ret;
    cucomp64 im1 = cucomp64(0.0f,1.0f);
    cucomp64 div = cucomp64(2.0f,0.0f);

    ret = (exp(im1*z)+exp(-im1*z))/div;

    return ret;
}

__host__ __device__ cucomp64 tan(const cucomp64 &z)
{
    return sin(z)/cos(z);
}

__host__ __device__ cucomp64 exp(const cucomp64 &z)
{
    cucomp64 ret;
    ret.real = ::exp(z.real)*::cos(z.imag);
    ret.imag = ::exp(z.real)*::sin(z.imag);
    return ret;
}

__host__ __device__ cucomp64 log(const cucomp64 &z)
{
    cucomp64 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__ cucomp64 conj(const cucomp64 &z)
{
    return z.conj();
}

__host__ __device__ cucomp64 cosh(const cucomp64 &z)
{
    cucomp64 ret;
    cucomp64 div = cucomp64(2.0f,0.0f);

    ret = (exp(z)+exp(-z))/div;

    return ret;
}

__host__ __device__ cucomp64 sinh(const cucomp64 &z)
{
    cucomp64 ret;
    cucomp64 div = cucomp64(2.0f,0.0f);

    ret = (exp(z)-exp(-z))/div;

    return ret;
}

__host__ __device__ cucomp64 tanh(const cucomp64 &z)
{
    return sinh(z)/cosh(z);
}

__host__ __device__ cucomp64 pow(const cucomp64 &z1, const cucomp64 &z2)
{
	cucomp64 ret;
	if(z1.mag()>0.0f)
		ret = amscuda::cmp::exp(amscuda::cmp::log(z1)*z2);
	else
		ret = cucomp64(0.0f,0.0f);
	return ret;
}

// //returns "complex sign" of complex number - 0, or a unit number with same argument
__host__ __device__ cucomp64 csgn(const cucomp64 &z)
{
    cucomp64 ret;
    float mag = z.mag();
    if(mag>0.0f)
    {
        ret = z;
        ret = ret/mag;
    }
    return ret;
}

//////////////////////////
//accumulation operators//
//////////////////////////

__host__ __device__ cucomp64& cucomp64::operator+=(const cucomp64& z)
{
    real += z.real;
    imag += z.imag;
    return (*this);
}

__host__ __device__ cucomp64& cucomp64::operator-=(const cucomp64& z)
{
    real -= z.real;
    imag -= z.imag;
    return (*this);
}

__host__ __device__ cucomp64& cucomp64::operator*=(const cucomp64& z)
{
    cucomp64 t = (*this);
    real = t.real*z.real - t.imag*z.imag;
    imag = t.real*z.imag + t.imag*z.real;
    return (*this);
}

__host__ __device__ cucomp64& cucomp64::operator/=(const cucomp64& z)
{
    amscuda::cmp::cucomp64 t = *this;
    *this = t/z;
    return (*this);
}

__host__ __device__ cucomp64& cucomp64::operator+=(const float& z)
{
    real += z;
    return (*this);
}

__host__ __device__ cucomp64& cucomp64::operator-=(const float& z)
{
    real -= z;
    return (*this);
}

__host__ __device__ cucomp64& cucomp64::operator*=(const float& z)
{
    real *= z;
    imag *= z;
    return (*this);
}

__host__ __device__ cucomp64& cucomp64::operator/=(const float& z)
{
    real /= z;
    imag /= z;
    return (*this);
}

void test_cucomp64_1()
{
    cucomp64 z1;
    cucomp64 a,b,c;
    double d1;
    float f1;

    printf("sizeof double=%ld\n",(long)(8*sizeof(f1)));
    printf("sizeof double=%ld\n",(long)(8*sizeof(d1)));
    printf("sizeof complex64=%ld\n",(long)(8*sizeof(z1)));

    a = cucomp64(1.0,1.0);
    b = cucomp64(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