//
// Copyright (c) 2017 The Khronos Group Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "conversions.h"
#include <cinttypes>
#include <limits.h>
#include <time.h>
#include <assert.h>
#include "mt19937.h"
#include "compat.h"

#if defined(__SSE__) || defined(_MSC_VER)
#include <xmmintrin.h>
#endif
#if defined(__SSE2__) || defined(_MSC_VER)
#include <emmintrin.h>
#endif

void print_type_to_string(ExplicitType type, void *data, char *string)
{
    switch (type)
    {
        case kBool:
            if (*(char *)data)
                sprintf(string, "true");
            else
                sprintf(string, "false");
            return;
        case kChar: sprintf(string, "%d", (int)*((cl_char *)data)); return;
        case kUChar:
        case kUnsignedChar:
            sprintf(string, "%u", (int)*((cl_uchar *)data));
            return;
        case kShort: sprintf(string, "%d", (int)*((cl_short *)data)); return;
        case kUShort:
        case kUnsignedShort:
            sprintf(string, "%u", (int)*((cl_ushort *)data));
            return;
        case kInt: sprintf(string, "%d", *((cl_int *)data)); return;
        case kUInt:
        case kUnsignedInt: sprintf(string, "%u", *((cl_uint *)data)); return;
        case kLong: sprintf(string, "%" PRId64 "", *((cl_long *)data)); return;
        case kULong:
        case kUnsignedLong:
            sprintf(string, "%" PRIu64 "", *((cl_ulong *)data));
            return;
        case kFloat: sprintf(string, "%f", *((cl_float *)data)); return;
        case kHalf: sprintf(string, "half"); return;
        case kDouble: sprintf(string, "%g", *((cl_double *)data)); return;
        default: sprintf(string, "INVALID"); return;
    }
}

size_t get_explicit_type_size(ExplicitType type)
{
    /* Quick method to avoid branching: make sure the following array matches
     * the Enum order */
    static size_t sExplicitTypeSizes[] = {
        sizeof(cl_bool),   sizeof(cl_char),  sizeof(cl_uchar),
        sizeof(cl_uchar),  sizeof(cl_short), sizeof(cl_ushort),
        sizeof(cl_ushort), sizeof(cl_int),   sizeof(cl_uint),
        sizeof(cl_uint),   sizeof(cl_long),  sizeof(cl_ulong),
        sizeof(cl_ulong),  sizeof(cl_float), sizeof(cl_half),
        sizeof(cl_double)
    };

    return sExplicitTypeSizes[type];
}

const char *get_explicit_type_name(ExplicitType type)
{
    /* Quick method to avoid branching: make sure the following array matches
     * the Enum order */
    static const char *sExplicitTypeNames[] = {
        "bool",           "char",  "uchar", "unsigned char", "short", "ushort",
        "unsigned short", "int",   "uint",  "unsigned int",  "long",  "ulong",
        "unsigned long",  "float", "half",  "double"
    };

    return sExplicitTypeNames[type];
}

static long lrintf_clamped(float f);
static long lrintf_clamped(float f)
{
    static const float magic[2] = { MAKE_HEX_FLOAT(0x1.0p23f, 0x1, 23),
                                    -MAKE_HEX_FLOAT(0x1.0p23f, 0x1, 23) };

    if (f >= -(float)LONG_MIN) return LONG_MAX;

    if (f <= (float)LONG_MIN) return LONG_MIN;

    // Round fractional values to integer in round towards nearest mode
    if (fabsf(f) < MAKE_HEX_FLOAT(0x1.0p23f, 0x1, 23))
    {
        volatile float x = f;
        float magicVal = magic[f < 0];

#if defined(__SSE__) || defined(_WIN32)
        // Defeat x87 based arithmetic, which cant do FTZ, and will round this
        // incorrectly
        __m128 v = _mm_set_ss(x);
        __m128 m = _mm_set_ss(magicVal);
        v = _mm_add_ss(v, m);
        v = _mm_sub_ss(v, m);
        _mm_store_ss((float *)&x, v);
#else
        x += magicVal;
        x -= magicVal;
#endif
        f = x;
    }

    return (long)f;
}

static long lrint_clamped(double f);
static long lrint_clamped(double f)
{
    static const double magic[2] = { MAKE_HEX_DOUBLE(0x1.0p52, 0x1LL, 52),
                                     MAKE_HEX_DOUBLE(-0x1.0p52, -0x1LL, 52) };

    if (sizeof(long) > 4)
    {
        if (f >= -(double)LONG_MIN) return LONG_MAX;
    }
    else
    {
        if (f >= LONG_MAX) return LONG_MAX;
    }

    if (f <= (double)LONG_MIN) return LONG_MIN;

    // Round fractional values to integer in round towards nearest mode
    if (fabs(f) < MAKE_HEX_DOUBLE(0x1.0p52, 0x1LL, 52))
    {
        volatile double x = f;
        double magicVal = magic[f < 0];
#if defined(__SSE2__) || (defined(_MSC_VER))
        // Defeat x87 based arithmetic, which cant do FTZ, and will round this
        // incorrectly
        __m128d v = _mm_set_sd(x);
        __m128d m = _mm_set_sd(magicVal);
        v = _mm_add_sd(v, m);
        v = _mm_sub_sd(v, m);
        _mm_store_sd((double *)&x, v);
#else
        x += magicVal;
        x -= magicVal;
#endif
        f = x;
    }

    return (long)f;
}


typedef cl_long Long;
typedef cl_ulong ULong;

static ULong sUpperLimits[kNumExplicitTypes] = {
    0,
    127,
    255,
    255,
    32767,
    65535,
    65535,
    0x7fffffffLL,
    0xffffffffLL,
    0xffffffffLL,
    0x7fffffffffffffffLL,
    0xffffffffffffffffULL,
    0xffffffffffffffffULL,
    0,
    0
}; // Last two values aren't stored here

static Long sLowerLimits[kNumExplicitTypes] = {
    -1,
    -128,
    0,
    0,
    -32768,
    0,
    0,
    (Long)0xffffffff80000000LL,
    0,
    0,
    (Long)0x8000000000000000LL,
    0,
    0,
    0,
    0
}; // Last two values aren't stored here

#define BOOL_CASE(inType)                                                      \
    case kBool:                                                                \
        boolPtr = (bool *)outRaw;                                              \
        *boolPtr = (*inType##Ptr) != 0 ? true : false;                         \
        break;

#define SIMPLE_CAST_CASE(inType, outEnum, outType)                             \
    case outEnum:                                                              \
        outType##Ptr = (outType *)outRaw;                                      \
        *outType##Ptr = (outType)(*inType##Ptr);                               \
        break;

// Sadly, the ULong downcasting cases need a separate #define to get rid of
// signed/unsigned comparison warnings
#define DOWN_CAST_CASE(inType, outEnum, outType, sat)                          \
    case outEnum:                                                              \
        outType##Ptr = (outType *)outRaw;                                      \
        if (sat)                                                               \
        {                                                                      \
            if ((sLowerLimits[outEnum] < 0                                     \
                 && *inType##Ptr > (Long)sUpperLimits[outEnum])                \
                || (sLowerLimits[outEnum] == 0                                 \
                    && (ULong)*inType##Ptr > sUpperLimits[outEnum]))           \
                *outType##Ptr = (outType)sUpperLimits[outEnum];                \
            else if (*inType##Ptr < sLowerLimits[outEnum])                     \
                *outType##Ptr = (outType)sLowerLimits[outEnum];                \
            else                                                               \
                *outType##Ptr = (outType)*inType##Ptr;                         \
        }                                                                      \
        else                                                                   \
        {                                                                      \
            *outType##Ptr = (outType)(                                         \
                *inType##Ptr                                                   \
                & (0xffffffffffffffffLL >> (64 - (sizeof(outType) * 8))));     \
        }                                                                      \
        break;

#define U_DOWN_CAST_CASE(inType, outEnum, outType, sat)                        \
    case outEnum:                                                              \
        outType##Ptr = (outType *)outRaw;                                      \
        if (sat)                                                               \
        {                                                                      \
            if ((ULong)*inType##Ptr > sUpperLimits[outEnum])                   \
                *outType##Ptr = (outType)sUpperLimits[outEnum];                \
            else                                                               \
                *outType##Ptr = (outType)*inType##Ptr;                         \
        }                                                                      \
        else                                                                   \
        {                                                                      \
            *outType##Ptr = (outType)(                                         \
                *inType##Ptr                                                   \
                & (0xffffffffffffffffLL >> (64 - (sizeof(outType) * 8))));     \
        }                                                                      \
        break;

#define TO_FLOAT_CASE(inType)                                                  \
    case kFloat:                                                               \
        floatPtr = (float *)outRaw;                                            \
        *floatPtr = (float)(*inType##Ptr);                                     \
        break;
#define TO_DOUBLE_CASE(inType)                                                 \
    case kDouble:                                                              \
        doublePtr = (double *)outRaw;                                          \
        *doublePtr = (double)(*inType##Ptr);                                   \
        break;


/* Note: we use lrintf here to force the rounding instead of whatever the
 * processor's current rounding mode is */
#define FLOAT_ROUND_TO_NEAREST_CASE(outEnum, outType)                          \
    case outEnum:                                                              \
        outType##Ptr = (outType *)outRaw;                                      \
        *outType##Ptr = (outType)lrintf_clamped(*floatPtr);                    \
        break;

#define FLOAT_ROUND_CASE(outEnum, outType, rounding, sat)                      \
    case outEnum: {                                                            \
        outType##Ptr = (outType *)outRaw;                                      \
        /* Get the tens digit */                                               \
        Long wholeValue = (Long)*floatPtr;                                     \
        float largeRemainder = (*floatPtr - (float)wholeValue) * 10.f;         \
        /* What do we do based on that? */                                     \
        if (rounding == kRoundToEven)                                          \
        {                                                                      \
            if (wholeValue & 1LL) /*between 1 and 1.99 */                      \
                wholeValue += 1LL; /* round up to even */                      \
        }                                                                      \
        else if (rounding == kRoundToZero)                                     \
        {                                                                      \
            /* Nothing to do, round-to-zero is what C casting does */          \
        }                                                                      \
        else if (rounding == kRoundToPosInf)                                   \
        {                                                                      \
            /* Only positive numbers are wrong */                              \
            if (largeRemainder != 0.f && wholeValue >= 0) wholeValue++;        \
        }                                                                      \
        else if (rounding == kRoundToNegInf)                                   \
        {                                                                      \
            /* Only negative numbers are off */                                \
            if (largeRemainder != 0.f && wholeValue < 0) wholeValue--;         \
        }                                                                      \
        else                                                                   \
        { /* Default is round-to-nearest */                                    \
            wholeValue = (Long)lrintf_clamped(*floatPtr);                      \
        }                                                                      \
        /* Now apply saturation rules */                                       \
        if (sat)                                                               \
        {                                                                      \
            if ((sLowerLimits[outEnum] < 0                                     \
                 && wholeValue > (Long)sUpperLimits[outEnum])                  \
                || (sLowerLimits[outEnum] == 0                                 \
                    && (ULong)wholeValue > sUpperLimits[outEnum]))             \
                *outType##Ptr = (outType)sUpperLimits[outEnum];                \
            else if (wholeValue < sLowerLimits[outEnum])                       \
                *outType##Ptr = (outType)sLowerLimits[outEnum];                \
            else                                                               \
                *outType##Ptr = (outType)wholeValue;                           \
        }                                                                      \
        else                                                                   \
        {                                                                      \
            *outType##Ptr = (outType)(                                         \
                wholeValue                                                     \
                & (0xffffffffffffffffLL >> (64 - (sizeof(outType) * 8))));     \
        }                                                                      \
    }                                                                          \
    break;

#define DOUBLE_ROUND_CASE(outEnum, outType, rounding, sat)                     \
    case outEnum: {                                                            \
        outType##Ptr = (outType *)outRaw;                                      \
        /* Get the tens digit */                                               \
        Long wholeValue = (Long)*doublePtr;                                    \
        double largeRemainder = (*doublePtr - (double)wholeValue) * 10.0;      \
        /* What do we do based on that? */                                     \
        if (rounding == kRoundToEven)                                          \
        {                                                                      \
            if (wholeValue & 1LL) /*between 1 and 1.99 */                      \
                wholeValue += 1LL; /* round up to even */                      \
        }                                                                      \
        else if (rounding == kRoundToZero)                                     \
        {                                                                      \
            /* Nothing to do, round-to-zero is what C casting does */          \
        }                                                                      \
        else if (rounding == kRoundToPosInf)                                   \
        {                                                                      \
            /* Only positive numbers are wrong */                              \
            if (largeRemainder != 0.0 && wholeValue >= 0) wholeValue++;        \
        }                                                                      \
        else if (rounding == kRoundToNegInf)                                   \
        {                                                                      \
            /* Only negative numbers are off */                                \
            if (largeRemainder != 0.0 && wholeValue < 0) wholeValue--;         \
        }                                                                      \
        else                                                                   \
        { /* Default is round-to-nearest */                                    \
            wholeValue = (Long)lrint_clamped(*doublePtr);                      \
        }                                                                      \
        /* Now apply saturation rules */                                       \
        if (sat)                                                               \
        {                                                                      \
            if ((sLowerLimits[outEnum] < 0                                     \
                 && wholeValue > (Long)sUpperLimits[outEnum])                  \
                || (sLowerLimits[outEnum] == 0                                 \
                    && (ULong)wholeValue > sUpperLimits[outEnum]))             \
                *outType##Ptr = (outType)sUpperLimits[outEnum];                \
            else if (wholeValue < sLowerLimits[outEnum])                       \
                *outType##Ptr = (outType)sLowerLimits[outEnum];                \
            else                                                               \
                *outType##Ptr = (outType)wholeValue;                           \
        }                                                                      \
        else                                                                   \
        {                                                                      \
            *outType##Ptr = (outType)(                                         \
                wholeValue                                                     \
                & (0xffffffffffffffffLL >> (64 - (sizeof(outType) * 8))));     \
        }                                                                      \
    }                                                                          \
    break;

typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef unsigned long ulong;

void convert_explicit_value(void *inRaw, void *outRaw, ExplicitType inType,
                            bool saturate, RoundingType roundType,
                            ExplicitType outType)
{
    bool *boolPtr;
    char *charPtr;
    uchar *ucharPtr;
    short *shortPtr;
    ushort *ushortPtr;
    int *intPtr;
    uint *uintPtr;
    Long *LongPtr;
    ULong *ULongPtr;
    float *floatPtr;
    double *doublePtr;


    switch (inType)
    {
        case kBool:
            boolPtr = (bool *)inRaw;
            switch (outType)
            {
                case kBool:
                    memcpy(outRaw, inRaw, get_explicit_type_size(inType));
                    break;

                case kChar:
                case kUChar:
                case kUnsignedChar:
                case kShort:
                case kUShort:
                case kUnsignedShort:
                case kInt:
                case kUInt:
                case kUnsignedInt:
                case kLong:
                case kULong:
                case kUnsignedLong:
                    memset(outRaw, *boolPtr ? 0xff : 0,
                           get_explicit_type_size(outType));
                    break;

                case kFloat:
                    floatPtr = (float *)outRaw;
                    *floatPtr = (*boolPtr) ? -1.f : 0.f;
                    break;
                case kDouble:
                    doublePtr = (double *)outRaw;
                    *doublePtr = (*boolPtr) ? -1.0 : 0.0;
                    break;
                default:
                    log_error("ERROR: Invalid type given to "
                              "convert_explicit_value!!\n");
                    break;
            }
            break;

        case kChar:
            charPtr = (char *)inRaw;
            switch (outType)
            {
                BOOL_CASE(char)

                case kChar:
                    memcpy(outRaw, inRaw, get_explicit_type_size(inType));
                    break;

                    DOWN_CAST_CASE(char, kUChar, uchar, saturate)
                    SIMPLE_CAST_CASE(char, kUnsignedChar, uchar)
                    SIMPLE_CAST_CASE(char, kShort, short)
                    SIMPLE_CAST_CASE(char, kUShort, ushort)
                    SIMPLE_CAST_CASE(char, kUnsignedShort, ushort)
                    SIMPLE_CAST_CASE(char, kInt, int)
                    SIMPLE_CAST_CASE(char, kUInt, uint)
                    SIMPLE_CAST_CASE(char, kUnsignedInt, uint)
                    SIMPLE_CAST_CASE(char, kLong, Long)
                    SIMPLE_CAST_CASE(char, kULong, ULong)
                    SIMPLE_CAST_CASE(char, kUnsignedLong, ULong)

                    TO_FLOAT_CASE(char)
                    TO_DOUBLE_CASE(char)

                default:
                    log_error("ERROR: Invalid type given to "
                              "convert_explicit_value!!\n");
                    break;
            }
            break;

        case kUChar:
            ucharPtr = (uchar *)inRaw;
            switch (outType)
            {
                BOOL_CASE(uchar)

                case kUChar:
                case kUnsignedChar:
                    memcpy(outRaw, inRaw, get_explicit_type_size(inType));
                    break;

                    DOWN_CAST_CASE(uchar, kChar, char, saturate)
                    SIMPLE_CAST_CASE(uchar, kShort, short)
                    SIMPLE_CAST_CASE(uchar, kUShort, ushort)
                    SIMPLE_CAST_CASE(uchar, kUnsignedShort, ushort)
                    SIMPLE_CAST_CASE(uchar, kInt, int)
                    SIMPLE_CAST_CASE(uchar, kUInt, uint)
                    SIMPLE_CAST_CASE(uchar, kUnsignedInt, uint)
                    SIMPLE_CAST_CASE(uchar, kLong, Long)
                    SIMPLE_CAST_CASE(uchar, kULong, ULong)
                    SIMPLE_CAST_CASE(uchar, kUnsignedLong, ULong)

                    TO_FLOAT_CASE(uchar)
                    TO_DOUBLE_CASE(uchar)

                default:
                    log_error("ERROR: Invalid type given to "
                              "convert_explicit_value!!\n");
                    break;
            }
            break;

        case kUnsignedChar:
            ucharPtr = (uchar *)inRaw;
            switch (outType)
            {
                BOOL_CASE(uchar)

                case kUChar:
                case kUnsignedChar:
                    memcpy(outRaw, inRaw, get_explicit_type_size(inType));
                    break;

                    DOWN_CAST_CASE(uchar, kChar, char, saturate)
                    SIMPLE_CAST_CASE(uchar, kShort, short)
                    SIMPLE_CAST_CASE(uchar, kUShort, ushort)
                    SIMPLE_CAST_CASE(uchar, kUnsignedShort, ushort)
                    SIMPLE_CAST_CASE(uchar, kInt, int)
                    SIMPLE_CAST_CASE(uchar, kUInt, uint)
                    SIMPLE_CAST_CASE(uchar, kUnsignedInt, uint)
                    SIMPLE_CAST_CASE(uchar, kLong, Long)
                    SIMPLE_CAST_CASE(uchar, kULong, ULong)
                    SIMPLE_CAST_CASE(uchar, kUnsignedLong, ULong)

                    TO_FLOAT_CASE(uchar)
                    TO_DOUBLE_CASE(uchar)

                default:
                    log_error("ERROR: Invalid type given to "
                              "convert_explicit_value!!\n");
                    break;
            }
            break;

        case kShort:
            shortPtr = (short *)inRaw;
            switch (outType)
            {
                BOOL_CASE(short)

                case kShort:
                    memcpy(outRaw, inRaw, get_explicit_type_size(inType));
                    break;

                    DOWN_CAST_CASE(short, kChar, char, saturate)
                    DOWN_CAST_CASE(short, kUChar, uchar, saturate)
                    DOWN_CAST_CASE(short, kUnsignedChar, uchar, saturate)
                    DOWN_CAST_CASE(short, kUShort, ushort, saturate)
                    DOWN_CAST_CASE(short, kUnsignedShort, ushort, saturate)
                    SIMPLE_CAST_CASE(short, kInt, int)
                    SIMPLE_CAST_CASE(short, kUInt, uint)
                    SIMPLE_CAST_CASE(short, kUnsignedInt, uint)
                    SIMPLE_CAST_CASE(short, kLong, Long)
                    SIMPLE_CAST_CASE(short, kULong, ULong)
                    SIMPLE_CAST_CASE(short, kUnsignedLong, ULong)

                    TO_FLOAT_CASE(short)
                    TO_DOUBLE_CASE(short)

                default:
                    log_error("ERROR: Invalid type given to "
                              "convert_explicit_value!!\n");
                    break;
            }
            break;

        case kUShort:
            ushortPtr = (ushort *)inRaw;
            switch (outType)
            {
                BOOL_CASE(ushort)

                case kUShort:
                case kUnsignedShort:
                    memcpy(outRaw, inRaw, get_explicit_type_size(inType));
                    break;

                    DOWN_CAST_CASE(ushort, kChar, char, saturate)
                    DOWN_CAST_CASE(ushort, kUChar, uchar, saturate)
                    DOWN_CAST_CASE(ushort, kUnsignedChar, uchar, saturate)
                    DOWN_CAST_CASE(ushort, kShort, short, saturate)
                    SIMPLE_CAST_CASE(ushort, kInt, int)
                    SIMPLE_CAST_CASE(ushort, kUInt, uint)
                    SIMPLE_CAST_CASE(ushort, kUnsignedInt, uint)
                    SIMPLE_CAST_CASE(ushort, kLong, Long)
                    SIMPLE_CAST_CASE(ushort, kULong, ULong)
                    SIMPLE_CAST_CASE(ushort, kUnsignedLong, ULong)

                    TO_FLOAT_CASE(ushort)
                    TO_DOUBLE_CASE(ushort)

                default:
                    log_error("ERROR: Invalid type given to "
                              "convert_explicit_value!!\n");
                    break;
            }
            break;

        case kUnsignedShort:
            ushortPtr = (ushort *)inRaw;
            switch (outType)
            {
                BOOL_CASE(ushort)

                case kUShort:
                case kUnsignedShort:
                    memcpy(outRaw, inRaw, get_explicit_type_size(inType));
                    break;

                    DOWN_CAST_CASE(ushort, kChar, char, saturate)
                    DOWN_CAST_CASE(ushort, kUChar, uchar, saturate)
                    DOWN_CAST_CASE(ushort, kUnsignedChar, uchar, saturate)
                    DOWN_CAST_CASE(ushort, kShort, short, saturate)
                    SIMPLE_CAST_CASE(ushort, kInt, int)
                    SIMPLE_CAST_CASE(ushort, kUInt, uint)
                    SIMPLE_CAST_CASE(ushort, kUnsignedInt, uint)
                    SIMPLE_CAST_CASE(ushort, kLong, Long)
                    SIMPLE_CAST_CASE(ushort, kULong, ULong)
                    SIMPLE_CAST_CASE(ushort, kUnsignedLong, ULong)

                    TO_FLOAT_CASE(ushort)
                    TO_DOUBLE_CASE(ushort)

                default:
                    log_error("ERROR: Invalid type given to "
                              "convert_explicit_value!!\n");
                    break;
            }
            break;

        case kInt:
            intPtr = (int *)inRaw;
            switch (outType)
            {
                BOOL_CASE(int)

                case kInt:
                    memcpy(outRaw, inRaw, get_explicit_type_size(inType));
                    break;

                    DOWN_CAST_CASE(int, kChar, char, saturate)
                    DOWN_CAST_CASE(int, kUChar, uchar, saturate)
                    DOWN_CAST_CASE(int, kUnsignedChar, uchar, saturate)
                    DOWN_CAST_CASE(int, kShort, short, saturate)
                    DOWN_CAST_CASE(int, kUShort, ushort, saturate)
                    DOWN_CAST_CASE(int, kUnsignedShort, ushort, saturate)
                    DOWN_CAST_CASE(int, kUInt, uint, saturate)
                    DOWN_CAST_CASE(int, kUnsignedInt, uint, saturate)
                    SIMPLE_CAST_CASE(int, kLong, Long)
                    SIMPLE_CAST_CASE(int, kULong, ULong)
                    SIMPLE_CAST_CASE(int, kUnsignedLong, ULong)

                    TO_FLOAT_CASE(int)
                    TO_DOUBLE_CASE(int)

                default:
                    log_error("ERROR: Invalid type given to "
                              "convert_explicit_value!!\n");
                    break;
            }
            break;

        case kUInt:
            uintPtr = (uint *)inRaw;
            switch (outType)
            {
                BOOL_CASE(uint)

                case kUInt:
                case kUnsignedInt:
                    memcpy(outRaw, inRaw, get_explicit_type_size(inType));
                    break;

                    DOWN_CAST_CASE(uint, kChar, char, saturate)
                    DOWN_CAST_CASE(uint, kUChar, uchar, saturate)
                    DOWN_CAST_CASE(uint, kUnsignedChar, uchar, saturate)
                    DOWN_CAST_CASE(uint, kShort, short, saturate)
                    DOWN_CAST_CASE(uint, kUShort, ushort, saturate)
                    DOWN_CAST_CASE(uint, kUnsignedShort, ushort, saturate)
                    DOWN_CAST_CASE(uint, kInt, int, saturate)
                    SIMPLE_CAST_CASE(uint, kLong, Long)
                    SIMPLE_CAST_CASE(uint, kULong, ULong)
                    SIMPLE_CAST_CASE(uint, kUnsignedLong, ULong)

                    TO_FLOAT_CASE(uint)
                    TO_DOUBLE_CASE(uint)

                default:
                    log_error("ERROR: Invalid type given to "
                              "convert_explicit_value!!\n");
                    break;
            }
            break;

        case kUnsignedInt:
            uintPtr = (uint *)inRaw;
            switch (outType)
            {
                BOOL_CASE(uint)

                case kUInt:
                case kUnsignedInt:
                    memcpy(outRaw, inRaw, get_explicit_type_size(inType));
                    break;

                    DOWN_CAST_CASE(uint, kChar, char, saturate)
                    DOWN_CAST_CASE(uint, kUChar, uchar, saturate)
                    DOWN_CAST_CASE(uint, kUnsignedChar, uchar, saturate)
                    DOWN_CAST_CASE(uint, kShort, short, saturate)
                    DOWN_CAST_CASE(uint, kUShort, ushort, saturate)
                    DOWN_CAST_CASE(uint, kUnsignedShort, ushort, saturate)
                    DOWN_CAST_CASE(uint, kInt, int, saturate)
                    SIMPLE_CAST_CASE(uint, kLong, Long)
                    SIMPLE_CAST_CASE(uint, kULong, ULong)
                    SIMPLE_CAST_CASE(uint, kUnsignedLong, ULong)

                    TO_FLOAT_CASE(uint)
                    TO_DOUBLE_CASE(uint)

                default:
                    log_error("ERROR: Invalid type given to "
                              "convert_explicit_value!!\n");
                    break;
            }
            break;

        case kLong:
            LongPtr = (Long *)inRaw;
            switch (outType)
            {
                BOOL_CASE(Long)

                case kLong:
                    memcpy(outRaw, inRaw, get_explicit_type_size(inType));
                    break;

                    DOWN_CAST_CASE(Long, kChar, char, saturate)
                    DOWN_CAST_CASE(Long, kUChar, uchar, saturate)
                    DOWN_CAST_CASE(Long, kUnsignedChar, uchar, saturate)
                    DOWN_CAST_CASE(Long, kShort, short, saturate)
                    DOWN_CAST_CASE(Long, kUShort, ushort, saturate)
                    DOWN_CAST_CASE(Long, kUnsignedShort, ushort, saturate)
                    DOWN_CAST_CASE(Long, kInt, int, saturate)
                    DOWN_CAST_CASE(Long, kUInt, uint, saturate)
                    DOWN_CAST_CASE(Long, kUnsignedInt, uint, saturate)
                    DOWN_CAST_CASE(Long, kULong, ULong, saturate)
                    DOWN_CAST_CASE(Long, kUnsignedLong, ULong, saturate)

                    TO_FLOAT_CASE(Long)
                    TO_DOUBLE_CASE(Long)

                default:
                    log_error("ERROR: Invalid type given to "
                              "convert_explicit_value!!\n");
                    break;
            }
            break;

        case kULong:
            ULongPtr = (ULong *)inRaw;
            switch (outType)
            {
                BOOL_CASE(ULong)

                case kUnsignedLong:
                case kULong:
                    memcpy(outRaw, inRaw, get_explicit_type_size(inType));
                    break;

                    U_DOWN_CAST_CASE(ULong, kChar, char, saturate)
                    U_DOWN_CAST_CASE(ULong, kUChar, uchar, saturate)
                    U_DOWN_CAST_CASE(ULong, kUnsignedChar, uchar, saturate)
                    U_DOWN_CAST_CASE(ULong, kShort, short, saturate)
                    U_DOWN_CAST_CASE(ULong, kUShort, ushort, saturate)
                    U_DOWN_CAST_CASE(ULong, kUnsignedShort, ushort, saturate)
                    U_DOWN_CAST_CASE(ULong, kInt, int, saturate)
                    U_DOWN_CAST_CASE(ULong, kUInt, uint, saturate)
                    U_DOWN_CAST_CASE(ULong, kUnsignedInt, uint, saturate)
                    U_DOWN_CAST_CASE(ULong, kLong, Long, saturate)

                    TO_FLOAT_CASE(ULong)
                    TO_DOUBLE_CASE(ULong)

                default:
                    log_error("ERROR: Invalid type given to "
                              "convert_explicit_value!!\n");
                    break;
            }
            break;

        case kUnsignedLong:
            ULongPtr = (ULong *)inRaw;
            switch (outType)
            {
                BOOL_CASE(ULong)

                case kULong:
                case kUnsignedLong:
                    memcpy(outRaw, inRaw, get_explicit_type_size(inType));
                    break;

                    U_DOWN_CAST_CASE(ULong, kChar, char, saturate)
                    U_DOWN_CAST_CASE(ULong, kUChar, uchar, saturate)
                    U_DOWN_CAST_CASE(ULong, kUnsignedChar, uchar, saturate)
                    U_DOWN_CAST_CASE(ULong, kShort, short, saturate)
                    U_DOWN_CAST_CASE(ULong, kUShort, ushort, saturate)
                    U_DOWN_CAST_CASE(ULong, kUnsignedShort, ushort, saturate)
                    U_DOWN_CAST_CASE(ULong, kInt, int, saturate)
                    U_DOWN_CAST_CASE(ULong, kUInt, uint, saturate)
                    U_DOWN_CAST_CASE(ULong, kUnsignedInt, uint, saturate)
                    U_DOWN_CAST_CASE(ULong, kLong, Long, saturate)

                    TO_FLOAT_CASE(ULong)
                    TO_DOUBLE_CASE(ULong)

                default:
                    log_error("ERROR: Invalid type given to "
                              "convert_explicit_value!!\n");
                    break;
            }
            break;

        case kFloat:
            floatPtr = (float *)inRaw;
            switch (outType)
            {
                BOOL_CASE(float)

                FLOAT_ROUND_CASE(kChar, char, roundType, saturate)
                FLOAT_ROUND_CASE(kUChar, uchar, roundType, saturate)
                FLOAT_ROUND_CASE(kUnsignedChar, uchar, roundType, saturate)
                FLOAT_ROUND_CASE(kShort, short, roundType, saturate)
                FLOAT_ROUND_CASE(kUShort, ushort, roundType, saturate)
                FLOAT_ROUND_CASE(kUnsignedShort, ushort, roundType, saturate)
                FLOAT_ROUND_CASE(kInt, int, roundType, saturate)
                FLOAT_ROUND_CASE(kUInt, uint, roundType, saturate)
                FLOAT_ROUND_CASE(kUnsignedInt, uint, roundType, saturate)
                FLOAT_ROUND_CASE(kLong, Long, roundType, saturate)
                FLOAT_ROUND_CASE(kULong, ULong, roundType, saturate)
                FLOAT_ROUND_CASE(kUnsignedLong, ULong, roundType, saturate)

                case kFloat:
                    memcpy(outRaw, inRaw, get_explicit_type_size(inType));
                    break;

                    TO_DOUBLE_CASE(float);

                default:
                    log_error("ERROR: Invalid type given to "
                              "convert_explicit_value!!\n");
                    break;
            }
            break;

        case kDouble:
            doublePtr = (double *)inRaw;
            switch (outType)
            {
                BOOL_CASE(double)

                DOUBLE_ROUND_CASE(kChar, char, roundType, saturate)
                DOUBLE_ROUND_CASE(kUChar, uchar, roundType, saturate)
                DOUBLE_ROUND_CASE(kUnsignedChar, uchar, roundType, saturate)
                DOUBLE_ROUND_CASE(kShort, short, roundType, saturate)
                DOUBLE_ROUND_CASE(kUShort, ushort, roundType, saturate)
                DOUBLE_ROUND_CASE(kUnsignedShort, ushort, roundType, saturate)
                DOUBLE_ROUND_CASE(kInt, int, roundType, saturate)
                DOUBLE_ROUND_CASE(kUInt, uint, roundType, saturate)
                DOUBLE_ROUND_CASE(kUnsignedInt, uint, roundType, saturate)
                DOUBLE_ROUND_CASE(kLong, Long, roundType, saturate)
                DOUBLE_ROUND_CASE(kULong, ULong, roundType, saturate)
                DOUBLE_ROUND_CASE(kUnsignedLong, ULong, roundType, saturate)

                TO_FLOAT_CASE(double);

                case kDouble:
                    memcpy(outRaw, inRaw, get_explicit_type_size(inType));
                    break;

                default:
                    log_error("ERROR: Invalid type given to "
                              "convert_explicit_value!!\n");
                    break;
            }
            break;

        default:
            log_error(
                "ERROR: Invalid type given to convert_explicit_value!!\n");
            break;
    }
}

void generate_random_data(ExplicitType type, size_t count, MTdata d,
                          void *outData)
{
    bool *boolPtr;
    cl_char *charPtr;
    cl_uchar *ucharPtr;
    cl_short *shortPtr;
    cl_ushort *ushortPtr;
    cl_int *intPtr;
    cl_uint *uintPtr;
    cl_long *longPtr;
    cl_ulong *ulongPtr;
    cl_float *floatPtr;
    cl_double *doublePtr;
    cl_half *halfPtr;
    size_t i;
    cl_uint bits = genrand_int32(d);
    cl_uint bitsLeft = 32;

    switch (type)
    {
        case kBool:
            boolPtr = (bool *)outData;
            for (i = 0; i < count; i++)
            {
                if (0 == bitsLeft)
                {
                    bits = genrand_int32(d);
                    bitsLeft = 32;
                }
                boolPtr[i] = (bits & 1) ? true : false;
                bits >>= 1;
                bitsLeft -= 1;
            }
            break;

        case kChar:
            charPtr = (cl_char *)outData;
            for (i = 0; i < count; i++)
            {
                if (0 == bitsLeft)
                {
                    bits = genrand_int32(d);
                    bitsLeft = 32;
                }
                charPtr[i] = (cl_char)((cl_int)(bits & 255) - 127);
                bits >>= 8;
                bitsLeft -= 8;
            }
            break;

        case kUChar:
        case kUnsignedChar:
            ucharPtr = (cl_uchar *)outData;
            for (i = 0; i < count; i++)
            {
                if (0 == bitsLeft)
                {
                    bits = genrand_int32(d);
                    bitsLeft = 32;
                }
                ucharPtr[i] = (cl_uchar)(bits & 255);
                bits >>= 8;
                bitsLeft -= 8;
            }
            break;

        case kShort:
            shortPtr = (cl_short *)outData;
            for (i = 0; i < count; i++)
            {
                if (0 == bitsLeft)
                {
                    bits = genrand_int32(d);
                    bitsLeft = 32;
                }
                shortPtr[i] = (cl_short)((cl_int)(bits & 65535) - 32767);
                bits >>= 16;
                bitsLeft -= 16;
            }
            break;

        case kUShort:
        case kUnsignedShort:
            ushortPtr = (cl_ushort *)outData;
            for (i = 0; i < count; i++)
            {
                if (0 == bitsLeft)
                {
                    bits = genrand_int32(d);
                    bitsLeft = 32;
                }
                ushortPtr[i] = (cl_ushort)((cl_int)(bits & 65535));
                bits >>= 16;
                bitsLeft -= 16;
            }
            break;

        case kInt:
            intPtr = (cl_int *)outData;
            for (i = 0; i < count; i++)
            {
                intPtr[i] = (cl_int)genrand_int32(d);
            }
            break;

        case kUInt:
        case kUnsignedInt:
            uintPtr = (cl_uint *)outData;
            for (i = 0; i < count; i++)
            {
                uintPtr[i] = (unsigned int)genrand_int32(d);
            }
            break;

        case kLong:
            longPtr = (cl_long *)outData;
            for (i = 0; i < count; i++)
            {
                longPtr[i] = (cl_long)genrand_int32(d)
                    | ((cl_long)genrand_int32(d) << 32);
            }
            break;

        case kULong:
        case kUnsignedLong:
            ulongPtr = (cl_ulong *)outData;
            for (i = 0; i < count; i++)
            {
                ulongPtr[i] = (cl_ulong)genrand_int32(d)
                    | ((cl_ulong)genrand_int32(d) << 32);
            }
            break;

        case kFloat:
            floatPtr = (cl_float *)outData;
            for (i = 0; i < count; i++)
            {
                // [ -(double) 0x7fffffff, (double) 0x7fffffff ]
                double t = genrand_real1(d);
                floatPtr[i] = (float)((1.0 - t) * -(double)0x7fffffff
                                      + t * (double)0x7fffffff);
            }
            break;

        case kDouble:
            doublePtr = (cl_double *)outData;
            for (i = 0; i < count; i++)
            {
                cl_long u = (cl_long)genrand_int32(d)
                    | ((cl_long)genrand_int32(d) << 32);
                double t = (double)u;
                // scale [-2**63, 2**63] to [-2**31, 2**31]
                t *= MAKE_HEX_DOUBLE(0x1.0p-32, 0x1, -32);
                doublePtr[i] = t;
            }
            break;

        case kHalf:
            halfPtr = (ushort *)outData;
            for (i = 0; i < count; i++)
            {
                if (0 == bitsLeft)
                {
                    bits = genrand_int32(d);
                    bitsLeft = 32;
                }
                halfPtr[i] =
                    bits & 65535; /* Kindly generates random bits for us */
                bits >>= 16;
                bitsLeft -= 16;
            }
            break;

        default:
            log_error(
                "ERROR: Invalid type passed in to generate_random_data!\n");
            break;
    }
}

void *create_random_data(ExplicitType type, MTdata d, size_t count)
{
    void *data = malloc(get_explicit_type_size(type) * count);
    generate_random_data(type, count, d, data);
    return data;
}

cl_long read_upscale_signed(void *inRaw, ExplicitType inType)
{
    switch (inType)
    {
        case kChar: return (cl_long)(*((cl_char *)inRaw));
        case kUChar:
        case kUnsignedChar: return (cl_long)(*((cl_uchar *)inRaw));
        case kShort: return (cl_long)(*((cl_short *)inRaw));
        case kUShort:
        case kUnsignedShort: return (cl_long)(*((cl_ushort *)inRaw));
        case kInt: return (cl_long)(*((cl_int *)inRaw));
        case kUInt:
        case kUnsignedInt: return (cl_long)(*((cl_uint *)inRaw));
        case kLong: return (cl_long)(*((cl_long *)inRaw));
        case kULong:
        case kUnsignedLong: return (cl_long)(*((cl_ulong *)inRaw));
        default: return 0;
    }
}

cl_ulong read_upscale_unsigned(void *inRaw, ExplicitType inType)
{
    switch (inType)
    {
        case kChar: return (cl_ulong)(*((cl_char *)inRaw));
        case kUChar:
        case kUnsignedChar: return (cl_ulong)(*((cl_uchar *)inRaw));
        case kShort: return (cl_ulong)(*((cl_short *)inRaw));
        case kUShort:
        case kUnsignedShort: return (cl_ulong)(*((cl_ushort *)inRaw));
        case kInt: return (cl_ulong)(*((cl_int *)inRaw));
        case kUInt:
        case kUnsignedInt: return (cl_ulong)(*((cl_uint *)inRaw));
        case kLong: return (cl_ulong)(*((cl_long *)inRaw));
        case kULong:
        case kUnsignedLong: return (cl_ulong)(*((cl_ulong *)inRaw));
        default: return 0;
    }
}

float read_as_float(void *inRaw, ExplicitType inType)
{
    switch (inType)
    {
        case kChar: return (float)(*((cl_char *)inRaw));
        case kUChar:
        case kUnsignedChar: return (float)(*((cl_char *)inRaw));
        case kShort: return (float)(*((cl_short *)inRaw));
        case kUShort:
        case kUnsignedShort: return (float)(*((cl_ushort *)inRaw));
        case kInt: return (float)(*((cl_int *)inRaw));
        case kUInt:
        case kUnsignedInt: return (float)(*((cl_uint *)inRaw));
        case kLong: return (float)(*((cl_long *)inRaw));
        case kULong:
        case kUnsignedLong: return (float)(*((cl_ulong *)inRaw));
        case kFloat: return *((float *)inRaw);
        case kDouble: return (float)*((double *)inRaw);
        default: return 0;
    }
}

float get_random_float(float low, float high, MTdata d)
{
    float t = (float)((double)genrand_int32(d) / (double)0xFFFFFFFF);
    return (1.0f - t) * low + t * high;
}

double get_random_double(double low, double high, MTdata d)
{
    cl_ulong u =
        (cl_ulong)genrand_int32(d) | ((cl_ulong)genrand_int32(d) << 32);
    double t = (double)u * MAKE_HEX_DOUBLE(0x1.0p-64, 0x1, -64);
    return (1.0f - t) * low + t * high;
}

float any_float(MTdata d)
{
    union {
        float f;
        cl_uint u;
    } u;

    u.u = genrand_int32(d);
    return u.f;
}


double any_double(MTdata d)
{
    union {
        double f;
        cl_ulong u;
    } u;

    u.u = (cl_ulong)genrand_int32(d) | ((cl_ulong)genrand_int32(d) << 32);
    return u.f;
}

int random_in_range(int minV, int maxV, MTdata d)
{
    cl_ulong r = ((cl_ulong)genrand_int32(d)) * (maxV - minV + 1);
    return (cl_uint)(r >> 32) + minV;
}

size_t get_random_size_t(size_t low, size_t high, MTdata d)
{
    enum
    {
        N = sizeof(size_t) / sizeof(int)
    };

    union {
        int word[N];
        size_t size;
    } u;

    for (unsigned i = 0; i != N; ++i)
    {
        u.word[i] = genrand_int32(d);
    }

    assert(low <= high && "Invalid random number range specified");
    size_t range = high - low;

    return (range) ? low + ((u.size - low) % range) : low;
}
