/*
 * LIBOIL - Library of Optimized Inner Loops
 * Copyright (c) 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 <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#include <liboil/liboilprototype.h>
#include <liboil/liboildebug.h>

/**
 * SECTION:liboilprototype
 * @title:OilPrototype
 * @short_description:
 * Functions to manipulate prototype information.
 *
 */

/**
 * SECTION:liboilparameter
 * @title: OilParameter
 * @short_description:
 * Functions to manipulate prototype parameter information.
 *
 */

#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif

typedef struct _OilString OilString;
struct _OilString {
  int len;
  int alloc_len;
  char *data;
};

static OilType oil_type_from_string (const char *s, int ptr);

static OilString *oil_string_new (const char *a);
static void oil_string_append (OilString *s, const char *a);
static char * oil_string_free (OilString *s, int free_seg);

static char * parse_string (const char *s, const char **endptr);

static int oil_prototype_check_sanity (OilPrototype *proto);

/**
 * oil_prototype_append_param:
 * @proto: the OilPrototype
 * @param: an OilParameter
 *
 * Appends @param to the list of parameters contained in @proto.  The
 * contents of @param are copied.
 */
#ifdef __SYMBIAN32__
EXPORT_C  
#endif 
void
oil_prototype_append_param (OilPrototype *proto, OilParameter *param)
{
  proto->n_params++;
  proto->params = realloc(proto->params,
      sizeof(OilParameter) * proto->n_params);
  proto->params[proto->n_params - 1] = *param;
}

/**
 * oil_prototype_to_string:
 * @proto: the OilPrototype
 *
 * Converts a prototype into the corresponding C style declaration.
 *
 * Returns: a string that should be freed using free() when it is no
 * longer needed.
 */
#ifdef __SYMBIAN32__
EXPORT_C 
#endif 
char *
oil_prototype_to_string (OilPrototype *proto)
{
  OilString *string;
  OilParameter *param;
  int i;

  string = oil_string_new ("");

  for(i=0;i<proto->n_params;i++){
    param = proto->params + i;

    if (param->parameter_type == OIL_ARG_SRC1 ||
        param->parameter_type == OIL_ARG_SRC2 ||
        param->parameter_type == OIL_ARG_SRC3 ||
        param->parameter_type == OIL_ARG_SRC4 ||
        param->parameter_type == OIL_ARG_SRC5) {
      oil_string_append (string, "const ");
    }

    if (param->type != OIL_TYPE_UNKNOWN) {
      oil_string_append (string, oil_type_name (param->type));
    } else {
      oil_string_append (string, param->type_name);
    }
    oil_string_append (string, " ");
    oil_string_append (string, param->parameter_name);

    if(i<proto->n_params-1){
      oil_string_append (string, ", ");
    }
  }

  return oil_string_free (string, FALSE);
}

/**
 * oil_prototype_to_arg_string:
 * @proto: the OilPrototype
 *
 * Converts a prototype into the corresponding C argument list.
 *
 * Returns: a string that should be freed using free() when it is no
 * longer needed.
 */
#ifdef __SYMBIAN32__
EXPORT_C  
#endif
char *
oil_prototype_to_arg_string (OilPrototype *proto)
{
  OilString *string;
  OilParameter *param;
  int i;

  string = oil_string_new ("");

  for(i=0;i<proto->n_params;i++){
    param = proto->params + i;

    oil_string_append (string, param->parameter_name);

    if(i<proto->n_params-1){
      oil_string_append (string, ", ");
    }
  }

  return oil_string_free (string, FALSE);
}

/**
 * oil_prototype_from_string:
 * @s: a string
 *
 * Converts the string @s containing C prototype that follows
 * Liboil parameter naming rules into a OilPrototype.  If the
 * string cannot be converted, NULL is returned.
 *
 * Returns: the OilPrototype.  When it is no longer needed, free
 * the prototype using oil_prototype_free().
 */
#ifdef __SYMBIAN32__
EXPORT_C  
#endif 
OilPrototype *
oil_prototype_from_string (const char *s)
{
  OilPrototype *proto;
  OilParameter param = { 0 };
  char *type_name;
  char *parameter_name;
  int ptr;
  int order = 0;

  proto = malloc (sizeof(OilPrototype));
  memset (proto, 0, sizeof(OilPrototype));

  while (isspace((int)*s))s++;
  while (*s) {
    type_name = parse_string (s, &s);

    while (isspace((int)*s))s++;

    ptr = FALSE;
    if(s[0] == '*'){
      ptr = TRUE;
      s++;
    }
    while (isspace((int)*s))s++;
    parameter_name = parse_string (s, &s);

    while (isspace((int)*s))s++;

    if(s[0] == ','){
      s++;
    }
    while (isspace((int)*s))s++;

    param.type = oil_type_from_string (type_name, ptr);
    param.type_name = type_name;
    param.parameter_name = parameter_name;
    oil_param_from_string (&param, parameter_name);
    param.order = order;
    oil_prototype_append_param (proto, &param);

    if (param.type == OIL_TYPE_UNKNOWN ||
        param.parameter_type == OIL_ARG_UNKNOWN) {
      OIL_ERROR ("bad type (%s) or parameter (%s)", type_name, parameter_name);
      oil_prototype_free (proto);
      return NULL;
    }

    order++;
  }

  if (!oil_prototype_check_sanity (proto)) {
    OIL_ERROR ("prototype failed sanity check");
    oil_prototype_free (proto);
    return NULL;
  }

  return proto;
}

static int
oil_prototype_check_sanity (OilPrototype *proto)
{
  int i;
  int has_n;

  has_n = 0;
  for (i=0;i<proto->n_params;i++){
    if (proto->params[i].parameter_type == OIL_ARG_N) {
      has_n = 1;
    }
  }

  if (has_n == 0) {
    /* check that no parameter depends on n */
    for(i=0;i<proto->n_params;i++){
      if (proto->params[i].is_stride) continue;
      if (proto->params[i].parameter_type == OIL_ARG_N) continue;
      if (proto->params[i].parameter_type == OIL_ARG_M) continue;

      if (proto->params[i].prestride_var == 1) return 0;
      if (proto->params[i].poststride_var == 1) return 0;
    }
  }

  return 1;
}

static char * parse_string (const char *s, const char **endptr)
{
  const char *s0;

  s0 = s;
  while(isalnum((int)*s) || *s=='_') {
    s++;
  }
  *endptr = s;

  return (char *)_strndup(s0, s - s0);
}

/**
 * oil_prototype_free:
 * @proto: the OilPrototype
 *
 * Frees memory associated with @proto.
 */
 #ifdef __SYMBIAN32__
EXPORT_C  
#endif 
void
oil_prototype_free (OilPrototype *proto)
{
  int i;

  if (proto->params) {
    for(i=0;i<proto->n_params;i++) {
      if (proto->params[i].parameter_name) {
        free (proto->params[i].parameter_name);
      }
      if (proto->params[i].type_name) {
        free (proto->params[i].type_name);
      }
    }
    free (proto->params);
  }
  free (proto);
}



/* OilString */

static OilString *
oil_string_new (const char *a)
{
  OilString *s;

  s = malloc (sizeof(OilString));
  memset (s, 0, sizeof(OilString));

  if (a) {
    oil_string_append (s, a);
  }

  return s;

}

static void
oil_string_append (OilString *s, const char *a)
{
  int len = strlen (a);

  if (s->len + len + 1 > s->alloc_len) {
    s->alloc_len = s->len + len + 1;
    s->data = realloc (s->data, s->alloc_len);
  }
  memcpy (s->data + s->len, a, len + 1);
  s->len += len;
}

static char *
oil_string_free (OilString *s, int free_seg)
{
  char *data = s->data;
  free(s);
  if (free_seg) {
    free (data);
    return NULL;
  }
  return data;
}


/**
 * oil_type_sizeof:
 * @type: the OilType
 *
 * Looks up the size of the OilType represented by @type.
 *
 * Returns: the size of @type
 */
#ifdef __SYMBIAN32__
EXPORT_C
#endif 
int oil_type_sizeof (OilType type)
{
  switch (type) {
    case OIL_TYPE_UNKNOWN:
    case OIL_TYPE_INT:
      return 0;
    case OIL_TYPE_s8:
    case OIL_TYPE_u8:
    case OIL_TYPE_s8p:
    case OIL_TYPE_u8p:
      return 1;
    case OIL_TYPE_s16:
    case OIL_TYPE_u16:
    case OIL_TYPE_s16p:
    case OIL_TYPE_u16p:
      return 2;
    case OIL_TYPE_s32:
    case OIL_TYPE_u32:
    case OIL_TYPE_f32:
    case OIL_TYPE_s32p:
    case OIL_TYPE_u32p:
    case OIL_TYPE_f32p:
      return 4;
    case OIL_TYPE_s64:
    case OIL_TYPE_u64:
    case OIL_TYPE_f64:
    case OIL_TYPE_s64p:
    case OIL_TYPE_u64p:
    case OIL_TYPE_f64p:
      return 8;
  }
  return 0;
}

static const char *oil_type_names [] = {
  "unknown",
  "int",
  "int8_t",
  "uint8_t",
  "int16_t",
  "uint16_t",
  "int32_t",
  "uint32_t",
  "int64_t",
  "uint64_t",
  "float",
  "double",
  "int8_t *",
  "uint8_t *",
  "int16_t *",
  "uint16_t *",
  "int32_t *",
  "uint32_t *",
  "int64_t *",
  "uint64_t *",
  "float *",
  "double *"
};
static const char *oil_type_names_2 [] = {
  "int8_t",
  "uint8_t",
  "int16_t",
  "uint16_t",
  "int32_t",
  "uint32_t",
  "int64_t",
  "uint64_t",
  "float",
  "double"
};
static const char *oil_type_names_3 [] = {
  "oil_type_s8",
  "oil_type_u8",
  "oil_type_s16",
  "oil_type_u16",
  "oil_type_s32",
  "oil_type_u32",
  "oil_type_s64",
  "oil_type_u64",
  "oil_type_f32",
  "oil_type_f64"
};
#define ARRAY_LENGTH(x) (sizeof(x)/sizeof(x[0]))

/**
 * oil_type_name:
 * @type: an OilType
 *
 * Looks up the name of the OilType given by @type.
 *
 * Returns: a string containing the name of the OilType @type.
 */
 #ifdef __SYMBIAN32__
EXPORT_C
#endif
const char * oil_type_name (OilType type)
{

  if (type < 0 || type >= ARRAY_LENGTH(oil_type_names))
    return "unknown";

  return oil_type_names[type];
}

#ifdef __SYMBIAN32__
#ifdef __WINSCW__
#pragma suppress_warnings on 
#endif//__WINSCW__
#ifdef __ARMCC__
#pragma diag_remark 188
#endif//__ARMCC__
#endif//__SYMBIAN32__
static OilType oil_type_from_string (const char *s, int ptr)
{
  int i;

  if (ptr) {
    for(i=0;i<ARRAY_LENGTH(oil_type_names_2);i++){
      if (strcmp(s,oil_type_names_2[i]) == 0){
        return OIL_TYPE_s8p + i;
      }
    }
    for(i=0;i<ARRAY_LENGTH(oil_type_names_3);i++){
      if (strcmp(s,oil_type_names_3[i]) == 0){
        return OIL_TYPE_s8p + i;
      }
    }
  } else {
    if (strcmp (s,"int") == 0) {
      return OIL_TYPE_INT;
    }
    for(i=0;i<ARRAY_LENGTH(oil_type_names_2);i++){
      if (strcmp(s,oil_type_names_2[i]) == 0){
        return OIL_TYPE_s8 + i;
      }
    }
    for(i=0;i<ARRAY_LENGTH(oil_type_names_3);i++){
      if (strcmp(s,oil_type_names_3[i]) == 0){
        return OIL_TYPE_s8 + i;
      }
    }
  }
  return OIL_TYPE_UNKNOWN;
}

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

/**
 * oil_arg_type_name:
 * @type: an OilArgType
 *
 * Looks up the name of the OilArgType specified by @type.
 *
 * Returns: a string containing the name
 */
#ifdef __SYMBIAN32__
EXPORT_C
#endif
const char * oil_arg_type_name (OilArgType type)
{
  switch (type) {
    case OIL_ARG_N:
      return "n";
    case OIL_ARG_M:
      return "m";
    case OIL_ARG_DEST1:
      return "dest1";
    case OIL_ARG_DSTR1:
      return "dstr1";
    case OIL_ARG_DEST2:
      return "dest2";
    case OIL_ARG_DSTR2:
      return "dstr2";
    case OIL_ARG_DEST3:
      return "dest3";
    case OIL_ARG_DSTR3:
      return "dstr3";
    case OIL_ARG_SRC1:
      return "src1";
    case OIL_ARG_SSTR1:
      return "sstr1";
    case OIL_ARG_SRC2:
      return "src2";
    case OIL_ARG_SSTR2:
      return "sstr2";
    case OIL_ARG_SRC3:
      return "src3";
    case OIL_ARG_SSTR3:
      return "sstr3";
    case OIL_ARG_SRC4:
      return "src4";
    case OIL_ARG_SSTR4:
      return "sstr4";
    case OIL_ARG_SRC5:
      return "src5";
    case OIL_ARG_SSTR5:
      return "sstr5";
    case OIL_ARG_INPLACE1:
      return "inplace1";
    case OIL_ARG_ISTR1:
      return "istr1";
    case OIL_ARG_INPLACE2:
      return "inplace2";
    case OIL_ARG_ISTR2:
      return "istr2";
    default:
      return "INVALID";
  }
}

static const
struct {
  char *from;
  char *to;
} arg_aliases[] = {
  { "dest", "d1" },
  { "dest1", "d1" },
  { "dstr", "ds1" },
  { "dstr1", "ds1" },
  { "dest2", "d2" },
  { "dstr2", "ds2" },
  { "dest3", "d3" },
  { "dstr3", "ds3" },
  { "src", "s1" },
  { "src1", "s1" },
  { "sstr", "ss1" },
  { "sstr1", "ss1" },
  { "src2", "s2" },
  { "sstr2", "ss2" },
  { "src3", "s3" },
  { "sstr3", "ss3" },
  { NULL, NULL }
};

static const
struct {
  OilArgType type;
  int direction;
  int is_stride;
  int index;
} arg_types[] = {
  { OIL_ARG_N, 'n', 0, 0 },
  { OIL_ARG_M, 'm', 0, 0 },
  { OIL_ARG_DEST1, 'd', 0, 1 },
  { OIL_ARG_DSTR1, 'd', 1, 1 },
  { OIL_ARG_DEST2, 'd', 0, 2 },
  { OIL_ARG_DSTR2, 'd', 1, 2 },
  { OIL_ARG_DEST3, 'd', 0, 3 },
  { OIL_ARG_DSTR3, 'd', 1, 3 },
  { OIL_ARG_SRC1, 's', 0, 1 },
  { OIL_ARG_SSTR1, 's', 1, 1 },
  { OIL_ARG_SRC2, 's', 0, 2 },
  { OIL_ARG_SSTR2, 's', 1, 2 },
  { OIL_ARG_SRC3, 's', 0, 3 },
  { OIL_ARG_SSTR3, 's', 1, 3 },
  { OIL_ARG_SRC4, 's', 0, 4 },
  { OIL_ARG_SSTR4, 's', 1, 4 },
  { OIL_ARG_SRC5, 's', 0, 5 },
  { OIL_ARG_SSTR5, 's', 1, 5 },
  { OIL_ARG_INPLACE1, 'i', 0, 1 },
  { OIL_ARG_ISTR1, 'i', 1, 1 },
  { OIL_ARG_INPLACE2, 'i', 0, 2 },
  { OIL_ARG_ISTR2, 'i', 1, 2 },
#ifndef __SYMBIAN32__
  { 0, 0, 0, 0 }
#else
  { OIL_ARG_UNKNOWN, 0, 0, 0 }
#endif//__SYMBIAN32__
};

#ifdef __SYMBIAN32__
EXPORT_C 
#endif 
int
oil_param_from_string (OilParameter *p, char *s)
{
  int i;

  p->parameter_type = OIL_ARG_UNKNOWN;

  if (s[0] == 'n' && s[1] == 0) {
    p->direction = *s;
    p->is_stride = 0;
    p->is_pointer = 0;
    p->parameter_type = OIL_ARG_N;
    return 1;
  }
  if (s[0] == 'm' && s[1] == 0) {
    p->direction = *s;
    p->is_stride = 0;
    p->is_pointer = 0;
    p->parameter_type = OIL_ARG_M;
    return 1;
  }

  for(i=0;arg_aliases[i].from;i++){
    if (strcmp (s,arg_aliases[i].from) == 0) {
      s = arg_aliases[i].to;
      break;
    }
  }

  p->direction = *s;
  switch (*s) {
    case 'i':
      break;
    case 's':
      break;
    case 'd':
      break;
    default:
      return 0;
  }
  s++;

  if (*s == 's') {
    p->is_stride = 1;
    p->is_pointer = 0;
    s++;
  } else {
    p->is_stride = 0;
    p->is_pointer = 1;
  }

  if (isdigit ((int)*s)) {
    p->index = *s - '0';
    s++;
  } else {
    p->index = 1;
  }

  if (!p->is_stride && *s == '_') {
    int length;
    int var;

    s++;

    if (isdigit ((int)*s)) {
      length = strtoul (s, &s, 10);
      var = 0;
    } else if (*s == 'n' || *s == 'm') {
      var = (*s == 'n') ? 1 : 2;
      s++;
      if (*s == 'p') {
        s++;
        length = strtoul (s, &s, 10);
      } else {
        length = 0;
      }
    } else {
      return 0;
    }

    if (*s == 'x') {
      s++;
      p->prestride_length = length;
      p->prestride_var = var;

      if (isdigit ((int)*s)) {
        p->poststride_length = strtoul (s, &s, 10);
        p->poststride_var = 0;
      } else if (*s == 'n' || *s == 'm') {
        p->poststride_var = (*s == 'n') ? 1 : 2;
        s++;
        if (*s == 'p') {
          s++;
          p->poststride_length = strtoul (s, &s, 10);
        } else {
          p->poststride_length = 0;
        }
      } else {
        return 0;
      }

    } else {
      p->poststride_length = length;
      p->poststride_var = var;
      p->prestride_length = 1;
      p->prestride_var = 0;
    }

  } else {
    p->poststride_length = 0;
    p->poststride_var = 1;
    p->prestride_length = 1;
    p->prestride_var = 0;
  }
  if (*s != 0) {
    return 0;
  }

  p->parameter_type = OIL_ARG_UNKNOWN;
  for (i=0;arg_types[i].type != OIL_ARG_UNKNOWN; i++) {
    if (p->direction == arg_types[i].direction &&
        p->is_stride == arg_types[i].is_stride &&
        p->index == arg_types[i].index) {
      p->parameter_type = arg_types[i].type;
      break;
    }
  }

  if (p->parameter_type == OIL_ARG_UNKNOWN) {
    return 0;
  }

  return 1;
}

#ifdef __SYMBIAN32__
EXPORT_C
#endif 
void *oil_param_get_source_data (OilParameter *param)
{
  uint8_t *ptr;
 
  ptr = param->src_data;
  ptr += param->test_header;

  return ptr;
}

