/* ====================================================================
 * Copyright (c) 2004-2006 Open Source Applications Foundation.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software 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. 
 *
 * 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 OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * ====================================================================
 */

#include "cpp/PyLucene.h"
#include "functions.h"
#include "converters.h"
#include <java/lang/Integer.h>
#include <java/lang/System.h>
#include <java/lang/Throwable.h>
#include <java/util/Map.h>
#include <java/util/Hashtable.h>
#include <java/util/IdentityHashMap.h>

#include "java.h"
#include "org/osafoundation/util/PythonException.h"

java::util::Map *pythonRefs;

static PyObject *PyExc_JavaError, *PyExc_InvalidArgsError;
static java::lang::Integer *one;


static PyObject *refError(char *msg, jobject obj)
{
    PyObject *format =
        PyString_FromString("unrefObject: %s '%s' of class %s");
    PyObject *args = PyTuple_New(3);

    PyTuple_SET_ITEM(args, 0, PyString_FromString(msg));
    PyTuple_SET_ITEM(args, 1, j2p(obj->toString()));
    PyTuple_SET_ITEM(args, 2, j2p(obj->getClass()->getName()));

    PyObject *err = PyString_Format(format, args);
    Py_DECREF(format);
    Py_DECREF(args);

    return err;
}

/* runs under the GIL, doesn't need to synchronize access to pythonRefs */
jobject refObject(jobject obj)
{
    java::lang::Integer *integer = (java::lang::Integer *) pythonRefs->get(obj);

    if (integer == NULL)
        pythonRefs->put(obj, one);
    else
    {
        jint refcount = integer->intValue() + 1;
        pythonRefs->put(obj, new java::lang::Integer(refcount));
    }

    return obj;
}

void unrefObject(jobject obj)
{
    PythonGIL gil;
    java::lang::Integer *integer = (java::lang::Integer *) pythonRefs->get(obj);

    if (!integer)
    {
        if (obj)
        {
            PyObject *err = refError("unregistered object", obj);

            PyErr_SetObject(PyExc_AssertionError, err);
            Py_DECREF(err);
        }
        else
            PyErr_SetString(PyExc_AssertionError, "unrefObject: null object");

        throw new org::osafoundation::util::PythonException();
    }

    jint refcount = integer->intValue() - 1;

    if (refcount == 0)
        pythonRefs->remove(obj);
    else if (refcount == 1)
        pythonRefs->put(obj, one);
    else if (refcount < 0)
    {
        PyObject *err = refError("refcount is negative for", obj);

        PyErr_SetObject(PyExc_AssertionError, err);
        Py_DECREF(err);

        throw new org::osafoundation::util::PythonException();
    }
    else
        pythonRefs->put(obj, new java::lang::Integer(refcount));
}


PyObject *callPython(PyObject *self, char *methodName, ...)
{
    PyObject *pyCallable = PyObject_GetAttrString(self, methodName);
    PyObject *result = NULL;

    if (pyCallable != NULL)
    {
        va_list args;
        int count = 0;

        va_start(args, methodName);
        while (va_arg(args, PyObject *) != NULL)
            count += 1;
        va_end(args);

        PyObject *pyArgs = PyTuple_New(count);
        PyObject *pyArg;
        int i = 0;

        va_start(args, methodName);
        while ((pyArg = va_arg(args, PyObject *)) != NULL) {
            PyTuple_SET_ITEM(pyArgs, i++, pyArg);
            Py_INCREF(pyArg);
        }
        va_end(args);

        result = PyObject_Call(pyCallable, pyArgs, NULL);
        Py_DECREF(pyArgs);
        Py_DECREF(pyCallable);
    }

    return result;
}


int initVM()
{
    PyObject *m = PyImport_ImportModule("PyLucene");

    if (!m)
    {
        if (!PyErr_Occurred())
            PyErr_SetString(PyExc_ImportError, "PyLucene");
        return -1;
    }

    PyExc_JavaError = PyObject_GetAttrString(m, "JavaError");
    PyExc_InvalidArgsError = PyObject_GetAttrString(m, "InvalidArgsError");

    Py_DECREF(m);

    PyEval_InitThreads();

    JvCreateJavaVM(NULL);
    JvAttachCurrentThread(NULL, NULL);

    java::lang::Integer *_one = one = new java::lang::Integer(1);
    java::util::Map *_pythonRefs = pythonRefs = (java::util::Map *)
        new java::util::IdentityHashMap();
    java::util::Hashtable *props = (java::util::Hashtable *)
        java::lang::System::getProperties();

    //props->put(JvNewStringUTF("pythonRefs"), pythonRefs);
    pythonRefs->put(one, one);

    PyObject *sys = PyImport_ImportModule("sys");
    PyObject *path = PyObject_GetAttrString(sys, "path");

    if (path)
    {
        int count = PyList_Size(path);

        while (count-- > 0) {
            PyObject *pp = PyList_GET_ITEM(path, count);
            PyObject *unicode_pp =
                PyString_AsDecodedObject(pp, Py_FileSystemDefaultEncoding,
                                         "strict");
            PyObject *utf8_pp;

            if (!unicode_pp)
            {
                PyErr_Clear();
                break;
            }

            utf8_pp = PyUnicode_AsUTF8String(unicode_pp);
            Py_DECREF(unicode_pp);

            if (!utf8_pp || !PyString_CheckExact(utf8_pp))
            {
                Py_XDECREF(utf8_pp);
                PyErr_Clear();
                break;
            }

            char *p = PyString_AS_STRING(utf8_pp);
            int l = strlen(p);

            if (l > 13 && !strcmp(p + l - 13, "site-packages"))
            {
                jstring jp = JvNewStringUTF(p);

                if (jp)
                    props->put(JvNewStringUTF("gnu.classpath.home.url"),
                               JvNewStringUTF("file:")->concat(jp->replace('\\','/')));
                Py_DECREF(utf8_pp);
                break;
            }

            Py_DECREF(utf8_pp);
        }

        Py_DECREF(path);
    }
    Py_DECREF(sys);

    return 0;
}

PyObject *PyErr_SetJavaError(java::lang::Throwable *e)
{
    PyObject *err = wrap_Throwable(e);

    PyErr_SetObject(PyExc_JavaError, err);
    Py_DECREF(err);

    return NULL;
}

PyObject *PyErr_SetArgsError(char *name, PyObject *args)
{
    PyObject *err = Py_BuildValue("(sO)", name, args);

    PyErr_SetObject(PyExc_InvalidArgsError, err);
    Py_DECREF(err);

    return NULL;
}

PyObject *PyErr_SetArgsError(PyObject *self, char *name, PyObject *args)
{
    PyObject *type = (PyObject *) self->ob_type;
    PyObject *err = Py_BuildValue("(OsO)", type, name, args);

    PyErr_SetObject(PyExc_InvalidArgsError, err);
    Py_DECREF(err);

    return NULL;
}

PyObject *PyErr_SetArgsError(PyTypeObject *type, char *name, PyObject *args)
{
    PyObject *err = Py_BuildValue("(OsO)", type, name, args);

    PyErr_SetObject(PyExc_InvalidArgsError, err);
    Py_DECREF(err);

    return NULL;
}

int abstract_init(PyObject *self, PyObject *args, PyObject *kwds)
{
    PyObject *err =
        Py_BuildValue("(sO)", "instantiating java class", self->ob_type);

    PyErr_SetObject(PyExc_NotImplementedError, err);
    Py_DECREF(err);

    return -1;
}


int _parseArgs(PyObject **args, int count, char *types, ...)
{
    va_list list;

    if (count != strlen(types))
        return -1;

    va_start(list, types);
    for (int i = 0; i < count; i++) {
        PyObject *arg = args[i];
        
        switch (types[i]) {
          case 'J':           /* Java object */
          {
              jclass cls = va_arg(list, jclass);
              
              if (arg == Py_None)
                  break;

              if (PyObject_TypeCheck(arg, &ObjectType) &&
                  cls->isInstance(((j_object *) arg)->object))
                  break;

              return -1;
          }

          case 'K':           /* Java object array */
          {
              jclass cls = va_arg(list, jclass);
              
              if (PySequence_Check(arg))
              {
                  if (PySequence_Length(arg) > 0)
                  {
                      PyObject *obj = PySequence_GetItem(arg, 0);
                      int ok = (obj == Py_None ||
                                PyObject_TypeCheck(obj, &ObjectType) &&
                                cls->isInstance(((j_object *) obj)->object));

                      Py_DECREF(obj);
                      if (ok)
                          break;
                  }
                }
                else
                    break;
          
              return -1;
          }

          case 'E':           /* Python extension object */
          {
              int (*check)(PyObject *) = va_arg(list, int (*)(PyObject *));

              if (check(arg))
                  break;

              return -1;
          }

          case 'P':           /* Java or Python extension object */
          {
              jclass cls = va_arg(list, jclass);
              int (*check)(PyObject *) = va_arg(list, int (*)(PyObject *));

              if (arg == Py_None)
                  break;
              if (PyObject_TypeCheck(arg, &ObjectType) &&
                  cls->isInstance(((j_object *) arg)->object))
                  break;
              if (check(arg))
                  break;

              return -1;
          }

          case 'Q':           /* Java or Python extension object array */
          {
              jclass cls = va_arg(list, jclass);
              int (*check)(PyObject *) = va_arg(list, int (*)(PyObject *));
              
              if (PySequence_Check(arg))
              {
                  if (PySequence_Length(arg) > 0)
                  {
                      PyObject *obj = PySequence_GetItem(arg, 0);
                      int ok = 0;

                      if (obj == Py_None)
                          ok = 1;
                      else if (PyObject_TypeCheck(obj, &ObjectType) &&
                               cls->isInstance(((j_object *) obj)->object))
                          ok = 1;
                      else if (check(obj))
                          ok = 1;

                      Py_DECREF(obj);
                      if (ok)
                          break;
                  }
                }
                else
                    break;
          
              return -1;
          }

          case 'B':           /* boolean, strict */
            if (arg == Py_True || arg == Py_False)
                break;
            return -1;

          case 'a':           /* byte */
            if (PyString_Check(arg) && (PyString_Size(arg) == 1))
                break;
            return -1;

          case 'A':           /* byte array */
            if (arg == Py_None || PyString_Check(arg))
                break;
            return -1;

          case 'b':           /* boolean */
            break;

          case 'c':           /* char */
            if (PyUnicode_Check(arg) && PyUnicode_GET_SIZE(arg) == 1)
                break;
            return -1;

          case 'C':           /* char array */
            if (arg == Py_None || PyString_Check(arg) || PyUnicode_Check(arg))
                break;
            return -1;

          case 'i':           /* int */
            if (PyInt_Check(arg))
                break;
            return -1;

          case 'd':           /* double */
            if (PyFloat_Check(arg) || PyInt_Check(arg) || PyLong_Check(arg))
                break;
            return -1;

          case 'L':           /* long long */
            if (PyLong_Check(arg) || PyInt_Check(arg))
                break;
            return -1;

          case 's':           /* string  */
            if (arg == Py_None || PyString_Check(arg) || PyUnicode_Check(arg))
                break;
            return -1;

          case 'S':           /* string array */
            if (arg == Py_None)
                break;
            if (PySequence_Check(arg))
            {
                if (PySequence_Length(arg) > 0)
                {
                    PyObject *obj = PySequence_GetItem(arg, 0);
                    int ok = (obj == Py_None ||
                              PyString_Check(obj) || PyUnicode_Check(obj));

                    Py_DECREF(obj);
                    if (ok)
                        break;
                }
                else
                    break;
            }
            return -1;

          default:
            return -1;
        }
    }

    for (int i = 0; i < count; i++) {
        PyObject *arg = args[i];
        
        switch (types[i]) {
          case 'J':           /* Java object */
          {
              jobject *obj = va_arg(list, jobject *);
              *obj = arg == Py_None ? NULL : ((j_object *) arg)->object;
              break;
          }

          case 'K':           /* Java object array */
          {
              jobjectArray *array = va_arg(list, jobjectArray *);
              jclass cls = va_arg(list, jclass);
              *array = pl2ja(arg, cls);
              if (!*array)
                  return -1;
              break;
          }

          case 'E':           /* Python extension object */
          {
              jlong *ptr = va_arg(list, jlong *);
              *(PyObject **) ptr = arg;
              break;
          }

          case 'P':           /* Java or Python extension object */
          {
              jobject *obj = va_arg(list, jobject *);
              jobject (*make)(PyObject *) =
                  va_arg(list, jobject (*)(PyObject *));

              if (arg == Py_None)
                  *obj = NULL;
              else if (PyObject_TypeCheck(arg, &ObjectType))
                  *obj = ((j_object *) arg)->object;
              else
                  *obj = make(arg);
              break;
          }

          case 'Q':           /* Java or Python extension object array */
          {
              jobjectArray *array = va_arg(list, jobjectArray *);
              jclass cls = va_arg(list, jclass);
              int (*check)(PyObject *) = va_arg(list, int (*)(PyObject *));
              jobject (*make)(PyObject *) =
                  va_arg(list, jobject (*)(PyObject *));
              *array = pl2ja(arg, cls, check, make);
              if (!*array)
                  return -1;
              break;
          }

          case 'a':           /* byte */
          {
              jbyte *a = va_arg(list, jbyte *);
              *a = (jbyte) PyString_AS_STRING(arg)[0];
              break;
          }

          case 'A':           /* byte array */
          {
              jbyteArray *a = va_arg(list, jbyteArray *);
              if (arg == Py_None)
                  *a = JvNewByteArray(0);
              else
              {   
                  jstring string = p2j(arg);
                  if (!string && PyErr_Occurred())
                      return -1;
                  *a = string->getBytes();
              }
              break;
          }

          case 'B':           /* boolean, strict */
          case 'b':           /* boolean */
          {
              jboolean *b = va_arg(list, jboolean *);
              *b = PyObject_IsTrue(arg);
              break;
          }

          case 'c':           /* char */
          {
              jchar *c = va_arg(list, jchar *);
              Py_UNICODE *u = PyUnicode_AS_UNICODE(arg);
              *c = (jchar) u[0];
              break;
          }

          case 'C':           /* char array */
          {
              jcharArray *c = va_arg(list, jcharArray *);
              if (arg == Py_None)
                  *c = JvNewCharArray(0);
              else
              {   
                  jstring string = p2j(arg);
                  if (!string && PyErr_Occurred())
                      return -1;
                  *c = string->toCharArray();
                  break;
              }
          }

          case 'i':           /* int */
          {
              jint *n = va_arg(list, jint *);
              *n = PyInt_AsLong(arg);
              break;
          }

          case 'd':           /* double */
          {
              jdouble *d = va_arg(list, jdouble *);
              if (PyFloat_Check(arg))
                  *d = PyFloat_AsDouble(arg);
              else if (PyInt_Check(arg))
                  *d = (double) PyInt_AsLong(arg);
              else
                  *d = PyLong_AsDouble(arg);
              break;
          }

          case 'L':           /* long long */
          {
              jlong *l = va_arg(list, jlong *);
              *l = PyLong_AsLongLong(arg);
              break;
          }

          case 's':           /* string  */
          {
              jstring *str = va_arg(list, jstring *);
              if (arg == Py_None)
                  *str = NULL;
              else
              {
                  *str = p2j(arg);
                  if (!*str)
                      return -1;
              }
              break;
          }

          case 'S':           /* string array */
          {
              jstringArray *strs = va_arg(list, jstringArray *);
              if (arg == Py_None)
                  *strs = NULL;
              else
              {
                  *strs = psl2jsa(arg);
                  if (!*strs)
                      return -1;
              }
              break;
          }

          default:
            return -1;
        }
    }

    return 0;
}
