#include <amsculib2/amsculib2.hpp>

namespace amscuda
{

    __host__ __device__ double dabs(const double &x)
    {
        double ret = x;
        if(x<0.0)
        {
            ret = -x;
        }
        return ret;
    }

    __host__ __device__ float fabs(const float &x)
    {
        float ret = x;
        if(x<0.0f)
        {
            ret = -x;
        }
        return ret;
    }

    __host__ __device__ double mod(const double &x, const double &md)
    {
        double ret = 0.0;
        ret = fmod(x,md);
        if(ret<0.0)
        {
            ret = ret + md;
        }
        return ret;
    }

    __host__ __device__ float mod(const float &x, const float &md)
    {
        float ret;
        ret = fmodf(x,md);
        if(ret<0.0f)
        {
            ret = ret + md;
        }
        return ret;
    }

    __host__ __device__ int mod(const int &x, const int &n)
    {
        int ret;
        ret = x % n;
        if(ret<0)
        {
            ret = ret + n;
        }
        return ret;
    }

    __host__ __device__ long mod(const long &x, const long &n)
    {
        int ret;
        ret = x % n;
        if(ret<0)
        {
            ret = ret + n;
        }
        return ret;
    }

    __host__ __device__ int truediv(const int &x, const int &y)
    {
        int z = 0;
        if(x>=0 && y>0)
        {
            z = x/y;
        }
        else if(x<0 && y>0)
        {
            z = -((-x)/y) - 1;
        }
        else if(x>=0 && y<0)
        {
            z = -(x/(-y)) - 1;
        }
        else if(x<0 && y<0)
        {
            z = ((-x)/(-y));
        }

        return z;
    }

    __host__ __device__ long truediv(const long &x, const long &y)
    {
        int z = 0;
        if(x>=0 && y>0)
        {
            z = x/y;
        }
        else if(x<0 && y>0)
        {
            z = -((-x)/y) - 1;
        }
        else if(x>=0 && y<0)
        {
            z = -(x/(-y)) - 1;
        }
        else if(x<0 && y<0)
        {
            z = ((-x)/(-y));
        }

        return z;
    }


    template<> __host__ __device__ double min(const double &a, const double &b)
    {
        if(isnan(a))
        {
            return b;
        }
        else if(isnan(b))
        {
            return a;
        }
        else if(a>b)
        {
            return b;
        }
        else
        {
            return a;
        }
    }

    template<> __host__ __device__ float min(const float &a, const float &b)
    {
        if(isnan(a))
        {
            return b;
        }
        else if(isnan(b))
        {
            return a;
        }
        else if(a>b)
        {
            return b;
        }
        else
        {
            return a;
        }
    }

    template<> __host__ __device__ double max(const double &a, const double &b)
    {
        if(isnan(a))
        {
            return b;
        }
        else if(isnan(b))
        {
            return a;
        }
        else if(a>b)
        {
            return a;
        }
        else
        {
            return b;
        }
    }

    template<> __host__ __device__ float max(const float &a, const float &b)
    {
        if(isnan(a))
        {
            return b;
        }
        else if(isnan(b))
        {
            return a;
        }
        else if(a>b)
        {
            return a;
        }
        else
        {
            return b;
        }
    }

    __device__ __host__ double arg(const double &x, const double &y)
    {
        double ret = 0.0;


        double z = ::sqrt(x*x+y*y);

        if(z>0.0)
        {
            ret = ::atan2(x,y);
            // if(y<=x && y>=-x)
            // {
            //     ret = asin(y/z);
            // }
            // else if(y>=x && y>=-x)
            // {
            //     ret = acos(x/z);
            // }
            // else if(y>=x && y<=-x)
            // {
            //     ret = pi-asin(y/z);
            // }
            // else
            // {
            //     ret = 2.0*pi-acos(x/z);
            // }
        }

        if(ret<0.0)
        {
            ret = 2.0*pi+ret;
        }

        return ret;
    }

    __device__ __host__ double argf(const float &x, const float &y)
    {
        double ret = 0.0f;
        double z = ::sqrtf(x*x+y*y);
        if(z>0.0f)
        {
            ret = ::atan2(x,y);
            // if(y<=x && y>=-x)
            // {
            //     ret = asin(y/z);
            // }
            // else if(y>=x && y>=-x)
            // {
            //     ret = acos(x/z);
            // }
            // else if(y>=x && y<=-x)
            // {
            //     ret = pi-asin(y/z);
            // }
            // else
            // {
            //     ret = 2.0*pi-acos(x/z);
            // }
        }

        if(ret<0.0)
        {
            ret = 2.0*pi+ret;
        }

        return ret;
    }

    __device__ __host__ void get_azel(const double &x, const double &y, const double &z, double *az, double *el)
    {
        //int ret = -2; //should never see this return
        double n, rp;
        n = ::sqrt(x*x+y*y+z*z);
        if(n>0.0)
        {
            rp = ::sqrt(x*x+y*y);
            if(rp>0.0)
            {
                //ret = 1; //nonzero vector - should work
                *az = arg(x,y);
                *el = ::atan(z/rp);
            }
            else
            {
                //ret = 0; //straight up or straight down
                if(z>0.0)
                {
                    if(az!=NULL) *az = 0.0;
                    if(el!=NULL) *el = pi/2.0;
                }
                else
                {
                    if(az!=NULL) *az = 0.0;
                    if(el!=NULL) *el = -pi/2.0;
                }
            }
        }
        else
        {
            if(az!=NULL) *az = 0.0;
            if(el!=NULL) *el = 0.0;
            //ret = -1; //zero vector - no real azimuth/elevation
        }

        return;
    }

    __device__ __host__ void get_azel(const double &x, const double &y, const double &z, double &az_out, double &el_out)
    {
        //int ret = -2; //should never see this return
        double n, rp;
        n = ::sqrt(x*x+y*y+z*z);
        if(n>0.0)
        {
            rp = ::sqrt(x*x+y*y);
            if(rp>0.0)
            {
                //ret = 1; //nonzero vector - should work
                az_out = arg(x,y);
                el_out = ::atan(z/rp);
            }
            else
            {
                //ret = 0; //straight up or straight down
                if(z>0.0)
                {
                    az_out = 0.0;
                    el_out = pi/2.0;
                }
                else
                {
                    az_out = 0.0;
                    el_out = -pi/2.0;
                }
            }
        }
        else
        {
            az_out = 0.0;
            el_out = 0.0;
            //ret = -1; //zero vector - no real azimuth/elevation
        }

        return;
    }

    __device__ __host__ void get_azelf(const float &x, const float &y, const float &z, float &az_out, float &el_out)
    {
        //int ret = -2; //should never see this return
        float n, rp;
        n = ::sqrtf(x*x+y*y+z*z);
        if(n>0.0f)
        {
            rp = ::sqrtf(x*x+y*y);
            if(rp>0.0f)
            {
                //ret = 1; //nonzero vector - should work
                az_out = argf(x,y);
                el_out = ::atanf(z/rp);
            }
            else
            {
                //ret = 0; //straight up or straight down
                if(z>0.0f)
                {
                    az_out = 0.0f;
                    el_out = pif/2.0f;
                }
                else
                {
                    az_out = 0.0f;
                    el_out = -pif/2.0f;
                }
            }
        }
        else
        {
            az_out = 0.0f;
            el_out = 0.0f;
            //ret = -1; //zero vector - no real azimuth/elevation
        }

        return;
    }


    void test_amscumath1()
    {
        printf("pi = %1.16f\n",amscuda::pi);
    }

};