/******************************************************************************
#
#  %W%  %G% CSS
#
#  "pyspec" Release %R%
#
#  Copyright (c) 2012,2014,2015,2016,2020
#  by Certified Scientific Software.
#  All rights reserved.
#
#  Permission is hereby granted, free of charge, to any person obtaining a
#  copy of this software ("pyspec") and associated documentation files (the
#  "Software"), to deal in the Software without restriction, including
#  without limitation the rights to use, copy, modify, merge, publish,
#  distribute, sublicense, and/or sell copies of the Software, and to
#  permit persons to whom the Software is furnished to do so, subject to
#  the following conditions:
#
#  The above copyright notice and this permission notice shall be included
#  in all copies or substantial portions of the Software.
#
#  Neither the name of the copyright holder nor the names of its contributors
#  may be used to endorse or promote products derived from this software
#  without specific prior written permission.
#
#     * The software is provided "as is", without warranty of any   *
#     * kind, express or implied, including but not limited to the  *
#     * warranties of merchantability, fitness for a particular     *
#     * purpose and noninfringement.  In no event shall the authors *
#     * or copyright holders be liable for any claim, damages or    *
#     * other liability, whether in an action of contract, tort     *
#     * or otherwise, arising from, out of or in connection with    *
#     * the software or the use of other dealings in the software.  *
#
******************************************************************************/

#include <sps.h>
#include <Python.h>

struct module_state {
     PyObject *error;
};

#if PY_MAJOR_VERSION >= 3
#define GETSTATE(m) ((struct module_state*) PyModule_GetState(m))
#else
#define GETSTATE(m) (&_state)
static struct module_state _state;
#endif

void        initdatashm(void);
static void datashm_cleanup(void);

static char datashm_type2py (int t)
{
  switch (t) {
     case SPS_SHORT:  return('h');
     case SPS_USHORT: return('H');
     case SPS_INT:    return('i');
     case SPS_UINT:   return('I');
     case SPS_LONG:   return('k');
     case SPS_ULONG:  return('K');
     case SPS_LONG64: return('L');
     case SPS_DOUBLE: return('d');
     case SPS_FLOAT:  return('f');
     case SPS_CHAR:   return('b');
     case SPS_UCHAR:  return('B');

     // returning n for non supported types

     case SPS_STRING: 
     case SPS_ULONG64: 
     default:         
           return('n');
  }
  return('n');
}

static PyObject *buildEntry(char ptype, void **ptr) 
{
   PyObject *entry;

   double *dptr;
   float  *fptr;
   long   *lptr;
   long long   *llptr;
   int    *iptr;
   unsigned int    *uiptr;
   short  *sptr;
   unsigned short  *usptr;
   unsigned long  *ulptr;
   char  *bptr;
   unsigned char  *ubptr;

   if (ptype == 'd') { 
        dptr = *ptr;
        entry = Py_BuildValue("d", *dptr);
        dptr++; *ptr = dptr;
   } else if (ptype == 'f') {  
        fptr = *ptr;
        entry = Py_BuildValue( "f", *fptr);
        fptr++; *ptr = fptr;
   } else if (ptype == 'i') {  
        iptr = *ptr;
        entry = Py_BuildValue( "i", *iptr); 
        iptr++; *ptr = iptr;
   } else if (ptype == 'I') { 
        uiptr = *ptr;
        entry = Py_BuildValue( "I", *uiptr); 
        uiptr++; *ptr = uiptr;
   } else if (ptype == 'k') { 
        lptr = *ptr;
        entry = Py_BuildValue( "k", *lptr); 
        lptr++; *ptr = lptr;
   } else if (ptype == 'K') { 
        ulptr = *ptr;
        entry = Py_BuildValue( "K", *ulptr); 
        ulptr++; *ptr = ulptr;
   } else if (ptype == 'L') { 
        llptr = *ptr;
        entry = Py_BuildValue( "L", *llptr); 
        llptr++; *ptr = llptr;
   } else if (ptype == 'h') { 
        sptr = *ptr;
        entry = Py_BuildValue( "h", *sptr); 
        sptr++; *ptr = sptr;
   } else if (ptype == 'H') { 
        usptr = *ptr;
        entry = Py_BuildValue( "H", *usptr); 
        usptr++;  *ptr = usptr;
   } else if (ptype == 'b') { 
        bptr = *ptr;
        entry = Py_BuildValue( "b", *bptr); 
        bptr++;  *ptr = bptr;
   } else if (ptype == 'B') { 
        ubptr = *ptr;
        entry = Py_BuildValue( "B", *ubptr); 
        ubptr++;  *ptr = ubptr;
   } else {
        entry = Py_None;
   }

   return entry;
}

static PyObject * datashm_getarraylist(PyObject *self, PyObject *args)
{
  char *spec_version=NULL;
  int i;
  char *array;
  PyObject *list, *string;
  
  if (!PyArg_ParseTuple(args, "|s", &spec_version)) {
    return NULL;
  }

  list = PyList_New(0);
  for (i=0; (array = SPS_GetNextArray (spec_version,i)) ; i++) {
#if PY_MAJOR_VERSION >= 3
  	string = PyUnicode_FromString(array);
#else
        string = PyString_FromString(array);
#endif
    PyList_Append (list, string);
    Py_DECREF(string);
  }

  return list;
}    

static PyObject * datashm_getspeclist(PyObject *self, PyObject *args)
{
  char *spec_version;
  int i;
  PyObject *list, *string;

  if (!PyArg_ParseTuple(args, "")) {
    return NULL;
  }
  
  list = PyList_New(0);
  for (i=0; (spec_version = SPS_GetNextSpec (i)) ; i++) {
#if PY_MAJOR_VERSION >= 3
  	string = PyUnicode_FromString(spec_version);
#else
        string = PyString_FromString(spec_version);
#endif
    PyList_Append (list, string);
    Py_DECREF(string);
  }

  return list;
}    

static PyObject *datashm_isupdated(PyObject *self, PyObject *args)
{
  char *spec_version, *array_name;

  if (!PyArg_ParseTuple(args, "ss", &spec_version, &array_name)) {
    return NULL;
  }

#if PY_MAJOR_VERSION >= 3
  return PyLong_FromLong(SPS_IsUpdated(spec_version, array_name));
#else
  return PyInt_FromLong(SPS_IsUpdated(spec_version, array_name));
#endif

}    

static PyObject *datashm_getinfo(PyObject *self, PyObject *args)
{
  char *spec_version, *array_name, *ret;

  if (!PyArg_ParseTuple(args, "ss", &spec_version, &array_name)) {
    return NULL;
  }

  ret = SPS_GetInfoString(spec_version, array_name);

  if (ret) { 
#if PY_MAJOR_VERSION >= 3
  	return PyUnicode_FromString(ret);
#else
  	return PyString_FromString(ret);
#endif
  } else {
        struct module_state *st = GETSTATE(self);
        PyErr_SetString(st->error, "Array Info cannot be read");
  	return NULL;
  }
}    

static PyObject *datashm_getmetadata(PyObject *self, PyObject *args)
{
  char *spec_version, *array_name,  *ret;
  u32_t length;

  if (!PyArg_ParseTuple(args, "ss", &spec_version, &array_name)) {
    return NULL;
  }

  ret = SPS_GetMetaData(spec_version, array_name, &length);

  if (ret) { 
#if PY_MAJOR_VERSION >= 3
  	return PyUnicode_FromString(ret);
#else
  	return PyString_FromString(ret);
#endif
  } else {
        struct module_state *st = GETSTATE(self);
        PyErr_SetString(st->error, "Array metadata cannot be read");
  	return NULL;
  }
}    

static PyObject *datashm_arrayinfo(PyObject *self, PyObject *args)
{
  char *spec_version, *array_name;
  int rows, cols, type, flag;
  int j;

  PyObject *output,*entry;

  if (!PyArg_ParseTuple(args, "ss", &spec_version, &array_name)) {
    struct module_state *st = GETSTATE(self);
    PyErr_SetString(st->error, "Cannot find array.");
    return NULL;
  }

  if (SPS_GetArrayInfo(spec_version, array_name, &rows, &cols, &type, &flag)) {
    struct module_state *st = GETSTATE(self);
    PyErr_Format(st->error,
                 "Error getting array info for %s/%s.", \
                 spec_version, array_name
                 );
    return NULL;
  }

  j=0;
  output = PyList_New(4);
  
  entry = Py_BuildValue("i", rows);
  PyList_SetItem( output, j, entry);j++;
  entry = Py_BuildValue("i", cols);
  PyList_SetItem( output, j, entry);j++;
  entry = Py_BuildValue("i", type);
  PyList_SetItem( output, j, entry);j++;
  entry = Py_BuildValue("i", flag);
  PyList_SetItem( output, j, entry);j++;

  return output;

}

static PyObject *datashm_getdata(PyObject *self, PyObject *args)
{
  char *spec_version, *array_name;
  int rows, cols, type, flag;
  char ptype;
  
  int write_flag = 0;

  void *data;

  PyObject *output,*entry;
  PyObject *rowlist;

  int j,k;
  void *ptr;

  if (!PyArg_ParseTuple(args, "ss", &spec_version, &array_name)) {
    return NULL;
  }

  if (SPS_GetArrayInfo(spec_version, array_name, &rows, &cols, &type, &flag)) {
    struct module_state *st = GETSTATE(self);
    PyErr_Format(st->error,
                 "Error getting array info for %s/%s.", \
                 spec_version, array_name
                 );
    return NULL;
  }
  ptype = datashm_type2py ( type );

  data = SPS_GetDataPointer(spec_version,array_name, write_flag);
  output = PyList_New(rows);

  ptr = (void *) data;

  if (ptype == 'n') { 
     struct module_state *st = GETSTATE(self);
     PyErr_SetString(st->error, "Data type not supported");
     return NULL;
  }

  for (j=0;j<rows;j++) {
      rowlist = PyList_New(cols);
      for (k=0;k<cols;k++) {
          entry = buildEntry(ptype, &ptr); 
          PyList_SetItem( rowlist, k, entry);
      }
      PyList_SetItem( output, j, rowlist);
  }
  SPS_ReturnDataPointer(data);

  return output;
}    

static PyObject *datashm_getdatarow(PyObject *self, PyObject *args)
{
  char *spec_version, *array_name;
  int rows, cols, type, flag;

  char ptype;

  int in_row;
  int nb_copied, j;


  PyObject *output, *entry;
  void *data;
  void *ptr;

  if (!PyArg_ParseTuple(args, "ssi", &spec_version, &array_name, &in_row)) {
    return NULL;
  }

  if (SPS_GetArrayInfo(spec_version, array_name, &rows, &cols, &type, &flag)) {
     struct module_state *st = GETSTATE(self);
     PyErr_SetString(st->error, "Error getting array info");
    return NULL;
  }
  ptype = datashm_type2py ( type );


  data = SPS_GetDataRow(spec_version,array_name, type, in_row, 0,&nb_copied);
  output = PyList_New(nb_copied);

  ptr = data;
  for (j=0;j<nb_copied;j++) {
      entry = buildEntry(ptype, &ptr);
      PyList_SetItem( output, j, entry);
  }

  return output;
}    

static PyObject *datashm_getdatacol(PyObject *self, PyObject *args)
{
  char *spec_version, *array_name;
  int rows, cols, type, flag;

  char ptype;

  int in_col;
  int nb_copied, j;

  PyObject *output, *entry;
  void *data;
  void *ptr;

  if (!PyArg_ParseTuple(args, "ssi", &spec_version, &array_name, &in_col)) {
    return NULL;
  }

  if (SPS_GetArrayInfo(spec_version, array_name, &rows, &cols, &type, &flag)) {
     struct module_state *st = GETSTATE(self);
     PyErr_SetString(st->error, "Error getting array info");
     return NULL;
  }
  ptype = datashm_type2py ( type );

  data = SPS_GetDataCol(spec_version,array_name, type, in_col, 0,&nb_copied);
  output = PyList_New(nb_copied);

  ptr = data;
  for (j=0;j<nb_copied;j++) {
      entry = buildEntry(ptype, &ptr);
      PyList_SetItem( output, j, entry);
  }

  return output;
}    

static void datashm_cleanup()
{
  SPS_CleanUpAll();
}

static PyMethodDef datashm_methods[] = {
  { "getspeclist",   datashm_getspeclist,METH_VARARGS},
  { "getarraylist",  datashm_getarraylist, METH_VARARGS},
  { "isupdated",     datashm_isupdated,  METH_VARARGS},
  { "getdata",       datashm_getdata,    METH_VARARGS},
  { "getdatarow",    datashm_getdatarow, METH_VARARGS},
  { "getdatacol",    datashm_getdatacol, METH_VARARGS},
  { "getinfo",       datashm_getinfo,    METH_VARARGS},
  { "getmetadata",   datashm_getmetadata, METH_VARARGS},
  { "getarrayinfo",  datashm_arrayinfo,  METH_VARARGS},
  { NULL, NULL}
};

#if PY_MAJOR_VERSION >= 3

static int datashm_traverse(PyObject *m, visitproc visit, void *arg) {
    Py_VISIT(GETSTATE(m)->error);
    return 0;
}

static int datashm_clear(PyObject *m) {
    Py_CLEAR(GETSTATE(m)->error);
    return 0;
}

static struct PyModuleDef moduledef = {
        PyModuleDef_HEAD_INIT,
        "datashm",
        NULL,
        sizeof(struct module_state),
        datashm_methods,
        NULL,
        datashm_traverse,
        datashm_clear,
        NULL
};

#define INITERROR return NULL

PyObject *
PyInit_datashm(void)

#else

#define INITERROR return

void
initdatashm(void)
#endif
{
#if PY_MAJOR_VERSION >= 3
  PyObject *module = PyModule_Create (&moduledef);
#else
  PyObject *module = Py_InitModule ("datashm", datashm_methods);
#endif

  if (module == NULL)
        INITERROR;

  struct module_state *st = GETSTATE(module);

  st->error = PyErr_NewException("datashm.Error", NULL, NULL);
  if (st->error == NULL) {
      Py_DECREF(module);
      INITERROR;
  }

  Py_AtExit(datashm_cleanup);

#if PY_MAJOR_VERSION >= 3
    return module;
#endif

}
