//
// 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 "../../test_common/harness/compat.h"
#include "exceptions.h"
#include "datagen.h"

RandomGenerator gRG;

size_t WorkSizeInfo::getGlobalWorkSize() const
{
    switch( work_dim )
    {
    case 1: return global_work_size[0];
    case 2: return global_work_size[0] * global_work_size[1];
    case 3: return global_work_size[0] * global_work_size[1] * global_work_size[2];
    default:
        throw Exceptions::TestError("wrong work dimention\n");
    }
}

/*
 * DataGenerator
 */

DataGenerator* DataGenerator::Instance = NULL;

DataGenerator* DataGenerator::getInstance()
{
    if (!Instance)
        Instance = new DataGenerator();

    return Instance;
}

DataGenerator::DataGenerator()
{
    #define TYPE_HNDL( type, isBuffer, base_element_size, vector_size, min_value, max_value, Generator) \
    assert(m_argGenerators.find(type) == m_argGenerators.end()) ; \
    m_argGenerators[type] = new Generator( isBuffer, vector_size, min_value, max_value);
    #include "typeinfo.h"
    #undef TYPE_HNDL
}

DataGenerator::~DataGenerator()
{
    ArgGeneratorsMap::iterator i = m_argGenerators.begin();
    ArgGeneratorsMap::iterator e = m_argGenerators.end();

    for(; i != e; ++i)
    {
        delete i->second;
    }
}

KernelArgGenerator* DataGenerator::getArgGenerator( const KernelArgInfo& argInfo )
{
    //try to match the full type first
    ArgGeneratorsMap::iterator i = m_argGenerators.find(argInfo.getTypeName());
    ArgGeneratorsMap::iterator e = m_argGenerators.end();

    if( i != e )
    {
        return i->second;
    }
    // search for the proper prefix of the type
    for(i = m_argGenerators.begin(); i != e; ++i)
    {
        if( 0 == argInfo.getTypeName().find(i->first))
        {
            return i->second;
        }
    }
    throw Exceptions::TestError(std::string("Can't find the generator for the type ")
      + argInfo.getTypeName() + " for argument " + argInfo.getName() + "\n");
}

void DataGenerator::setArgGenerator(const KernelArgInfo& argInfo,
                                    KernelArgGenerator* pGen)
{
    m_argGenerators[argInfo.getTypeName()] = pGen;
}

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;
}

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;
}

size_t get_random_int32(int low, int high, MTdata d)
{
  int v = genrand_int32(d);

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

  return (range) ? low + ((v - low) % range) : low;
}

/*
 * KernelArgGeneratorSampler
 */
KernelArgGeneratorSampler::KernelArgGeneratorSampler(bool isBuffer,
                                                     size_t vectorSize,
                                                     int minValue,
                                                     int maxValue) {
  initToDefaults();
}

void KernelArgGeneratorSampler::initToDefaults() {
  m_normalized = false;
  m_addressingMode = CL_ADDRESS_NONE;
  m_filterMode = CL_FILTER_NEAREST;
}

KernelArgGeneratorSampler::KernelArgGeneratorSampler() {
  initToDefaults();

}

void KernelArgGeneratorSampler::setNormalized(cl_bool isNormalized)
{
    m_normalized = isNormalized;
}

void KernelArgGeneratorSampler::setAddressingMode(cl_addressing_mode mode)
{
    m_addressingMode = mode;
}

void KernelArgGeneratorSampler::setFiterMode(cl_filter_mode mode)
{
    m_filterMode = mode;
}


/*
 * SamplerValuesGenerator.
 */

/*
 * Static fields initialization.
 */
cl_bool SamplerValuesGenerator::coordNormalizations[] = {CL_TRUE, CL_FALSE};

cl_filter_mode SamplerValuesGenerator::filterModes[]  = {
    CL_FILTER_NEAREST,
    CL_FILTER_LINEAR
};

cl_addressing_mode SamplerValuesGenerator::addressingModes[] = {
    CL_ADDRESS_NONE,
    CL_ADDRESS_CLAMP,
    CL_ADDRESS_CLAMP_TO_EDGE,
    CL_ADDRESS_REPEAT,
    CL_ADDRESS_MIRRORED_REPEAT
};

const size_t NUM_NORM_MODES =
    sizeof(SamplerValuesGenerator::coordNormalizations)/sizeof(cl_bool);

const size_t NUM_FILTER_MODES =
    sizeof(SamplerValuesGenerator::filterModes)/sizeof(cl_filter_mode);

const size_t NUM_ADDR_MODES =
    sizeof(SamplerValuesGenerator::addressingModes)/sizeof(cl_addressing_mode);

SamplerValuesGenerator::iterator SamplerValuesGenerator::end()
{
    return iterator(NUM_NORM_MODES-1, NUM_FILTER_MODES-1, NUM_ADDR_MODES-1);
}

/*
 * A constructor for generating an 'end iterator'.
 */
SamplerValuesGenerator::iterator::iterator(size_t norm, size_t filter,
                                           size_t addressing):
    m_normIndex(norm), m_filterIndex(filter), m_addressingModeIndex(addressing){}

/*
 * A constructor for generating a 'begin iterator'.
 */
SamplerValuesGenerator::iterator::iterator():
    m_normIndex(0), m_filterIndex(0), m_addressingModeIndex(0){}

SamplerValuesGenerator::iterator& SamplerValuesGenerator::iterator::operator ++()
{
    if (incrementIndex(m_normIndex, NUM_NORM_MODES)) return *this;
    if (incrementIndex(m_filterIndex, NUM_FILTER_MODES)) return *this;
    if (incrementIndex(m_addressingModeIndex, NUM_ADDR_MODES)) return *this;

    assert(false && "incrementing end iterator!");
    return *this;
}

bool SamplerValuesGenerator::iterator::incrementIndex(size_t &i,
                                                      const size_t limit)
{
    i = (i+1) % limit;
    return i != 0;
}

bool SamplerValuesGenerator::iterator::operator == (const iterator& other) const
{
    return m_normIndex == other.m_normIndex &&
         m_filterIndex == other.m_filterIndex &&
         m_addressingModeIndex == other.m_addressingModeIndex;
}

bool SamplerValuesGenerator::iterator::operator != (const iterator& other) const
{
    return !(*this == other);
}

cl_bool SamplerValuesGenerator::iterator::getNormalized() const
{
    assert(m_normIndex < NUM_NORM_MODES && "illegal index");
    return coordNormalizations[m_normIndex];
}

cl_filter_mode SamplerValuesGenerator::iterator::getFilterMode() const
{
    assert(m_filterIndex < NUM_FILTER_MODES && "illegal index");
    return filterModes[m_filterIndex];
}

cl_addressing_mode SamplerValuesGenerator::iterator::getAddressingMode() const
{
    assert(m_addressingModeIndex < NUM_ADDR_MODES && "illegal index");
    return addressingModes[m_addressingModeIndex];
}

unsigned SamplerValuesGenerator::iterator::toBitmap() const
{
    unsigned norm, filter, addressingModes;
    switch (getNormalized())
    {
    case CL_TRUE:
        norm = 8;
        break;
    case CL_FALSE:
        norm = 0;
        break;
    default:
    assert(0 && "invalid normalize value");
    }

    switch (getFilterMode())
    {
    case CL_FILTER_NEAREST:
        filter = 0;
        break;
    case CL_FILTER_LINEAR:
        filter = 16;
        break;
    default:
    assert(0 && "invalid filter value");
    }

    switch(getAddressingMode())
    {
    case CL_ADDRESS_NONE:
        addressingModes = 0;
        break;
    case CL_ADDRESS_CLAMP:
        addressingModes = 1;
        break;
    case CL_ADDRESS_CLAMP_TO_EDGE:
        addressingModes = 2;
        break;
    case CL_ADDRESS_REPEAT:
        addressingModes = 3;
        break;
    case CL_ADDRESS_MIRRORED_REPEAT:
        addressingModes = 4;
        break;
    default:
    assert(0 && "invalid filter value");
    }

    return norm | filter | addressingModes;
}

std::string SamplerValuesGenerator::iterator::toString() const
{
    std::string ret("(");

    switch (getNormalized())
    {
    case CL_TRUE:
        ret.append("Normalized | ");
        break;
    case CL_FALSE:
        ret.append("Not Normalized | ");
        break;
    default:
    assert(0 && "invalid normalize value");
    }

    switch (getFilterMode())
    {
    case CL_FILTER_NEAREST:
        ret.append("Filter Nearest | ");
        break;
    case CL_FILTER_LINEAR:
        ret.append("Filter Linear | ");
        break;
    default:
    assert(0 && "invalid filter value");
    }

    switch(getAddressingMode())
    {
    case CL_ADDRESS_NONE:
        ret.append("Address None");
        break;
    case CL_ADDRESS_CLAMP:
        ret.append("Address clamp");
        break;
    case CL_ADDRESS_CLAMP_TO_EDGE:
        ret.append("Address clamp to edge");
        break;
    case CL_ADDRESS_REPEAT:
        ret.append("Address repeat");
        break;
    case CL_ADDRESS_MIRRORED_REPEAT:
        ret.append("Address mirrored repeat");
        break;
    default:
    assert(0 && "invalid filter value");
    }

    ret.append(")");
    return ret;
}

/*
 * ImageValuesGenerator.
 */

/*
 * Static fields initialization.
 */
const char* ImageValuesGenerator::imageTypes[] = {
    "image1d_array_float",
    "image1d_array_int",
    "image1d_array_uint",
    "image1d_buffer_float",
    "image1d_buffer_int",
    "image1d_buffer_uint",
    "image1d_float",
    "image1d_int",
    "image1d_uint",
    "image2d_array_float",
    "image2d_array_int",
    "image2d_array_uint",
    "image2d_float",
    "image2d_int",
    "image2d_uint",
    "image3d_float",
    "image3d_int",
    "image3d_uint"
};

cl_channel_order ImageValuesGenerator::channelOrders[] = {
    CL_A,
    CL_R,
    CL_Rx,
    CL_RG,
    CL_RGx,
    CL_RA,
    CL_RGB,
    CL_RGBx,
    CL_RGBA,
    CL_ARGB,
    CL_BGRA,
    CL_INTENSITY,
    CL_LUMINANCE,
    CL_DEPTH,
    CL_DEPTH_STENCIL
};

const size_t NUM_CHANNEL_ORDERS = sizeof(ImageValuesGenerator::channelOrders)/sizeof(ImageValuesGenerator::channelOrders[0]);
const size_t NUM_IMG_TYS = sizeof(ImageValuesGenerator::imageTypes)/sizeof(ImageValuesGenerator::imageTypes[0]);

ImageValuesGenerator::iterator ImageValuesGenerator::begin()
{
    return ImageValuesGenerator::iterator(this);
}

ImageValuesGenerator::iterator ImageValuesGenerator::end()
{
    return ImageValuesGenerator::iterator(0);
}
/*
 * Class Iterator
 */
ImageValuesGenerator::iterator::iterator(ImageValuesGenerator *pParent):
    m_parent(pParent), m_channelIndex(0), m_imgTyIndex(0)
{
}

/*
 * Initializes an 'end' iterator.
 */
ImageValuesGenerator::iterator::iterator(int):
    m_parent(NULL),
    m_channelIndex(NUM_CHANNEL_ORDERS),
    m_imgTyIndex(NUM_IMG_TYS) {}

ImageValuesGenerator::iterator& ImageValuesGenerator::iterator::operator ++()
{
    assert(m_channelIndex < NUM_CHANNEL_ORDERS && m_imgTyIndex < NUM_IMG_TYS &&
           "Attempt to increment an end iterator");

    ImageValuesGenerator::iterator endIter = iterator(0);
    // Incrementing untill we find the next legal combination, or we reach the
    // end value.
    while (incrementIndex(m_channelIndex,NUM_CHANNEL_ORDERS))
        if (isLegalCombination())
            return *this;

     // We have reach to this line because last increment caused an 'oveflow'
     // in data channel order index.
     if (incrementIndex(m_imgTyIndex, NUM_IMG_TYS))
        // In case this combination is not legal, we go on to the next legal
        // combo.
        return isLegalCombination() ? *this : ++(*this);

    *this = endIter;
    return *this;
}

bool ImageValuesGenerator::iterator::operator == (
    const ImageValuesGenerator::iterator& o) const
{
    return m_channelIndex == o.m_channelIndex &&
           m_imgTyIndex == o.m_imgTyIndex;
}

bool ImageValuesGenerator::iterator::operator != (
    const ImageValuesGenerator::iterator& o) const
{
    return !(*this == o);
}

std::string ImageValuesGenerator::iterator::getDataTypeName() const
{
    assert(m_imgTyIndex < NUM_IMG_TYS && "image type index is out of bound");

    std::string tyName(imageTypes[m_imgTyIndex]);
    // Find the last '_' and remove it (the suffix is _<channel type>).
    size_t pos = tyName.find_last_of('_');
    assert (std::string::npos != pos && "no under score in type name?");
    tyName = tyName.erase(0, pos+1);
    return tyName;
}

int ImageValuesGenerator::iterator::getOpenCLChannelOrder() const
{
    assert(m_channelIndex < NUM_CHANNEL_ORDERS && "channel index out of bound");
    return channelOrders[m_channelIndex];
}

int ImageValuesGenerator::iterator::getSPIRChannelOrder() const
{
    return getOpenCLChannelOrder();
}

std::string ImageValuesGenerator::iterator::getImageTypeName() const
{
    assert(m_imgTyIndex < NUM_IMG_TYS && "image type index is out of bound");

    std::string tyName = imageTypes[m_imgTyIndex];
    // Find the last '_' and remove it (the suffix is _<channel type>).
    size_t pos = tyName.find_last_of('_');
    assert (std::string::npos != pos && "no under score in type name?");
    tyName = tyName.erase(pos, tyName.size() - pos);

    return tyName;
}

std::string ImageValuesGenerator::iterator::getImageGeneratorName() const
{
    assert(m_imgTyIndex < NUM_IMG_TYS && "image type index is out of bound");
    return imageTypes[m_imgTyIndex];
}

std::string ImageValuesGenerator::iterator::getBaseImageGeneratorName() const
{
    assert(m_imgTyIndex < NUM_IMG_TYS && "image type index is out of bound");
    std::string tyName = getImageTypeName();
    tyName.append("_t");
    return tyName;
}

int ImageValuesGenerator::iterator::getDataType() const
{
    assert(m_imgTyIndex < NUM_IMG_TYS && "image type index is out of bound");
    std::string tyName = getDataTypeName();

    if ("int" == tyName)
       return SPIR_CLK_SIGNED_INT32;
    if ("uint" == tyName)
        return SPIR_CLK_UNSIGNED_INT32;
    if ("float" == tyName)
        return SPIR_CLK_FLOAT;
    assert (false && "unkown image data type");
    return -1;
}

std::string ImageValuesGenerator::iterator::toString() const
{
    if (*this == m_parent->end())
        return "End iterator";

    // Sanity.
    assert(m_imgTyIndex < NUM_IMG_TYS && "image type index is out of bound");
    assert(m_channelIndex < NUM_CHANNEL_ORDERS && "channel index out of bound");

    std::string str = imageTypes[m_imgTyIndex];
    str.append("_");

    switch (channelOrders[m_channelIndex])
    {
    case CL_R:
        str.append("cl_r");
        break;
    case CL_A:
        str.append("cl_a");
        break;
    case CL_RG:
        str.append("cl_rg");
        break;
    case CL_RA:
        str.append("cl_ra");
        break;
    case CL_RGB:
        str.append("cl_rgb");
        break;
    case CL_RGBA:
        str.append("cl_rgba");
        break;
    case CL_BGRA:
        str.append("cl_bgra");
        break;
    case CL_ARGB:
        str.append("cl_argb");
        break;
    case CL_INTENSITY:
        str.append("cl_intensity");
        break;
    case CL_LUMINANCE:
        str.append("cl_luminace");
        break;
    case CL_Rx:
        str.append("cl_Rx");
        break;
    case CL_RGx:
        str.append("cl_RGx");
        break;
    case CL_RGBx:
        str.append("cl_RGBx");
        break;
    case CL_DEPTH:
        str.append("cl_depth");
        break;
    case CL_DEPTH_STENCIL:
        str.append( "cl_depth_stencil");
        break;
    default:
        assert(false && "Invalid channel order");
        str.append("<invalid channel order>");
        break;
    }

    return str;
}

bool ImageValuesGenerator::iterator::incrementIndex(size_t& index,
                                                    size_t arrSize)
{
    index = (index + 1) % arrSize;
    return index != 0;
}

bool ImageValuesGenerator::iterator::isLegalCombination() const
{
    cl_channel_order corder = channelOrders[m_channelIndex];
    std::string strImgTy(imageTypes[m_imgTyIndex]);

    if (corder == CL_INTENSITY || corder == CL_LUMINANCE)
    {
        return getDataTypeName() == std::string("float");
    }

    if (corder == CL_DEPTH)
        return false;

    if (corder == CL_RGBx || corder == CL_RGB // Can only be applied for int unorms.
        || corder == CL_ARGB || corder == CL_BGRA) // Can only be applied for int8.
        return false;

    return true;
}

