/******************************************************************************
 *
 * MantaFlow fluid solver framework
 * Copyright 2011 Tobias Pfaff, Nils Thuerey
 *
 * This program is free software, distributed under the terms of the
 * Apache License, Version 2.0
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Python argument wrappers and conversion tools
 *
 ******************************************************************************/

#include "pythonInclude.h"
#include <sstream>
#include <algorithm>
#include "vectorbase.h"
#include "manta.h"

using namespace std;

//******************************************************************************
// Explicit definition and instantiation of python object converters

namespace Manta {

extern PyTypeObject PbVec3Type;
extern PyTypeObject PbVec4Type;

struct PbVec3 {
  PyObject_HEAD
  float data[3];
};

struct PbVec4 {
  PyObject_HEAD
  float data[4];
};

PyObject *getPyNone()
{
  Py_INCREF(Py_None);
  return Py_None;
}
PyObject *incref(PyObject *obj)
{
  Py_INCREF(obj);
  return obj;
}

/*template<> PyObject* toPy<PyObject*>(PyObject* obj) {
  return obj;
}*/
template<> PyObject *toPy<int>(const int &v)
{
  return PyLong_FromLong(v);
}
/*template<> PyObject* toPy<char*>(const (char*) & val) {
  return PyUnicode_DecodeLatin1(val,strlen(val),"replace");
}*/
template<> PyObject *toPy<string>(const string &val)
{
  return PyUnicode_DecodeLatin1(val.c_str(), val.length(), "replace");
}
template<> PyObject *toPy<float>(const float &v)
{
  return PyFloat_FromDouble(v);
}
template<> PyObject *toPy<double>(const double &v)
{
  return PyFloat_FromDouble(v);
}
template<> PyObject *toPy<bool>(const bool &v)
{
  return PyBool_FromLong(v);
}
template<> PyObject *toPy<Vec3i>(const Vec3i &v)
{
  float x = (float)v.x, y = (float)v.y, z = (float)v.z;
  return PyObject_CallFunction((PyObject *)&PbVec3Type, (char *)"fff", x, y, z);
}
template<> PyObject *toPy<Vec3>(const Vec3 &v)
{
  float x = (float)v.x, y = (float)v.y, z = (float)v.z;
  return PyObject_CallFunction((PyObject *)&PbVec3Type, (char *)"fff", x, y, z);
}
template<> PyObject *toPy<Vec4i>(const Vec4i &v)
{
  float x = (float)v.x, y = (float)v.y, z = (float)v.z;
  return PyObject_CallFunction((PyObject *)&PbVec4Type, (char *)"ffff", x, y, z);
}
template<> PyObject *toPy<Vec4>(const Vec4 &v)
{
  float x = (float)v.x, y = (float)v.y, z = (float)v.z;
  return PyObject_CallFunction((PyObject *)&PbVec4Type, (char *)"ffff", x, y, z);
}
template<> PyObject *toPy<PbClass *>(const PbClass_Ptr &obj)
{
  return obj->getPyObject();
}
template<> PyObject *toPy<std::vector<PbClass *>>(const std::vector<PbClass *> &vec)
{
  PyObject *listObj = PyList_New(vec.size());
  if (!listObj)
    throw logic_error("Unable to allocate memory for Python list");
  for (unsigned int i = 0; i < vec.size(); i++) {
    PbClass *pb = vec[i];
    PyObject *item = pb->getPyObject();
    if (!item) {
      Py_DECREF(listObj);
      throw logic_error("Unable to allocate memory for Python list");
    }
    PyList_SET_ITEM(listObj, i, item);
  }
  return listObj;
}
template<> PyObject *toPy<std::vector<float>>(const std::vector<float> &vec)
{
  PyObject *listObj = PyList_New(vec.size());
  if (!listObj)
    throw logic_error("Unable to allocate memory for Python list");
  for (unsigned int i = 0; i < vec.size(); i++) {
    PyObject *item = toPy<float>(vec[i]);
    if (!item) {
      Py_DECREF(listObj);
      throw logic_error("Unable to allocate memory for Python list");
    }
    PyList_SET_ITEM(listObj, i, item);
  }
  return listObj;
}

template<> float fromPy<float>(PyObject *obj)
{
#if PY_MAJOR_VERSION <= 2
  if (PyInt_Check(obj))
    return PyInt_AsLong(obj);
#endif
  if (PyFloat_Check(obj))
    return PyFloat_AsDouble(obj);
  if (PyLong_Check(obj))
    return PyLong_AsDouble(obj);
  errMsg("argument is not a float");
}
template<> double fromPy<double>(PyObject *obj)
{
#if PY_MAJOR_VERSION <= 2
  if (PyInt_Check(obj))
    return PyInt_AsLong(obj);
#endif
  if (PyFloat_Check(obj))
    return PyFloat_AsDouble(obj);
  if (PyLong_Check(obj))
    return PyLong_AsDouble(obj);
  errMsg("argument is not a double");
}
template<> PyObject *fromPy<PyObject *>(PyObject *obj)
{
  return obj;
}
template<> PbClass *fromPy<PbClass *>(PyObject *obj)
{
  PbClass *pbo = Pb::objFromPy(obj);

  if (!PyType_Check(obj))
    return pbo;

  const char *tname = ((PyTypeObject *)obj)->tp_name;
  pbo->setName(tname);

  return pbo;
}
template<> std::vector<PbClass *> fromPy<std::vector<PbClass *>>(PyObject *obj)
{
  std::vector<PbClass *> vec;
  if (PyList_Check(obj)) {
    int sz = PyList_Size(obj);
    for (int i = 0; i < sz; ++i) {
      PyObject *lobj = PyList_GetItem(obj, i);
      vec.push_back(fromPy<PbClass *>(lobj));
    }
  }
  return vec;
}
template<> std::vector<float> fromPy<std::vector<float>>(PyObject *obj)
{
  std::vector<float> vec;
  if (PyList_Check(obj)) {
    int sz = PyList_Size(obj);
    for (int i = 0; i < sz; ++i) {
      PyObject *lobj = PyList_GetItem(obj, i);
      vec.push_back(fromPy<float>(lobj));
    }
  }
  return vec;
}
template<> int fromPy<int>(PyObject *obj)
{
#if PY_MAJOR_VERSION <= 2
  if (PyInt_Check(obj))
    return PyInt_AsLong(obj);
#endif
  if (PyLong_Check(obj))
    return PyLong_AsDouble(obj);
  if (PyFloat_Check(obj)) {
    double a = PyFloat_AsDouble(obj);
    if (fabs(a - floor(a + 0.5)) > 1e-5)
      errMsg("argument is not an int");
    return (int)(a + 0.5);
  }
  errMsg("argument is not an int");
}
template<> string fromPy<string>(PyObject *obj)
{
  if (PyUnicode_Check(obj))
#ifdef BLENDER
    // Blender is completely UTF-8 based
    return PyBytes_AsString(PyUnicode_AsUTF8String(obj));
#else
    return PyBytes_AsString(PyUnicode_AsLatin1String(obj));
#endif
#if PY_MAJOR_VERSION <= 2
  else if (PyString_Check(obj))
    return PyString_AsString(obj);
#endif
  else
    errMsg("argument is not a string");
}
template<> const char *fromPy<const char *>(PyObject *obj)
{
  if (PyUnicode_Check(obj))
#ifdef BLENDER
    // Blender is completely UTF-8 based
    return PyBytes_AsString(PyUnicode_AsUTF8String(obj));
#else
    return PyBytes_AsString(PyUnicode_AsLatin1String(obj));
#endif
#if PY_MAJOR_VERSION <= 2
  else if (PyString_Check(obj))
    return PyString_AsString(obj);
#endif
  else
    errMsg("argument is not a string");
}
template<> bool fromPy<bool>(PyObject *obj)
{
  if (!PyBool_Check(obj))
    errMsg("argument is not a boolean");
  return PyLong_AsLong(obj) != 0;
}
template<> Vec3 fromPy<Vec3>(PyObject *obj)
{
  if (PyObject_IsInstance(obj, (PyObject *)&PbVec3Type)) {
    return Vec3(((PbVec3 *)obj)->data);
  }
  else if (PyTuple_Check(obj) && PyTuple_Size(obj) == 3) {
    return Vec3(fromPy<Real>(PyTuple_GetItem(obj, 0)),
                fromPy<Real>(PyTuple_GetItem(obj, 1)),
                fromPy<Real>(PyTuple_GetItem(obj, 2)));
  }
  errMsg("argument is not a Vec3");
}
template<> Vec3i fromPy<Vec3i>(PyObject *obj)
{
  if (PyObject_IsInstance(obj, (PyObject *)&PbVec3Type)) {
    return toVec3iChecked(((PbVec3 *)obj)->data);
  }
  else if (PyTuple_Check(obj) && PyTuple_Size(obj) == 3) {
    return Vec3i(fromPy<int>(PyTuple_GetItem(obj, 0)),
                 fromPy<int>(PyTuple_GetItem(obj, 1)),
                 fromPy<int>(PyTuple_GetItem(obj, 2)));
  }
  errMsg("argument is not a Vec3i");
}
template<> Vec4 fromPy<Vec4>(PyObject *obj)
{
  if (PyObject_IsInstance(obj, (PyObject *)&PbVec4Type)) {
    return Vec4(((PbVec4 *)obj)->data);
  }
  else if (PyTuple_Check(obj) && PyTuple_Size(obj) == 4) {
    return Vec4(fromPy<Real>(PyTuple_GetItem(obj, 0)),
                fromPy<Real>(PyTuple_GetItem(obj, 1)),
                fromPy<Real>(PyTuple_GetItem(obj, 2)),
                fromPy<Real>(PyTuple_GetItem(obj, 3)));
  }
  errMsg("argument is not a Vec4");
}
template<> Vec4i fromPy<Vec4i>(PyObject *obj)
{
  if (PyObject_IsInstance(obj, (PyObject *)&PbVec4Type)) {
    return toVec4i(((PbVec4 *)obj)->data);
  }
  else if (PyTuple_Check(obj) && PyTuple_Size(obj) == 4) {
    return Vec4i(fromPy<int>(PyTuple_GetItem(obj, 0)),
                 fromPy<int>(PyTuple_GetItem(obj, 1)),
                 fromPy<int>(PyTuple_GetItem(obj, 2)),
                 fromPy<int>(PyTuple_GetItem(obj, 3)));
  }
  errMsg("argument is not a Vec4i");
}
template<> PbType fromPy<PbType>(PyObject *obj)
{
  PbType pb = {""};
  if (!PyType_Check(obj))
    return pb;

  const char *tname = ((PyTypeObject *)obj)->tp_name;
  pb.S = tname;
  return pb;
}
template<> PbTypeVec fromPy<PbTypeVec>(PyObject *obj)
{
  PbTypeVec vec;
  if (PyType_Check(obj)) {
    vec.T.push_back(fromPy<PbType>(obj));
  }
  else if (PyTuple_Check(obj)) {
    int sz = PyTuple_Size(obj);
    for (int i = 0; i < sz; i++)
      vec.T.push_back(fromPy<PbType>(PyTuple_GetItem(obj, i)));
  }
  else
    errMsg("argument is not a type tuple");
  return vec;
}

template<class T> T *tmpAlloc(PyObject *obj, std::vector<void *> *tmp)
{
  if (!tmp)
    throw Error("dynamic de-ref not supported for this type");

  T *ptr = new T(fromPy<T>(obj));
  tmp->push_back(ptr);
  return ptr;
}
template<> float *fromPyPtr<float>(PyObject *obj, std::vector<void *> *tmp)
{
  return tmpAlloc<float>(obj, tmp);
}
template<> double *fromPyPtr<double>(PyObject *obj, std::vector<void *> *tmp)
{
  return tmpAlloc<double>(obj, tmp);
}
template<> int *fromPyPtr<int>(PyObject *obj, std::vector<void *> *tmp)
{
  return tmpAlloc<int>(obj, tmp);
}
template<> std::string *fromPyPtr<std::string>(PyObject *obj, std::vector<void *> *tmp)
{
  return tmpAlloc<std::string>(obj, tmp);
}
template<> bool *fromPyPtr<bool>(PyObject *obj, std::vector<void *> *tmp)
{
  return tmpAlloc<bool>(obj, tmp);
}
template<> Vec3 *fromPyPtr<Vec3>(PyObject *obj, std::vector<void *> *tmp)
{
  return tmpAlloc<Vec3>(obj, tmp);
}
template<> Vec3i *fromPyPtr<Vec3i>(PyObject *obj, std::vector<void *> *tmp)
{
  return tmpAlloc<Vec3i>(obj, tmp);
}
template<> Vec4 *fromPyPtr<Vec4>(PyObject *obj, std::vector<void *> *tmp)
{
  return tmpAlloc<Vec4>(obj, tmp);
}
template<> Vec4i *fromPyPtr<Vec4i>(PyObject *obj, std::vector<void *> *tmp)
{
  return tmpAlloc<Vec4i>(obj, tmp);
}
template<>
std::vector<PbClass *> *fromPyPtr<std::vector<PbClass *>>(PyObject *obj, std::vector<void *> *tmp)
{
  return tmpAlloc<std::vector<PbClass *>>(obj, tmp);
}

template<> bool isPy<float>(PyObject *obj)
{
#if PY_MAJOR_VERSION <= 2
  if (PyInt_Check(obj))
    return true;
#endif
  return PyFloat_Check(obj) || PyLong_Check(obj);
}
template<> bool isPy<double>(PyObject *obj)
{
#if PY_MAJOR_VERSION <= 2
  if (PyInt_Check(obj))
    return true;
#endif
  return PyFloat_Check(obj) || PyLong_Check(obj);
}
template<> bool isPy<PyObject *>(PyObject *obj)
{
  return true;
}
template<> bool isPy<int>(PyObject *obj)
{
#if PY_MAJOR_VERSION <= 2
  if (PyInt_Check(obj))
    return true;
#endif
  if (PyLong_Check(obj))
    return true;
  if (PyFloat_Check(obj)) {
    double a = PyFloat_AsDouble(obj);
    return fabs(a - floor(a + 0.5)) < 1e-5;
  }
  return false;
}
template<> bool isPy<string>(PyObject *obj)
{
  if (PyUnicode_Check(obj))
    return true;
#if PY_MAJOR_VERSION <= 2
  if (PyString_Check(obj))
    return true;
#endif
  return false;
}
template<> bool isPy<const char *>(PyObject *obj)
{
  if (PyUnicode_Check(obj))
    return true;
#if PY_MAJOR_VERSION <= 2
  if (PyString_Check(obj))
    return true;
#endif
  return false;
}
template<> bool isPy<bool>(PyObject *obj)
{
  return PyBool_Check(obj);
}
template<> bool isPy<Vec3>(PyObject *obj)
{
  if (PyObject_IsInstance(obj, (PyObject *)&PbVec3Type))
    return true;
  if (PyTuple_Check(obj) && PyTuple_Size(obj) == 3) {
    return isPy<Real>(PyTuple_GetItem(obj, 0)) && isPy<Real>(PyTuple_GetItem(obj, 1)) &&
           isPy<Real>(PyTuple_GetItem(obj, 2));
  }
  return false;
}
template<> bool isPy<Vec3i>(PyObject *obj)
{
  if (PyObject_IsInstance(obj, (PyObject *)&PbVec3Type))
    return true;
  if (PyTuple_Check(obj) && PyTuple_Size(obj) == 3) {
    return isPy<int>(PyTuple_GetItem(obj, 0)) && isPy<int>(PyTuple_GetItem(obj, 1)) &&
           isPy<int>(PyTuple_GetItem(obj, 2));
  }
  return false;
}
template<> bool isPy<Vec4>(PyObject *obj)
{
  if (PyObject_IsInstance(obj, (PyObject *)&PbVec4Type))
    return true;
  if (PyTuple_Check(obj) && PyTuple_Size(obj) == 4) {
    return isPy<Real>(PyTuple_GetItem(obj, 0)) && isPy<Real>(PyTuple_GetItem(obj, 1)) &&
           isPy<Real>(PyTuple_GetItem(obj, 2)) && isPy<Real>(PyTuple_GetItem(obj, 3));
  }
  return false;
}
template<> bool isPy<Vec4i>(PyObject *obj)
{
  if (PyObject_IsInstance(obj, (PyObject *)&PbVec4Type))
    return true;
  if (PyTuple_Check(obj) && PyTuple_Size(obj) == 4) {
    return isPy<int>(PyTuple_GetItem(obj, 0)) && isPy<int>(PyTuple_GetItem(obj, 1)) &&
           isPy<int>(PyTuple_GetItem(obj, 2)) && isPy<int>(PyTuple_GetItem(obj, 3));
  }
  return false;
}
template<> bool isPy<PbType>(PyObject *obj)
{
  return PyType_Check(obj);
}
template<> bool isPy<std::vector<PbClass *>>(PyObject *obj)
{
  if (PyList_Check(obj))
    return true;
  return false;
}
template<> bool isPy<std::vector<float>>(PyObject *obj)
{
  if (PyList_Check(obj))
    return true;
  return false;
}

//******************************************************************************
// PbArgs class defs

PbArgs PbArgs::EMPTY(nullptr, nullptr);

PbArgs::PbArgs(PyObject *linarg, PyObject *dict) : mLinArgs(0), mKwds(0)
{
  setup(linarg, dict);
}
PbArgs::~PbArgs()
{
  for (int i = 0; i < (int)mTmpStorage.size(); i++)
    operator delete(mTmpStorage[i]);
  mTmpStorage.clear();
}

void PbArgs::copy(PbArgs &a)
{
  mKwds = a.mKwds;
  mData = a.mData;
  mLinData = a.mLinData;
  mLinArgs = a.mLinArgs;
}
void PbArgs::clear()
{
  mLinArgs = 0;
  mKwds = 0;
  mData.clear();
  mLinData.clear();
}

PbArgs &PbArgs::operator=(const PbArgs &a)
{
  //    mLinArgs = 0;
  //    mKwds = 0;
  return *this;
}

void PbArgs::setup(PyObject *linarg, PyObject *dict)
{
  if (dict) {
    PyObject *key, *value;
    Py_ssize_t pos = 0;
    while (PyDict_Next(dict, &pos, &key, &value)) {
      DataElement el;
      el.obj = value;
      el.visited = false;
      mData[fromPy<string>(key)] = el;
    }
    mKwds = dict;
  }
  if (linarg) {
    size_t len = PyTuple_Size(linarg);
    for (size_t i = 0; i < len; i++) {
      DataElement el;
      el.obj = PyTuple_GetItem(linarg, i);
      el.visited = false;
      mLinData.push_back(el);
    }
    mLinArgs = linarg;
  }
}

void PbArgs::addLinArg(PyObject *obj)
{
  DataElement el = {obj, false};
  mLinData.push_back(el);
}

void PbArgs::check()
{
  if (has("nocheck"))
    return;

  for (map<string, DataElement>::iterator it = mData.begin(); it != mData.end(); it++) {
    if (!it->second.visited)
      errMsg("Argument '" + it->first + "' unknown");
  }
  for (size_t i = 0; i < mLinData.size(); i++) {
    if (!mLinData[i].visited) {
      stringstream s;
      s << "Function does not read argument number #" << i;
      errMsg(s.str());
    }
  }
}

FluidSolver *PbArgs::obtainParent()
{
  FluidSolver *solver = getPtrOpt<FluidSolver>("solver", -1, nullptr);
  if (solver != 0)
    return solver;

  for (map<string, DataElement>::iterator it = mData.begin(); it != mData.end(); it++) {
    PbClass *obj = Pb::objFromPy(it->second.obj);

    if (obj) {
      if (solver == nullptr)
        solver = obj->getParent();
    }
  }
  for (vector<DataElement>::iterator it = mLinData.begin(); it != mLinData.end(); it++) {
    PbClass *obj = Pb::objFromPy(it->obj);

    if (obj) {
      if (solver == nullptr)
        solver = obj->getParent();
    }
  }

  return solver;
}

void PbArgs::visit(int number, const string &key)
{
  if (number >= 0 && number < (int)mLinData.size())
    mLinData[number].visited = true;
  map<string, DataElement>::iterator lu = mData.find(key);
  if (lu != mData.end())
    lu->second.visited = true;
}

PyObject *PbArgs::getItem(const std::string &key, bool strict, ArgLocker *lk)
{
  map<string, DataElement>::iterator lu = mData.find(key);
  if (lu == mData.end()) {
    if (strict)
      errMsg("Argument '" + key + "' is not defined.");
    return nullptr;
  }
  PbClass *pbo = Pb::objFromPy(lu->second.obj);
  // try to lock
  if (pbo && lk)
    lk->add(pbo);
  return lu->second.obj;
}

PyObject *PbArgs::getItem(size_t number, bool strict, ArgLocker *lk)
{
  if (number >= mLinData.size()) {
    if (!strict)
      return nullptr;
    stringstream s;
    s << "Argument number #" << number << " not specified.";
    errMsg(s.str());
  }
  PbClass *pbo = Pb::objFromPy(mLinData[number].obj);
  // try to lock
  if (pbo && lk)
    lk->add(pbo);
  return mLinData[number].obj;
}

//******************************************************************************
// ArgLocker class defs

void ArgLocker::add(PbClass *p)
{
  if (find(locks.begin(), locks.end(), p) == locks.end()) {
    locks.push_back(p);
    p->lock();
  }
}
ArgLocker::~ArgLocker()
{
  for (size_t i = 0; i < locks.size(); i++)
    locks[i]->unlock();
  locks.clear();
}

}  // namespace Manta
