#ifndef __NPY_SORT_COMMON_H__
#define __NPY_SORT_COMMON_H__

#include <stdlib.h>
#include <numpy/ndarraytypes.h>
#include <numpy/npy_math.h>
#include "dtypemeta.h"

#ifdef __cplusplus
extern "C" {
#endif

/*
 *****************************************************************************
 **                        SWAP MACROS                                      **
 *****************************************************************************
 */

#define BOOL_SWAP(a,b) {npy_bool tmp = (b); (b)=(a); (a) = tmp;}
#define BYTE_SWAP(a,b) {npy_byte tmp = (b); (b)=(a); (a) = tmp;}
#define UBYTE_SWAP(a,b) {npy_ubyte tmp = (b); (b)=(a); (a) = tmp;}
#define SHORT_SWAP(a,b) {npy_short tmp = (b); (b)=(a); (a) = tmp;}
#define USHORT_SWAP(a,b) {npy_ushort tmp = (b); (b)=(a); (a) = tmp;}
#define INT_SWAP(a,b) {npy_int tmp = (b); (b)=(a); (a) = tmp;}
#define UINT_SWAP(a,b) {npy_uint tmp = (b); (b)=(a); (a) = tmp;}
#define LONG_SWAP(a,b) {npy_long tmp = (b); (b)=(a); (a) = tmp;}
#define ULONG_SWAP(a,b) {npy_ulong tmp = (b); (b)=(a); (a) = tmp;}
#define LONGLONG_SWAP(a,b) {npy_longlong tmp = (b); (b)=(a); (a) = tmp;}
#define ULONGLONG_SWAP(a,b) {npy_ulonglong tmp = (b); (b)=(a); (a) = tmp;}
#define HALF_SWAP(a,b) {npy_half tmp = (b); (b)=(a); (a) = tmp;}
#define FLOAT_SWAP(a,b) {npy_float tmp = (b); (b)=(a); (a) = tmp;}
#define DOUBLE_SWAP(a,b) {npy_double tmp = (b); (b)=(a); (a) = tmp;}
#define LONGDOUBLE_SWAP(a,b) {npy_longdouble tmp = (b); (b)=(a); (a) = tmp;}
#define CFLOAT_SWAP(a,b) {npy_cfloat tmp = (b); (b)=(a); (a) = tmp;}
#define CDOUBLE_SWAP(a,b) {npy_cdouble tmp = (b); (b)=(a); (a) = tmp;}
#define CLONGDOUBLE_SWAP(a,b) {npy_clongdouble tmp = (b); (b)=(a); (a) = tmp;}
#define DATETIME_SWAP(a,b) {npy_datetime tmp = (b); (b)=(a); (a) = tmp;}
#define TIMEDELTA_SWAP(a,b) {npy_timedelta tmp = (b); (b)=(a); (a) = tmp;}

/* Need this for the argsort functions */
#define INTP_SWAP(a,b) {npy_intp tmp = (b); (b)=(a); (a) = tmp;}

/*
 *****************************************************************************
 **                        COMPARISON FUNCTIONS                             **
 *****************************************************************************
 */

static inline int
BOOL_LT(npy_bool a, npy_bool b)
{
    return a < b;
}


static inline int
BYTE_LT(npy_byte a, npy_byte b)
{
    return a < b;
}


static inline int
UBYTE_LT(npy_ubyte a, npy_ubyte b)
{
    return a < b;
}


static inline int
SHORT_LT(npy_short a, npy_short b)
{
    return a < b;
}


static inline int
USHORT_LT(npy_ushort a, npy_ushort b)
{
    return a < b;
}


static inline int
INT_LT(npy_int a, npy_int b)
{
    return a < b;
}


static inline int
UINT_LT(npy_uint a, npy_uint b)
{
    return a < b;
}


static inline int
LONG_LT(npy_long a, npy_long b)
{
    return a < b;
}


static inline int
ULONG_LT(npy_ulong a, npy_ulong b)
{
    return a < b;
}


static inline int
LONGLONG_LT(npy_longlong a, npy_longlong b)
{
    return a < b;
}


static inline int
ULONGLONG_LT(npy_ulonglong a, npy_ulonglong b)
{
    return a < b;
}


static inline int
FLOAT_LT(npy_float a, npy_float b)
{
    return a < b || (b != b && a == a);
}


static inline int
DOUBLE_LT(npy_double a, npy_double b)
{
    return a < b || (b != b && a == a);
}


static inline int
LONGDOUBLE_LT(npy_longdouble a, npy_longdouble b)
{
    return a < b || (b != b && a == a);
}


static inline int
_npy_half_isnan(npy_half h)
{
    return ((h&0x7c00u) == 0x7c00u) && ((h&0x03ffu) != 0x0000u);
}


static inline int
_npy_half_lt_nonan(npy_half h1, npy_half h2)
{
    if (h1&0x8000u) {
        if (h2&0x8000u) {
            return (h1&0x7fffu) > (h2&0x7fffu);
        }
        else {
            /* Signed zeros are equal, have to check for it */
            return (h1 != 0x8000u) || (h2 != 0x0000u);
        }
    }
    else {
        if (h2&0x8000u) {
            return 0;
        }
        else {
            return (h1&0x7fffu) < (h2&0x7fffu);
        }
    }
}


static inline int
HALF_LT(npy_half a, npy_half b)
{
    int ret;

    if (_npy_half_isnan(b)) {
        ret = !_npy_half_isnan(a);
    }
    else {
        ret = !_npy_half_isnan(a) && _npy_half_lt_nonan(a, b);
    }

    return ret;
}

/*
 * For inline functions SUN recommends not using a return in the then part
 * of an if statement. It's a SUN compiler thing, so assign the return value
 * to a variable instead.
 */
static inline int
CFLOAT_LT(npy_cfloat a, npy_cfloat b)
{
    int ret;

    if (npy_crealf(a) < npy_crealf(b)) {
        ret = npy_cimagf(a) == npy_cimagf(a) || npy_cimagf(b) != npy_cimagf(b);
    }
    else if (npy_crealf(a) > npy_crealf(b)) {
        ret = npy_cimagf(b) != npy_cimagf(b) && npy_cimagf(a) == npy_cimagf(a);
    }
    else if (npy_crealf(a) == npy_crealf(b) || (npy_crealf(a) != npy_crealf(a) && npy_crealf(b) != npy_crealf(b))) {
        ret =  npy_cimagf(a) < npy_cimagf(b) || (npy_cimagf(b) != npy_cimagf(b) && npy_cimagf(a) == npy_cimagf(a));
    }
    else {
        ret = npy_crealf(b) != npy_crealf(b);
    }

    return ret;
}


static inline int
CDOUBLE_LT(npy_cdouble a, npy_cdouble b)
{
    int ret;

    if (npy_creal(a) < npy_creal(b)) {
        ret = npy_cimag(a) == npy_cimag(a) || npy_cimag(b) != npy_cimag(b);
    }
    else if (npy_creal(a) > npy_creal(b)) {
        ret = npy_cimag(b) != npy_cimag(b) && npy_cimag(a) == npy_cimag(a);
    }
    else if (npy_creal(a) == npy_creal(b) || (npy_creal(a) != npy_creal(a) && npy_creal(b) != npy_creal(b))) {
        ret =  npy_cimag(a) < npy_cimag(b) || (npy_cimag(b) != npy_cimag(b) && npy_cimag(a) == npy_cimag(a));
    }
    else {
        ret = npy_creal(b) != npy_creal(b);
    }

    return ret;
}


static inline int
CLONGDOUBLE_LT(npy_clongdouble a, npy_clongdouble b)
{
    int ret;

    if (npy_creall(a) < npy_creall(b)) {
        ret = npy_cimagl(a) == npy_cimagl(a) || npy_cimagl(b) != npy_cimagl(b);
    }
    else if (npy_creall(a) > npy_creall(b)) {
        ret = npy_cimagl(b) != npy_cimagl(b) && npy_cimagl(a) == npy_cimagl(a);
    }
    else if (npy_creall(a) == npy_creall(b) || (npy_creall(a) != npy_creall(a) && npy_creall(b) != npy_creall(b))) {
        ret =  npy_cimagl(a) < npy_cimagl(b) || (npy_cimagl(b) != npy_cimagl(b) && npy_cimagl(a) == npy_cimagl(a));
    }
    else {
        ret = npy_creall(b) != npy_creall(b);
    }

    return ret;
}


static inline void
STRING_COPY(char *s1, char const*s2, size_t len)
{
    memcpy(s1, s2, len);
}


static inline void
STRING_SWAP(char *s1, char *s2, size_t len)
{
    while(len--) {
        const char t = *s1;
        *s1++ = *s2;
        *s2++ = t;
    }
}


static inline int
STRING_LT(const char *s1, const char *s2, size_t len)
{
    const unsigned char *c1 = (const unsigned char *)s1;
    const unsigned char *c2 = (const unsigned char *)s2;
    size_t i;
    int ret = 0;

    for (i = 0; i < len; ++i) {
        if (c1[i] != c2[i]) {
            ret = c1[i] < c2[i];
            break;
        }
    }
    return ret;
}


static inline void
UNICODE_COPY(npy_ucs4 *s1, npy_ucs4 const *s2, size_t len)
{
    while(len--) {
        *s1++ = *s2++;
    }
}


static inline void
UNICODE_SWAP(npy_ucs4 *s1, npy_ucs4 *s2, size_t len)
{
    while(len--) {
        const npy_ucs4 t = *s1;
        *s1++ = *s2;
        *s2++ = t;
    }
}


static inline int
UNICODE_LT(const npy_ucs4 *s1, const npy_ucs4 *s2, size_t len)
{
    size_t i;
    int ret = 0;

    for (i = 0; i < len; ++i) {
        if (s1[i] != s2[i]) {
            ret = s1[i] < s2[i];
            break;
        }
    }
    return ret;
}


static inline int
DATETIME_LT(npy_datetime a, npy_datetime b)
{
    if (a == NPY_DATETIME_NAT) {
        return 0;
    }

    if (b == NPY_DATETIME_NAT) {
        return 1;
    }

    return a < b;
}


static inline int
TIMEDELTA_LT(npy_timedelta a, npy_timedelta b)
{
    if (a == NPY_DATETIME_NAT) {
        return 0;
    }

    if (b == NPY_DATETIME_NAT) {
        return 1;
    }

    return a < b;
}


static inline void
GENERIC_COPY(char *a, char *b, size_t len)
{
    memcpy(a, b, len);
}


static inline void
GENERIC_SWAP(char *a, char *b, size_t len)
{
    while(len--) {
        const char t = *a;
        *a++ = *b;
        *b++ = t;
    }
}

#ifdef __cplusplus
}
#endif

#endif
