/*
 * LIBOIL - Library of Optimized Inner Loops
 * Copyright (c) 2003,2004 David A. Schleef <ds@schleef.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
//Portions Copyright (c)  2008-2009 Nokia Corporation and/or its subsidiary(-ies). All rights reserved. 

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <liboil/liboiltest.h>
#include <liboil/liboildebug.h>
#include <liboil/liboilrandom.h>
#include <liboil/liboilprofile.h>
#include <liboil/liboilfault.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>

#ifdef __SYMBIAN32__
#ifdef __WINSCW__
#pragma warn_unusedarg off 
#endif//__WINSCW__
#endif//__SYMBIAN32__

#define MAX_PARAMS 20

/**
 * SECTION:liboiltest
 * @title:OilTest
 * @short_description: Test and profile function implementations.
 *
 */
static void oil_test_init_params (OilTest *test);
static void fill_array (void *ptr, OilType type, int pre_n, int stride,
    int post_n);
static double check_array (void *data, void *ref, OilType type, int pre_n,
    int stride, int post_n);
static int check_holes (void *data, OilType type, int pre_n,
    int stride, int post_n, int guard);

/**
 * oil_test_new:
 * @klass: an OilFunctionClass
 *
 * Creates a new OilTest for the OilFunctionClass represented by @klass.
 *
 * Returns: the new OilTest
 */
#ifdef __SYMBIAN32__
EXPORT_C
#endif
OilTest *
oil_test_new (OilFunctionClass *klass)
{
  OilTest *test;
  OilPrototype *proto;
  int i;

  if (klass == NULL) return NULL;

  proto = oil_prototype_from_string (klass->prototype);
  if (proto == NULL) return NULL;

  test = malloc (sizeof (OilTest));
  memset (test, 0, sizeof (OilTest));

  test->klass = klass;
  test->proto = proto;
  test->impl = klass->reference_impl;
  test->tolerance = 0.0;

  for (i=0;i<proto->n_params;i++){
    if (proto->params[i].parameter_type == OIL_ARG_UNKNOWN) {
      return NULL;
    }
    if (oil_type_is_floating_point(proto->params[i].type)) {
      test->tolerance = 0.001;
    }
    memcpy (&test->params[proto->params[i].parameter_type], &proto->params[i],
        sizeof(OilParameter));
  }
  for (i=0;i<OIL_ARG_LAST;i++){
    test->params[i].src_data = NULL;
    test->params[i].ref_data = NULL;
    test->params[i].test_data = NULL;
    test->params[i].test_header = OIL_TEST_HEADER;
    test->params[i].test_footer = OIL_TEST_FOOTER;
  }

  test->iterations = 10;
  test->n = 100;
  test->m = 100;

  return test;
}

/**
 * oil_test_free:
 * @test: the OilTest
 *
 * Frees memory associated with @test.
 */
 #ifdef __SYMBIAN32__
EXPORT_C
#endif
void
oil_test_free (OilTest *test)
{
  int i;

  if (test->proto) {
    oil_prototype_free (test->proto);
  }

  for(i=0;i<OIL_ARG_LAST;i++){
    if (test->params[i].src_data) {
      free (test->params[i].src_data);
    }
    if (test->params[i].ref_data) {
      free (test->params[i].ref_data);
    }
    if (test->params[i].test_data) {
      free (test->params[i].test_data);
    }
  }

  free(test);
}

/**
 * oil_test_set_impl:
 * @test: the OilTest
 * @impl: an OilFunctionImpl to set
 *
 * Sets the current implementation of @test to @impl.
 */
 #ifdef __SYMBIAN32__
 EXPORT_C
#endif
void
oil_test_set_impl (OilTest *test, OilFunctionImpl *impl)
{
  test->impl = impl;
}

/**
 * oil_test_set_iterations:
 * @test: the OilTest
 * @iterations: the number of iterations
 *
 * Sets the number of iterations of @test to @iterations.
 */
 #ifdef __SYMBIAN32__
 EXPORT_C
#endif
void
oil_test_set_iterations (OilTest *test, int iterations)
{
  test->iterations = iterations;
}

/**
 * oil_test_set_test_header:
 * @test: the OilTest
 * @p: the OilParameter to change the header for
 * @test_header: the number of bytes of guard header
 *
 * Sets the number of bytes of guard header for @p to @test_header.
 */
 #ifdef __SYMBIAN32__
 EXPORT_C
#endif
void
oil_test_set_test_header (OilTest *test, OilParameter *p, int test_header)
{
  p->test_header = test_header;
}

/**
 * oil_test_set_test_footer:
 * @test: the OilTest
 * @p: the OilParameter to change the footer for
 * @test_footer: the number of bytes of guard footer
 *
 * Sets the number of bytes of guard footer for @p to @test_footer.
 */
 #ifdef __SYMBIAN32__
 EXPORT_C
#endif
void
oil_test_set_test_footer (OilTest *test, OilParameter *p, int test_footer)
{
  p->test_footer = test_footer;
}

/**
 * oil_test_init:
 * @test: the OilTest
 *
 * Intializes @test.
 * 
 * FIXME: needs work
 */
 #ifdef __SYMBIAN32__
 EXPORT_C
#endif
void
oil_test_init (OilTest *test)
{
  if (test->inited) {
    return;
  }

  oil_test_init_params(test);

  test->params[OIL_ARG_N].value = test->n;
  
  test->inited = 1;

  if (test->klass->test_func) {
    test->klass->test_func (test);
  }
}

#ifdef __WINSCW__
#pragma suppress_warnings on 
#endif//__WINSCW__
#ifdef __ARMCC__
#pragma diag_remark 188
#endif//__ARMCC__

static void
oil_test_check_function (void * priv)
{
  OilTest *test = priv;
  int i;
  int j;
  unsigned long args[MAX_PARAMS];
  unsigned int pointer_mask;

  oil_test_init (test);

  OIL_LOG("calling function %s", test->impl->name);

  pointer_mask = 1;
  for(i=0;i<test->proto->n_params;i++){
    OilParameter *p;
    j = test->proto->params[i].parameter_type;
    p = &test->params[j];

    pointer_mask <<= 1;
    OIL_LOG("  %s: 0x%08lx (%ld)", oil_arg_type_name (j), p->value, p->value);
    if (p->is_pointer) {
      pointer_mask |= 1;
      if (p->direction == 's') {
        args[i] = (unsigned long)p->src_data + p->test_header;
      } else if (p->direction == 'i') {
        memcpy (p->test_data, p->src_data, p->size);
        args[i] = (unsigned long)p->test_data + p->test_header;
      } else if (p->direction == 'd') {
        memset (p->test_data, p->guard, p->size);
        args[i] = (unsigned long)p->test_data + p->test_header;
      } else {
        OIL_ERROR ("not reached");
      }
    } else {
      args[i] = p->value;
    }
  }
#ifdef __WINSCW__
#pragma suppress_warnings off
#endif//__WINSCW__

  oil_profile_init (&test->prof);
  for(i=0;i<test->iterations;i++){
    int k;

    for(k=0;k<test->proto->n_params;k++){
      OilParameter *p;
      j = test->proto->params[k].parameter_type;
      p = &test->params[j];
      if (p->direction == 'i') {
        memcpy (p->test_data, p->src_data, p->size);
      }
    }
    _oil_test_marshal_function (test->impl->func, args, test->proto->n_params,
        pointer_mask, &test->prof);
  }

  oil_profile_get_ave_std (&test->prof, &test->profile_ave,
      &test->profile_std);
}

/**
 * oil_test_check_ref:
 * @test: the OilTest
 *
 * Runs the test specified by @test on the reference function of the
 * class being tested.
 */
 #ifdef __SYMBIAN32__
 EXPORT_C
#endif
void
oil_test_check_ref (OilTest *test)
{
  int i;

  if (test->proto->n_params > MAX_PARAMS) {
    OIL_ERROR ("function class %s has too many parameters",
        test->klass->name);
    return;
  }
  if (test->klass->reference_impl == NULL) {
    OIL_ERROR ("function class %s has no reference implementation",
        test->klass->name);
    return;
  }

  test->impl = test->klass->reference_impl;

  oil_test_check_function (test);

  for(i=0;i<OIL_ARG_LAST;i++){
    OilParameter *p = &test->params[i];

    if (p->is_pointer) {
      if (p->direction == 'i' || p->direction == 'd') {
        memcpy (p->ref_data, p->test_data, p->size);
      }
    }
  }

  test->tested_ref = 1;
}

static int
check_guard (uint8_t *data, int n, int guard)
{
  int i;
  for(i=0;i<n;i++) {
    if (data[i] != guard) return 0;
  }
  return 1;
}

/**
 * oil_test_check_impl:
 * @test: the OilTest
 * @impl: an OilFunctionImpl
 *
 * Runs the testing procedure described by @test on the implementation
 * @impl.
 *
 * Returns: 1 if @impl passes the test, 0 if it fails
 */
 #ifdef __SYMBIAN32__
 EXPORT_C
#endif
int
oil_test_check_impl (OilTest *test, OilFunctionImpl *impl)
{
  double x;
  int i;
  int n;
  int fail = 0;
  int ret;

  if (test->proto->n_params > MAX_PARAMS) {
    OIL_ERROR ("function has too many parameters");
    return 0;
  }

  if (!test->inited || !test->tested_ref) {
    oil_test_check_ref(test);
  }

  test->impl = impl;
  ret = oil_fault_check_try (oil_test_check_function, test);
  if (!ret) {
    OIL_ERROR ("illegal instruction in %s", test->impl->name);
    test->profile_ave = 0;
    test->profile_std = 0;

    return 0;
  }

  x = 0;
  n = 0;
  for(i=0;i<OIL_ARG_LAST;i++){
    OilParameter *p = &test->params[i];

    if (p->is_pointer) {
      if (p->direction == 'i' || p->direction == 'd') {
        x += check_array (p->test_data + p->test_header,
            p->ref_data + p->test_header, p->type, p->pre_n, p->stride,
            p->post_n);
        n += p->pre_n * p->post_n;
        if (!check_guard (p->test_data, p->test_header, p->guard)) {
          fail = 1;
          OIL_ERROR("function %s wrote before area for parameter %s",
              test->impl->name, p->parameter_name);
        }
        if (!check_guard ((uint8_t *)p->test_data + p->size - p->test_footer,
              p->test_footer, p->guard)) {
          fail = 1;
          OIL_ERROR("function %s wrote after area for parameter %s",
              test->impl->name, p->parameter_name);
        }
        if (!check_holes (p->test_data, p->type, p->pre_n, p->stride,
              p->post_n, p->guard)) {
          fail = 1;
          OIL_ERROR("function %s wrote in interstitial area for parameter %s",
              test->impl->name, p->parameter_name);
        }
      }
    }
  }
  OIL_DEBUG("sum of absolute differences %g for %d values", x, n);
  test->sum_abs_diff = x;
  test->n_points = n;

  if (x > test->tolerance * n || fail) {
    OIL_ERROR ("function %s in class %s failed check (%g > %g) || (outside=%d)",
        test->impl->name, test->klass->name, x, test->tolerance * n, fail);
    return 0;
  }

  return 1;
}

/**
 * oil_test_cleanup
 * @test: the OilTest
 *
 * Cleans up @test.
 *
 * FIXME: needs work
 */
 #ifdef __SYMBIAN32__
 EXPORT_C
#endif
void
oil_test_cleanup (OilTest *test)
{
  OilParameter *params = test->params;

  /* src1 */
  if(params[OIL_ARG_SRC1].type) {
    if (!params[OIL_ARG_SSTR1].type) {
      params[OIL_ARG_SSTR1].value = oil_type_sizeof (params[OIL_ARG_SRC1].type);
    }
  }

  /* src2 */
  if(params[OIL_ARG_SRC2].type) {
    if (!params[OIL_ARG_SSTR2].type) {
      params[OIL_ARG_SSTR2].value = oil_type_sizeof (params[OIL_ARG_SRC2].type);
    }
  }

  /* src3 */
  if(params[OIL_ARG_SRC3].type) {
    if (!params[OIL_ARG_SSTR3].type) {
      params[OIL_ARG_SSTR3].value = oil_type_sizeof (params[OIL_ARG_SRC3].type);
    }
  }

  /* dest1 */
  if(params[OIL_ARG_DEST1].type) {
    if (!params[OIL_ARG_DSTR1].type) {
      params[OIL_ARG_DSTR1].value = oil_type_sizeof (params[OIL_ARG_DEST1].type);
    }
  }

  /* dest2 */
  if(params[OIL_ARG_DEST2].type) {
    if (!params[OIL_ARG_DSTR2].type) {
      params[OIL_ARG_DSTR2].value = oil_type_sizeof (params[OIL_ARG_DEST2].type);
    }
  }

  /* dest3 */
  if(params[OIL_ARG_DEST3].type) {
    if (!params[OIL_ARG_DSTR3].type) {
      params[OIL_ARG_DSTR3].value = oil_type_sizeof (params[OIL_ARG_DEST3].type);
    }
  }

}


static void
init_parameter (OilTest *test, OilParameter *p, OilParameter *ps)
{
  if (!p->type) return;

  p->pre_n = p->prestride_length;
  if (p->prestride_var == 1) {
    p->pre_n += test->n;
  }
  if (p->prestride_var == 2) {
    p->pre_n += test->m;
  }

  if (ps->value) {
    p->stride = ps->value;
  } else {
    p->stride = oil_type_sizeof (p->type) * p->pre_n;
    ps->value = p->stride;
  }

  p->post_n = p->poststride_length;
  if (p->poststride_var == 1) {
    p->post_n += test->n;
  }
  if (p->poststride_var == 2) {
    p->post_n += test->m;
  }

  p->size = p->stride * p->post_n + p->test_header + p->test_footer;
  p->guard = oil_rand_u8();

  if (p->direction == 'i' || p->direction == 's') {
    if (p->src_data) free (p->src_data);

    OIL_DEBUG("allocating %d bytes for src_data for %s", p->size, p->parameter_name);
    p->src_data = malloc (p->size);
    memset (p->src_data, p->guard, p->size);
    fill_array (p->src_data + p->test_header, p->type, p->pre_n, p->stride, p->post_n);
  }

  if (p->direction == 'i' || p->direction == 'd') {
    if (p->ref_data) free (p->ref_data);
    p->ref_data = malloc (p->size);
    memset (p->ref_data, p->guard, p->size);
    OIL_DEBUG("allocating %d bytes for ref_data and test_data for %s", p->size, p->parameter_name);

    if (p->test_data) free (p->test_data);
    p->test_data = malloc (p->size);
    memset (p->test_data, p->guard, p->size);
  }
}

static void
oil_test_init_params (OilTest *test)
{
  init_parameter (test, &test->params[OIL_ARG_DEST1],
      &test->params[OIL_ARG_DSTR1]);
  init_parameter (test, &test->params[OIL_ARG_DEST2],
      &test->params[OIL_ARG_DSTR2]);
  init_parameter (test, &test->params[OIL_ARG_DEST3],
      &test->params[OIL_ARG_DSTR3]);

  init_parameter (test, &test->params[OIL_ARG_SRC1],
      &test->params[OIL_ARG_SSTR1]);
  init_parameter (test, &test->params[OIL_ARG_SRC2],
      &test->params[OIL_ARG_SSTR2]);
  init_parameter (test, &test->params[OIL_ARG_SRC3],
      &test->params[OIL_ARG_SSTR3]);
  init_parameter (test, &test->params[OIL_ARG_SRC4],
      &test->params[OIL_ARG_SSTR4]);
  init_parameter (test, &test->params[OIL_ARG_SRC5],
      &test->params[OIL_ARG_SSTR5]);

  init_parameter (test, &test->params[OIL_ARG_INPLACE1],
      &test->params[OIL_ARG_ISTR1]);
  init_parameter (test, &test->params[OIL_ARG_INPLACE2],
      &test->params[OIL_ARG_ISTR2]);
}

static void
fill_array (void *data, OilType type, int pre_n, int stride, int post_n)
{
  int i;

#define FILL(type,func) do {\
  for(i=0;i<post_n;i++){ \
    func (OIL_OFFSET(data, i*stride), pre_n); \
  } \
}while(0)

  switch (type) {
    case OIL_TYPE_s8p:
      FILL(int8_t,oil_random_s8);
      break;
    case OIL_TYPE_u8p:
      FILL(uint8_t,oil_random_u8);
      break;
    case OIL_TYPE_s16p:
      FILL(int16_t,oil_random_s16);
      break;
    case OIL_TYPE_u16p:
      FILL(uint16_t,oil_random_u16);
      break;
    case OIL_TYPE_s32p:
      FILL(int32_t,oil_random_s32);
      break;
    case OIL_TYPE_u32p:
      FILL(uint32_t,oil_random_u32);
      break;
    case OIL_TYPE_s64p:
      FILL(int64_t,oil_random_s64);
      break;
    case OIL_TYPE_u64p:
      FILL(uint64_t,oil_random_u64);
      break;
    case OIL_TYPE_f32p:
      FILL(float,oil_random_f32);
      break;
    case OIL_TYPE_f64p:
      FILL(double,oil_random_f64);
      break;
    default:
      OIL_ERROR ("should not be reached (type == %d)", type);
      return;
      break;
  }
}

static double
check_array (void *data, void *ref, OilType type, int pre_n, int stride, int post_n)
{
  int i;
  int j;
  int s2 = oil_type_sizeof (type);
  double x = 0;

#if 0
  OIL_ERROR ("check array pre_n=%d stride=%d post_n=%d",
      pre_n, stride, post_n);
#endif

#define CHECK(type) do {\
  for(i=0;i<post_n;i++){ \
    for(j=0;j<pre_n;j++){ \
      x += fabs((double)OIL_GET(data, i*stride + j*s2, type) - \
          (double)OIL_GET(ref, i*stride + j*s2, type)); \
    } \
  } \
}while(0)

  switch (type) {
    case OIL_TYPE_s8p:
      CHECK(int8_t);
      break;
    case OIL_TYPE_u8p:
      CHECK(uint8_t);
      break;
    case OIL_TYPE_s16p:
      CHECK(int16_t);
      break;
    case OIL_TYPE_u16p:
      CHECK(uint16_t);
      break;
    case OIL_TYPE_s32p:
      CHECK(int32_t);
      break;
    case OIL_TYPE_u32p:
      CHECK(uint32_t);
      break;
    case OIL_TYPE_s64p:
      CHECK(int64_t);
      break;
    case OIL_TYPE_u64p:
      CHECK(uint64_t);
      break;
    case OIL_TYPE_f32p:
      CHECK(float);
      break;
    case OIL_TYPE_f64p:
      CHECK(double);
      break;
    default:
      OIL_ERROR ("should not be reached (type == %d)", type);
      return 1e9;
      break;
  }
  return x;
}

static int
check_holes (void *data, OilType type, int pre_n, int stride, int post_n,
    int guard)
{
  int i;
  int chunk_size;
  int hole_size;

  chunk_size = pre_n * oil_type_sizeof (type);
  hole_size = stride - chunk_size;
  if (hole_size == 0) {
    return 1;
  }

  for(i=0;i<post_n;i++){
    if (!check_guard (OIL_OFFSET(data, stride * i + chunk_size),
        hole_size, guard)) {
      return 0;
    }
  }

  return 1;
}

#ifdef __SYMBIAN32__
 EXPORT_C
#endif
void *
oil_test_get_source_data (OilTest *test, OilArgType arg_type)
{
  uint8_t *ptr;
 
  ptr = test->params[arg_type].src_data;
  ptr += test->params[arg_type].test_header;

  return ptr;
}

#ifdef __SYMBIAN32__
 EXPORT_C
#endif
int
oil_test_get_arg_pre_n (OilTest *test, OilArgType arg_type)
{
  return test->params[arg_type].pre_n;
}

#ifdef __SYMBIAN32__
 EXPORT_C
#endif
int
oil_test_get_arg_post_n (OilTest *test, OilArgType arg_type)
{
  return test->params[arg_type].post_n;
}

#ifdef __SYMBIAN32__
 EXPORT_C
#endif
int
oil_test_get_arg_stride (OilTest *test, OilArgType arg_type)
{
  return test->params[arg_type].stride;
}

#ifdef __SYMBIAN32__
 EXPORT_C
#endif
int
oil_test_get_value (OilTest *test, OilArgType arg_type)
{
  return test->params[arg_type].value;
}
