#include <amsculib2/amsculib2.hpp>

namespace amscuda
{
namespace cmp
{

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

__host__ __device__ cucomp64::~cucomp64()
{
    real = 0.0;
    imag = 0.0;
    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.0;
    return;
}

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

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

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

__host__ __device__ const cucomp64& cucomp64::operator=(const float& other) 
{
    this->real = other;
    this->imag = 0.0;
    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)
{
  cucomp64 ret;
  ret.real = real + z.real;
  ret.imag = imag + z.imag;
  return ret;  
}

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

__host__ __device__ cucomp64 cucomp64::operator*(const cucomp64& z)
{
  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)
{
  cucomp64 ret;
  float zm2 = z.real*z.real+z.imag*z.imag;
  
  if(zm2>0.0)
  {
      ret.real = (this->real*z.real+this->imag*z.imag)/zm2;
      ret.imag = (this->imag*z.real-this->real*z.imag)/zm2;
  }
  else
  {
      ret.real = (float) finf;
      ret.imag = (float) finf;
  }

  return ret;  
}

__host__ __device__ cucomp64 cucomp64::operator+(const float& z)
{
    cucomp64 ret;
    ret.real = this->real + z;
    ret.imag = this->imag;
    return ret;
}
__host__ __device__ cucomp64 cucomp64::operator-(const float& z)
{
    cucomp64 ret;
    ret.real = real-z;
    ret.imag = imag;
    return ret;
}
__host__ __device__ cucomp64 cucomp64::operator*(const float& z)
{
    cucomp64 ret;
    ret.real = real*z;
    ret.imag = imag*z;
    return ret;
}
__host__ __device__ cucomp64 cucomp64::operator/(const float& z)
{
    cucomp64 ret;
    if(z!=0.0f)
    {
        ret.real = real/z;
        ret.imag = imag/z;
    }
    else
    {
        ret.real = finf;
        ret.imag = finf;
    }

    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.0;
    ret = (float) amscuda::arg((double)real,(double)imag);
    return ret;
}

__host__ __device__ float cucomp64::mag() const
{
    float ret = 0.0;
    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(cucomp64 z)
{
    return z.arg();
}

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

__host__ __device__ cucomp64 dtocomp64(float _r, float _i)
{
    cucomp64 ret;
    ret.real = _r;
    ret.imag = _i;
    return ret;
}

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

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

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

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

    return ret;
}

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

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

    return ret;
}

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

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

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

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

    return ret;
}

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

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

    return ret;
}

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

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


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 complex=%ld\n",(long)(8*sizeof(z1)));
    printf("sizeof cucomp128=%ld\n",(long)(8*sizeof(a)));

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