/*=========================================================================

  Program:   ParaView
  Module:    vtkClientServerInterpreterPython.cxx

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "vtkPython.h"

#include "vtkClientServerInterpreter.h"

#include "vtkClientServerStream.h"
#include "vtkPythonInterpreter.h"
#include "vtkPythonUtil.h"
#include "vtkSmartPyObject.h"

namespace
{

struct PythonCSShimData
  {
    PythonCSShimData(PyObject* obj)
      {
      this->Object = obj;

      Py_INCREF(this->Object);
      }
    ~PythonCSShimData()
      {
      Py_DECREF(this->Object);
      }

    PyObject* Object;
  };

}

//----------------------------------------------------------------------------
static void FreePythonCSShimData(void* ctx)
{
    PythonCSShimData* data = (PythonCSShimData*)ctx;

    delete data;
}

#define Py_RETURN_CHECKED(pyobj) \
  do                             \
    {                            \
    if (pyobj)                   \
      {                          \
      return pyobj;              \
      }                          \
    else                         \
      {                          \
      Py_RETURN_NONE;            \
      }                          \
    } while (false)

//----------------------------------------------------------------------------
template <typename T>
static PyObject* PythonConvert(T const& value)
{
  long longValue = value;
  Py_RETURN_CHECKED(PyLong_FromLong(longValue));
}

//----------------------------------------------------------------------------
template <>
PyObject* PythonConvert(int const& value)
{
  long longValue = value;
  Py_RETURN_CHECKED(PyInt_FromLong(longValue));
}

//----------------------------------------------------------------------------
template <>
PyObject* PythonConvert(unsigned int const& value)
{
  unsigned long unsignedLongValue = value;
  Py_RETURN_CHECKED(PyLong_FromUnsignedLong(unsignedLongValue));
}

//----------------------------------------------------------------------------
template <>
PyObject* PythonConvert(unsigned long const& value)
{
  Py_RETURN_CHECKED(PyLong_FromUnsignedLong(value));
}

//----------------------------------------------------------------------------
template <>
PyObject* PythonConvert(long long const& value)
{
  Py_RETURN_CHECKED(PyLong_FromLongLong(value));
}

//----------------------------------------------------------------------------
template <>
PyObject* PythonConvert(unsigned long long const& value)
{
  Py_RETURN_CHECKED(PyLong_FromUnsignedLongLong(value));
}

//----------------------------------------------------------------------------
template <>
PyObject* PythonConvert(double const& value)
{
  Py_RETURN_CHECKED(PyFloat_FromDouble(value));
}

//----------------------------------------------------------------------------
template <>
PyObject* PythonConvert(float const& value)
{
  return PythonConvert<double>(value);
}

//----------------------------------------------------------------------------
template <>
PyObject* PythonConvert(bool const& value)
{
  if (value)
    {
    Py_RETURN_TRUE;
    }
  else
    {
    Py_RETURN_FALSE;
    }
}

//----------------------------------------------------------------------------
template <>
PyObject* PythonConvert(vtkStdString const& value)
{
  Py_RETURN_CHECKED(PyString_FromStringAndSize(value.c_str(), value.size()));
}

//----------------------------------------------------------------------------
template <typename T>
static PyObject* MakeArrayOf(const vtkClientServerStream& msg, int arg, vtkTypeUInt32 length)
{
  T* argArray = new T[length];
  if (!msg.GetArgument(0, arg, argArray, length))
    {
    delete [] argArray;
    Py_RETURN_NONE;
    }

  PyObject* tuple = PyTuple_New(length);
  for (vtkTypeUInt32 i = 0; i < length; ++i)
    {
    PyTuple_SET_ITEM(tuple, i, PythonConvert<T>(argArray[i]));
    }

  delete [] argArray;
  return tuple;
}

//----------------------------------------------------------------------------
template <typename T>
static PyObject* MakeScalar(const vtkClientServerStream& msg, int arg)
{
  T argValue;
  if (!msg.GetArgument(0, arg, &argValue))
    {
    Py_RETURN_NONE;
    }

  return PythonConvert<T>(argValue);
}

//----------------------------------------------------------------------------
static vtkObjectBase* NewInstanceCallback(void* ctx)
{
  PythonCSShimData* data = (PythonCSShimData*)ctx;

  vtkSmartPyObject args(PyTuple_New(0));
  vtkSmartPyObject instance(PyObject_Call(data->Object, args, NULL));

  vtkObjectBase* obj = PyVTKObject_GetObject(instance);
  obj->Register(NULL);
  // The caller owns the new object, so kick it out of the object cache.
  vtkPythonUtil::RemoveObjectFromMap(instance);

  return obj;
}

//----------------------------------------------------------------------------
static void ReturnResult(PyObject* result, vtkClientServerStream& resultStream)
{
  resultStream.Reset();

  if (PyVTKObject_Check(result))
    {
    vtkObjectBase* obj = PyVTKObject_GetObject(result);
    resultStream << vtkClientServerStream::Reply << obj << vtkClientServerStream::End;
    }
  else if (PyString_Check(result))
    {
    char* str = PyString_AsString(result);
    resultStream << vtkClientServerStream::Reply << str << vtkClientServerStream::End;
    }
  else if (PyBool_Check(result))
    {
    bool num = result == Py_True;
    resultStream << vtkClientServerStream::Reply << num << vtkClientServerStream::End;
    }
  else if (PyLong_Check(result))
    {
#if PY_VERSION_HEX >= 0x02070000
    int overflow;
    PY_LONG_LONG num = PyLong_AsLongLongAndOverflow(result, &overflow);
    if (!overflow)
      {
      resultStream << vtkClientServerStream::Reply << num << vtkClientServerStream::End;
      }
    else
      {
      unsigned PY_LONG_LONG unum = PyLong_AsUnsignedLongLong(result);
      resultStream << vtkClientServerStream::Reply << unum << vtkClientServerStream::End;
      }
#else
  PY_LONG_LONG num = PyLong_AsLongLong(result);
  if (PyErr_Occurred())
    {
    PyErr_Clear();
    unsigned PY_LONG_LONG unum = PyLong_AsUnsignedLongLong(result);
    resultStream << vtkClientServerStream::Reply << unum << vtkClientServerStream::End;
    }
  else
    {
    resultStream << vtkClientServerStream::Reply << num << vtkClientServerStream::End;
    }
#endif
    }
  else if (PyFloat_Check(result))
    {
    double num = PyFloat_AsDouble(result);
    resultStream << vtkClientServerStream::Reply << num << vtkClientServerStream::End;
    }
  else if (PyInt_Check(result))
    {
    long num = PyInt_AsLong(result);
    resultStream << vtkClientServerStream::Reply << num << vtkClientServerStream::End;
    }
  else if (PyTuple_Check(result))
    {
    Py_ssize_t size = PyTuple_Size(result);
    // Check the type of the first item to see what we should do.
    PyObject* sentinel_item = PyTuple_GET_ITEM(result, 0);

#define convert_array(convert, type)                   \
  do                                                   \
    {                                                  \
    type* arr = new type[size];                        \
    for (Py_ssize_t i = 0; i < size; ++i)              \
      {                                                \
      PyObject* item = PyTuple_GET_ITEM(result, i);    \
      type num = convert(item);                        \
      arr[i] = num;                                    \
      }                                                \
    resultStream                                       \
      << vtkClientServerStream::Reply                  \
      << vtkClientServerStream::InsertArray(arr, size) \
      << vtkClientServerStream::End;                   \
    delete [] arr;                                     \
    } while (false)

    if (PyLong_Check(sentinel_item))
      {
      convert_array(PyLong_AsUnsignedLongLong, unsigned PY_LONG_LONG);
      }
    else if (PyInt_Check(sentinel_item))
      {
      convert_array(PyInt_AsLong, long);
      }
    else if (PyFloat_Check(sentinel_item))
      {
      convert_array(PyFloat_AsDouble, double);
      }

#undef convert_array
    }
  else
    {
    resultStream << vtkClientServerStream::Error
                 << "Unable to convert result from Python"
                 << vtkClientServerStream::End;
    }
}

//----------------------------------------------------------------------------
static vtkStdString GetPythonErrorString()
{
  PyObject* type;
  PyObject* value;
  PyObject* traceback;

  // Increments refcounts for returns.
  PyErr_Fetch(&type, &value, &traceback);
  // place the results in vtkSmartPyObjects so the reference counts
  // are automatically decremented
  vtkSmartPyObject sType(type);
  vtkSmartPyObject sValue(value);
  vtkSmartPyObject sTraceback(traceback);

  if (!sType)
    {
    return "No error from Python?!";
    }

  vtkSmartPyObject pyexc_string(PyObject_Str(sValue));
  vtkStdString exc_string;
  if (pyexc_string)
    {
    exc_string = PyString_AsString(pyexc_string);
    }
  else
    {
    exc_string = "<Unable to convert Python error to string>";
    }

  PyErr_Clear();

  return exc_string;
}

//----------------------------------------------------------------------------
static int CommandFunctionCallback(vtkClientServerInterpreter* /*interp*/,
                                   vtkObjectBase* ptr,
                                   const char* method,
                                   const vtkClientServerStream& msg,
                                   vtkClientServerStream& result,
                                   void* ctx)
{
  PythonCSShimData* data = (PythonCSShimData*)ctx;

  if (!PyObject_HasAttrString(data->Object, method))
    {
    if (result.GetNumberOfMessages() > 0 &&
        result.GetCommand(0) == vtkClientServerStream::Error &&
        result.GetNumberOfArguments(0) > 1)
      {
      /* A superclass wrapper prepared a special message. */
      return 0;
      }
    vtkOStrStreamWrapper vtkmsg;
    vtkmsg << "Object type: " << ptr->GetClassName() << ", could not find requested method: \""
           << method << "\".\n";
    result.Reset();
    result << vtkClientServerStream::Error
           << vtkmsg.str()
           << vtkClientServerStream::End;
    vtkmsg.rdbuf()->freeze(0);
    return 0;
    }

  vtkSmartPyObject vtkObject(PyVTKObject_New(data->Object, NULL, ptr));
  PyObject* methodObject = PyObject_GetAttrString(vtkObject, method);

  const int argOffset = 2;
  int numArgs = msg.GetNumberOfArguments(0) - argOffset;
  vtkSmartPyObject args(PyTuple_New(numArgs));

  for (Py_ssize_t i = 0; i < numArgs; ++i)
    {
    int msgArg = i + argOffset;
    int pyArg = i;
    vtkClientServerStream::Types type = msg.GetArgumentType(0, msgArg);
    vtkTypeUInt32 length;
    msg.GetArgumentLength(0, msgArg, &length);

#define array_mappings(call)    \
  call(int8,    vtkTypeInt8)    \
  call(int16,   vtkTypeInt16)   \
  call(int32,   vtkTypeInt32)   \
  call(int64,   vtkTypeInt64)   \
  call(uint8,   vtkTypeUInt8)   \
  call(uint16,  vtkTypeUInt16)  \
  call(uint32,  vtkTypeUInt32)  \
  call(uint64,  vtkTypeUInt64)  \
  call(float32, vtkTypeFloat32) \
  call(float64, vtkTypeFloat64)
#define scalar_mappings(call) \
  array_mappings(call)        \
  call(bool,   bool)          \
  call(string, vtkStdString)

    switch (type)
      {
#define make_array(msgType, cxxType)                           \
  case vtkClientServerStream::msgType##_array:                 \
    {                                                          \
    PyObject* obj = MakeArrayOf<cxxType>(msg, msgArg, length); \
    PyTuple_SET_ITEM(args.GetPointer(), pyArg, obj);           \
    break;                                                     \
    }

      array_mappings(make_array)

#undef make_array

#define make_scalar(msgType, cxxType)                 \
  case vtkClientServerStream::msgType##_value:        \
    {                                                 \
    PyObject* obj = MakeScalar<cxxType>(msg, msgArg); \
    PyTuple_SET_ITEM(args.GetPointer(), pyArg, obj);  \
    break;                                            \
    }

      scalar_mappings(make_scalar)

#undef make_scalar

#undef scalar_mappings
#undef array_mappings

      case vtkClientServerStream::vtk_object_pointer:
        {
        vtkObjectBase* argObj;
        if (msg.GetArgument(0, msgArg, &argObj) && argObj)
          {
          PyObject* cls = vtkPythonUtil::FindNearestBaseClass(argObj);
          PyObject* obj = PyVTKObject_New(cls, NULL, argObj);
          PyTuple_SET_ITEM(args.GetPointer(), pyArg, obj);
          }
        else
          {
          Py_INCREF(Py_None);
          PyTuple_SET_ITEM(args.GetPointer(), pyArg, Py_None);
          }
        break;
        }
      // Streams and ID values are intended to be handled internally; they
      // should never be passed as arguments.
      case vtkClientServerStream::stream_value:
      case vtkClientServerStream::id_value:
      // If we don't recognize it, pass None as the argument.
      default:
        Py_INCREF(Py_None);
        PyTuple_SET_ITEM(args.GetPointer(), pyArg, Py_None);
        break;
      }
    }

  vtkSmartPyObject callResult(PyObject_CallObject(methodObject, args));

  // Stop tracking the created Python object within VTK. Without this, the
  // vtkPython code tries to delete `ptr`, but we can't let that happen since
  // even this function doesn't own `ptr`.
  vtkPythonUtil::RemoveObjectFromMap(vtkObject);

  if (!callResult)
    {
    vtkOStrStreamWrapper vtkmsg;
    vtkStdString pymsg = GetPythonErrorString();
    vtkmsg << "Object type: " << ptr->GetClassName() << ", failure when calling method: \""
           << method << "\": " << pymsg << ".\n";
    result.Reset();
    result << vtkClientServerStream::Error
           << vtkmsg.str()
           << vtkClientServerStream::End;
    vtkmsg.rdbuf()->freeze(0);
    return 0;
    }

  ReturnResult(callResult, result);

  return 1;
}

//----------------------------------------------------------------------------
int vtkClientServerInterpreter::LoadImpl(const char* moduleName)
{
  if (!vtkPythonInterpreter::IsInitialized())
    {
    return 1;
    }

  static std::string const moduleSuffix = "Python";
  vtkSmartPyObject module(PyImport_ImportModule((moduleName + moduleSuffix).c_str()));

  if (!module)
    {
    return 1;
    }

  vtkSmartPyObject properties(PyObject_Dir(module));
  Py_ssize_t size = PyList_Size(properties);

  for (Py_ssize_t i = 0; i < size; ++i)
    {
    PyObject* item = PyList_GetItem(properties, i);
    const char* name = PyString_AsString(item);
    if (!strncmp("vtk", name, 3))
      {
      vtkSmartPyObject cls(PyObject_GetAttr(module, item));

      PythonCSShimData* newInstanceData = new PythonCSShimData(cls);
      this->AddNewInstanceFunction(name, NewInstanceCallback, newInstanceData, FreePythonCSShimData);

      PythonCSShimData* commandData = new PythonCSShimData(cls);
      this->AddCommandFunction(name, CommandFunctionCallback, commandData, FreePythonCSShimData);

      }
    }

  return 0;
}
