// @HEADER
// ***********************************************************************
//
//          PyTrilinos: Python Interfaces to Trilinos Packages
//                 Copyright (2014) Sandia Corporation
//
// Under the terms of Contract DE-AC04-94AL85000 with Sandia
// Corporation, the U.S. Government retains certain rights in this
// software.
//
// 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.
//
// 3. Neither the name of the Corporation nor the names of the
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY SANDIA CORPORATION "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 SANDIA CORPORATION OR THE
// CONTRIBUTORS 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.
//
// Questions? Contact William F. Spotz (wfspotz@sandia.gov)
//
// ***********************************************************************
// @HEADER

#ifndef PYTRILINOS_TEUCHOS_UTIL_HPP
#define PYTRILINOS_TEUCHOS_UTIL_HPP

// Include Python and NumPy headers
#define NO_IMPORT_ARRAY
#include "numpy_include.hpp"
#ifdef HAVE_INTTYPES_H
#undef HAVE_INTTYPES_H
#endif

// Include PyTrilinos::NumPy_TypeCode function
#include "PyTrilinos_NumPy_Util.hpp"

// Include Teuchos::ParameterList prototypes
#include "Teuchos_ParameterList.hpp"

// ****************************************************************** //

// The Teuchos::ParameterList class can support parameters of any
// type, but the python wrappers need a subset of concrete parameter
// types supported a-priori.  Since this subset shows up in many
// places, a set of functions are provided to handle conversion
// between PyObjects and the supported C / C++ types in a single
// place.  The following type conversions are supported:

//    Python                           C / C++
//    ---------------------      -------------
//    bool       	    <--> bool
//    int        	    <--> int
//    float      	    <--> double
//    string     	    <--> std::string
//    string     	    <--  char *
//    dict       	     --> ParameterList
//    wrapped ParameterList <--> ParameterList
//    ndarray(dtype='i')    <--> Array<int>
//    ndarray(dtype='l')    <--> Array<long>
//    ndarray(dtype='f')    <--> Array<float>
//    ndarray(dtype='d')    <--> Array<double>
//    ndarray(dtype='S')    <--> Array<std::string>
//    sequence(int)          --> Array<long>
//    sequence(float)        --> Array<double>
//    sequence(str)          --> Array<std::string>

//    Note: The python None type is unsupported and used for error
//    reporting in getPythonParameter().

// To convert the wrapped ParameterList class, certain SWIG functions
// are used that only exist within the wrapper code generated by SWIG.
// Therefore, this code only compiles correctly within the context of
// a SWIG interface file.

// ****************************************************************** //

namespace PyTrilinos
{

// ****************************************************************** //

// The following enumeration is used as an optional flag in certain
// routines to indicate how the routine is supposed to react to
// illegal parameters.

enum ResponseToIllegalParameters
{
  raiseError,
  ignore,
  storeNames
};

// ****************************************************************** //

// Copy the data in a 1D NumPy array into a Teuchos Array.  The
// Teuchos Array will be resized to accommodate the data.  The user
// must verify that the NumPy data type is the same as the template
// type a priori and that the NumPy array is 1D.

template< typename T >
void copyNumPyToTeuchosArray(PyObject * pyArray,
                             Teuchos::Array< T > & tArray)
{
  typedef typename Teuchos::Array< T >::size_type size_type;
  size_type length = PyArray_DIM((PyArrayObject*) pyArray, 0);
  tArray.resize(length);
  T * data = (T*) PyArray_DATA((PyArrayObject*) pyArray);
  for (typename Teuchos::Array< T >::iterator it = tArray.begin();
       it != tArray.end(); ++it)
    *it = *(data++);
}

// // Specialize copyNumPyToTeuchosArray() for template type bool,
// // because you cannot iterate over Teuchos::Array<bool> in the
// // standard way, due to the compressed storage specialization for this
// // container type.

// template<>
// void copyNumPyToTeuchosArray(PyObject * pyArray,
//                              Teuchos::Array< bool > & tArray)
// {
//   typedef typename Teuchos::Array< bool >::size_type size_type;
//   size_type length = PyArray_DIM((PyArrayObject*) pyArray, 0);
//   tArray.resize(length);
//   bool* data = (bool*) PyArray_DATA((PyArrayObject*) pyArray);
//   for (size_type i = 0; i < length; ++i)
//   {
//     tArray[i] = *(data++);
//   }
// }

// Specialize copyNumPyToTeuchosArray() for template type std::string,
// because the Teuchos::Array will be of type std::string and the
// NumPy array will be of type char*.

template<>
void copyNumPyToTeuchosArray(PyObject * pyArray,
                             Teuchos::Array< std::string > & tArray)
{
  typedef typename Teuchos::Array< std::string >::size_type size_type;
  size_type stride = PyArray_STRIDE((PyArrayObject*)pyArray, 0);
  size_type length = PyArray_DIM((PyArrayObject*) pyArray, 0);
  tArray.resize(length);
  char* data = (char*) PyArray_DATA((PyArrayObject*) pyArray);
  for (typename Teuchos::Array< std::string >::iterator it = tArray.begin();
       it != tArray.end(); ++it)
  {
    char temp[stride+1];
    strncpy(temp, data, stride);
    temp[stride] = 0;
    *it = std::string(temp);
    data += stride;
  }
}

// ****************************************************************** //

// Copy the data in a Teuchos::Array into a new 1D NumPy array.  If
// any errors occur, a Python error will be set and the function will
// return NULL.

template< typename T >
PyObject * copyTeuchosArrayToNumPy(Teuchos::Array< T > & tArray)
{
  int typecode = PyTrilinos::NumPy_TypeCode< T >();
  npy_intp dims[] = { tArray.size() };
  PyObject * pyArray = PyArray_SimpleNew(1, dims, typecode);
  T * data = (T*) PyArray_DATA((PyArrayObject*) pyArray);
  for (typename Teuchos::Array< T >::iterator it = tArray.begin();
       it != tArray.end(); ++it)
    *(data++) = *it;
  return pyArray;
}

// // Specialize copyTeuchosArrayToNumPy() for template type bool,
// // because you cannot iterate over Teuchos::Array<bool> in the
// // standard way, due to the compressed storage specialization for this
// // container type.

// template<>
// PyObject * copyTeuchosArrayToNumPy(Teuchos::Array< bool > & tArray)
// {
//   typedef typename Teuchos::Array< bool >::size_type size_type;
//   int typecode = PyTrilinos::NumPy_TypeCode< bool >();
//   npy_intp dims[] = { tArray.size() };
//   PyObject * pyArray = PyArray_SimpleNew(1, dims, typecode);
//   bool* data = (bool*) PyArray_DATA((PyArrayObject*) pyArray);
//   for (size_type i = 0; i < dims[0]; ++i)
//   {
//     *(data++) = tArray[i];
//   }
//   return pyArray;
// }

// Specialize copyTeuchosArrayToNumPy() for template type std::string,
// because the Teuchos::Array will be of type std::string and the
// NumPy array will be of type char*.

template<>
PyObject * copyTeuchosArrayToNumPy(Teuchos::Array< std::string > & tArray)
{
  int typecode = PyTrilinos::NumPy_TypeCode< std::string >();
  npy_intp dims[] = { tArray.size() };
  int strlen = 1;
  for (typename Teuchos::Array< std::string >::iterator it = tArray.begin();
       it != tArray.end(); ++it)
  {
    int itlen = it->size();
    if (itlen > strlen) strlen = itlen;
  }
  PyObject * pyArray =
    PyArray_New(&PyArray_Type, 1, dims, typecode, NULL, NULL, strlen, 0, NULL);
  char* data = (char*) PyArray_DATA((PyArrayObject*) pyArray);
  for (typename Teuchos::Array< std::string >::iterator it = tArray.begin();
       it != tArray.end(); ++it)
  {
    strncpy(data, it->c_str(), strlen);
    data += strlen;
  }
  return pyArray;
}

// ****************************************************************** //

// Create a new Teuchos::ArrayRCP whose data buffer points to the same
// data buffer as a given NumPy array.  The user must verify that the
// NumPy data type is the same as the template type a priori and that
// the NumPy array is 1D.

template< typename T >
Teuchos::ArrayRCP< T > convertToTeuchosArray(PyArrayObject * pyArray)
{
  typedef typename Teuchos::ArrayRCP< T >::size_type size_type;
  size_type length = PyArray_DIM(pyArray, 0);
  T * data = (T*) PyArray_DATA(pyArray);
  return Teuchos::ArrayRCP< T >(data, 0, length, false);
}

// ****************************************************************** //

// Return a NumPy array whose data buffer points to the data buffer of
// the given Teuchos::ArrayRCP.  If any errors occur, a Python error
// will be set and the function will return NULL.

template< typename T >
PyObject *
convertToNumPyArray(const Teuchos::ArrayRCP< T > & teuchosArray)
{
  int typecode = PyTrilinos::NumPy_TypeCode< T >();
  npy_intp dims[] = { teuchosArray.size() };
  PyObject * pyArray =
    PyArray_SimpleNewFromData(1, dims, typecode,
                              (void*) teuchosArray.getRawPtr());
  return pyArray;
}

// ****************************************************************** //

// The setPythonParameter() function takes a Teuchos::ParameterList, a
// string name and a python object as input.  An attempt is made to
// convert the python object to a supported C / C++ type.  If
// successful, the Teuchos::ParameterList set() method is called using
// the given name and converted object, and true is returned.  If
// unsuccessful (ie, the python object represents an unsupported
// type), false is returned.  Typically, a python error will NOT be
// set except in the case when the python object is a dictionary with
// unsupported values.

bool setPythonParameter(Teuchos::ParameterList & plist,
			const std::string      & name,
			PyObject               * value);

// **************************************************************** //

// The getPythonParameter() function is the get counterpart to
// setPythonParameter().  It takes a Teuchos::ParameterList and string
// name as input.  If the requested parameter name does not exist, a
// new reference to None is returned (a type that is guaranteed not to
// be supported by setPythonParameter()).  If the name exists and its
// type is supported, it is returned as a new reference to a python
// object.  If the name exists, but the type is not supported, NULL is
// returned, to indicate an error.  All returned python object
// pointers are new references.  This function is coded in such a way
// that the Teuchos::ParameterList "used" flags are not altered.

PyObject * getPythonParameter(const Teuchos::ParameterList & plist,
			      const std::string            & name);

// **************************************************************** //

// Function isEquivalent() is a utility for determining whether a
// python dictionary is functionally equivalent to a
// Teuchos::ParameterList.  It supports interpreting
// Teuchos::ParameterList sublists as nested python dictionaries, so
// it calls itself recursively.

bool isEquivalent(PyObject                     * dict,
		  const Teuchos::ParameterList & plist);

// **************************************************************** //

// Function updatePyDictWithParameterList() takes all of the entries
// in a Teuchos::ParameterList and updates the given python dictionary
// to reflect the same values.  If the given python object is not a
// dictionary, or any of the Teuchos::ParameterList entries are of
// unsupported type, the function returns false.

bool updatePyDictWithParameterList(PyObject                     * dict,
				   const Teuchos::ParameterList & plist,
				   ResponseToIllegalParameters    flag=raiseError);

// **************************************************************** //

// Function updateParameterListWithPyDict() takes all of the entries
// in a python dictionary and updates the given Teuchos::ParameterList
// to reflect the same values.  If the given python object is not a
// dictionary, or if any of the dictionary keys are not strings, or if
// any of the dictionary values are of unsupported type, then the
// function returns false.

bool updateParameterListWithPyDict(PyObject                  * dict,
				   Teuchos::ParameterList    & plist,
				   ResponseToIllegalParameters flag=raiseError);

// **************************************************************** //

// Function synchronizeParameters() is a function for bringing the
// given python dictionary and Teuchos::ParameterList into sync with
// each other.  If a parameter exists for both the
// Teuchos::ParameterList and the python dictionary, the
// Teuchos::ParameterList takes precedence.  If the function returns
// false, it means the given PyObject was not a dictionary or the
// Teuchos::ParameterList or python dictionary had at least one value
// of an unsupported type.

bool synchronizeParameters(PyObject                  * dict,
			   Teuchos::ParameterList    & plist,
			   ResponseToIllegalParameters flag=raiseError);

// **************************************************************** //

// Function pyDictToNewParameterList is a helper function that takes a
// python dictionary and returns a pointer to an equivalent, new
// Teuchos::ParameterList.  If dict is not a python dictionary, or dict is
// not a valid dictionary (non-string keys or unsupported value
// types) then the function returns NULL.

Teuchos::ParameterList *
pyDictToNewParameterList(PyObject                  * dict,
                         ResponseToIllegalParameters flag=raiseError);

// **************************************************************** //

// Function parameterListToNewPyDict is a helper function that takes a
// Teuchos::ParameterList and returns a pointer to an equivalent, new
// python dictionary.  If the Teuchos::ParameterList contains entries
// of invalid type, then a python error is raised and NULL is
// returned.

PyObject *
parameterListToNewPyDict(const Teuchos::ParameterList & plist,
                         ResponseToIllegalParameters    flag=raiseError);

}    // Namespace PyTrilinos

#endif // PYTRILINOS_TEUCHOS_UTIL_HPP
