import py, pytest

from pypy.module.cpyext.test.test_api import BaseApiTest
from pypy.module.cpyext.test.test_cpyext import AppTestCpythonExtensionBase
from rpython.rtyper.lltypesystem import rffi, lltype
from pypy.module.cpyext.api import (
    Py_LT, Py_LE, Py_NE, Py_EQ, Py_GE, Py_GT)

class TestObject(BaseApiTest):
    def test_IsTrue(self, space, api):
        assert api.PyObject_IsTrue(space.wrap(1.0)) == 1
        assert api.PyObject_IsTrue(space.wrap(False)) == 0
        assert api.PyObject_IsTrue(space.wrap(0)) == 0

    def test_Not(self, space, api):
        assert api.PyObject_Not(space.wrap(False)) == 1
        assert api.PyObject_Not(space.wrap(0)) == 1
        assert api.PyObject_Not(space.wrap(True)) == 0
        assert api.PyObject_Not(space.wrap(3.14)) == 0

    def test_exception(self, space, api):
        w_obj = space.appexec([], """():
            class C:
                def __nonzero__(self):
                    raise ValueError
            return C()""")

        assert api.PyObject_IsTrue(w_obj) == -1
        assert api.PyObject_Not(w_obj) == -1
        api.PyErr_Clear()

    def test_HasAttr(self, space, api):
        hasattr_ = lambda w_obj, name: api.PyObject_HasAttr(w_obj,
                                                            space.wrap(name))
        assert hasattr_(space.wrap(''), '__len__')
        assert hasattr_(space.w_int, '__eq__')
        assert not hasattr_(space.w_int, 'nonexistingattr')

        buf = rffi.str2charp('__len__')
        assert api.PyObject_HasAttrString(space.w_str, buf)
        assert not api.PyObject_HasAttrString(space.w_int, buf)
        rffi.free_charp(buf)

    def test_SetAttr(self, space, api):
        w_obj = space.appexec([], """():
            class C:
                pass
            return C()""")

        api.PyObject_SetAttr(w_obj, space.wrap('test'), space.wrap(5))
        assert not api.PyErr_Occurred()
        assert space.unwrap(space.getattr(w_obj, space.wrap('test'))) == 5
        assert api.PyObject_HasAttr(w_obj, space.wrap('test'))
        api.PyObject_SetAttr(w_obj, space.wrap('test'), space.wrap(10))
        assert space.unwrap(space.getattr(w_obj, space.wrap('test'))) == 10

        buf = rffi.str2charp('test')
        api.PyObject_SetAttrString(w_obj, buf, space.wrap(20))
        rffi.free_charp(buf)
        assert space.unwrap(space.getattr(w_obj, space.wrap('test'))) == 20

    def test_getattr(self, space, api):
        charp1 = rffi.str2charp("__len__")
        charp2 = rffi.str2charp("not_real")
        assert api.PyObject_GetAttrString(space.wrap(""), charp1)
        assert not api.PyObject_GetAttrString(space.wrap(""), charp2)
        assert api.PyErr_Occurred() is space.w_AttributeError
        api.PyErr_Clear()
        assert api.PyObject_DelAttrString(space.wrap(""), charp1) == -1
        assert api.PyErr_Occurred() is space.w_AttributeError
        api.PyErr_Clear()
        rffi.free_charp(charp1)
        rffi.free_charp(charp2)

        assert api.PyObject_GetAttr(space.wrap(""), space.wrap("__len__"))
        assert api.PyObject_DelAttr(space.wrap(""), space.wrap("__len__")) == -1
        api.PyErr_Clear()

    def test_getitem(self, space, api):
        w_t = space.wrap((1, 2, 3, 4, 5))
        assert space.unwrap(api.PyObject_GetItem(w_t, space.wrap(3))) == 4

        w_d = space.newdict()
        space.setitem(w_d, space.wrap("a key!"), space.wrap(72))
        assert space.unwrap(api.PyObject_GetItem(w_d, space.wrap("a key!"))) == 72

        assert api.PyObject_SetItem(w_d, space.wrap("key"), space.w_None) == 0
        assert space.getitem(w_d, space.wrap("key")) is space.w_None

        assert api.PyObject_DelItem(w_d, space.wrap("key")) == 0
        assert api.PyObject_GetItem(w_d, space.wrap("key")) is None
        assert api.PyErr_Occurred() is space.w_KeyError
        api.PyErr_Clear()

    def test_size(self, space, api):
        assert api.PyObject_Size(space.newlist([space.w_None])) == 1

    def test_str(self, space, api):
        w_list = space.newlist([space.w_None, space.wrap(42)])
        assert space.str_w(api.PyObject_Str(None)) == "<NULL>"
        assert space.str_w(api.PyObject_Str(w_list)) == "[None, 42]"
        assert space.str_w(api.PyObject_Str(space.wrap("a"))) == "a"

    def test_repr(self, space, api):
        w_list = space.newlist([space.w_None, space.wrap(42)])
        assert space.str_w(api.PyObject_Repr(None)) == "<NULL>"
        assert space.str_w(api.PyObject_Repr(w_list)) == "[None, 42]"
        assert space.str_w(api.PyObject_Repr(space.wrap("a"))) == "'a'"

    def test_RichCompare(self, space, api):
        def compare(w_o1, w_o2, opid):
            res = api.PyObject_RichCompareBool(w_o1, w_o2, opid)
            w_res = api.PyObject_RichCompare(w_o1, w_o2, opid)
            assert space.is_true(w_res) == res
            return res

        def test_compare(o1, o2):
            w_o1 = space.wrap(o1)
            w_o2 = space.wrap(o2)

            for opid, expected in [
                    (Py_LT, o1 <  o2), (Py_LE, o1 <= o2),
                    (Py_NE, o1 != o2), (Py_EQ, o1 == o2),
                    (Py_GT, o1 >  o2), (Py_GE, o1 >= o2)]:
                assert compare(w_o1, w_o2, opid) == expected

        test_compare(1, 2)
        test_compare(2, 2)
        test_compare('2', '1')
        
        w_i = space.wrap(1)
        assert api.PyObject_RichCompareBool(w_i, w_i, 123456) == -1
        assert api.PyErr_Occurred() is space.w_SystemError
        api.PyErr_Clear()
        
    def test_IsInstance(self, space, api):
        assert api.PyObject_IsInstance(space.wrap(1), space.w_int) == 1
        assert api.PyObject_IsInstance(space.wrap(1), space.w_float) == 0
        assert api.PyObject_IsInstance(space.w_True, space.w_int) == 1
        assert api.PyObject_IsInstance(
            space.wrap(1), space.newtuple([space.w_int, space.w_float])) == 1
        assert api.PyObject_IsInstance(space.w_type, space.w_type) == 1
        assert api.PyObject_IsInstance(space.wrap(1), space.w_None) == -1
        api.PyErr_Clear()

    def test_IsSubclass(self, space, api):
        assert api.PyObject_IsSubclass(space.w_type, space.w_type) == 1
        assert api.PyObject_IsSubclass(space.w_type, space.w_object) == 1
        assert api.PyObject_IsSubclass(space.w_object, space.w_type) == 0
        assert api.PyObject_IsSubclass(
            space.w_type, space.newtuple([space.w_int, space.w_type])) == 1
        assert api.PyObject_IsSubclass(space.wrap(1), space.w_type) == -1
        api.PyErr_Clear()

    def test_fileno(self, space, api):
        assert api.PyObject_AsFileDescriptor(space.wrap(1)) == 1
        assert api.PyObject_AsFileDescriptor(space.wrap(-20)) == -1
        assert api.PyErr_Occurred() is space.w_ValueError
        api.PyErr_Clear()

        w_File = space.appexec([], """():
            class File:
                def fileno(self):
                    return 42
            return File""")
        w_f = space.call_function(w_File)
        assert api.PyObject_AsFileDescriptor(w_f) == 42
    
    def test_hash(self, space, api):
        assert api.PyObject_Hash(space.wrap(72)) == 72
        assert api.PyObject_Hash(space.wrap(-1)) == -1
        assert (api.PyObject_Hash(space.wrap([])) == -1 and
            api.PyErr_Occurred() is space.w_TypeError)
        api.PyErr_Clear()

    def test_hash_double(self, space, api):
        assert api._Py_HashDouble(72.0) == 72

    def test_type(self, space, api):
        assert api.PyObject_Type(space.wrap(72)) is space.w_int

    def test_compare(self, space, api):
        assert api.PyObject_Compare(space.wrap(42), space.wrap(72)) == -1
        assert api.PyObject_Compare(space.wrap(72), space.wrap(42)) == 1
        assert api.PyObject_Compare(space.wrap("a"), space.wrap("a")) == 0

    def test_cmp(self, space, api):
        w = space.wrap
        with lltype.scoped_alloc(rffi.INTP.TO, 1) as ptr:
            assert api.PyObject_Cmp(w(42), w(72), ptr) == 0
            assert ptr[0] == -1
            assert api.PyObject_Cmp(w("a"), w("a"), ptr) == 0
            assert ptr[0] == 0
            assert api.PyObject_Cmp(w(u"\xe9"), w("\xe9"), ptr) < 0
            assert api.PyErr_Occurred()
            api.PyErr_Clear()

    def test_unicode(self, space, api):
        assert space.unwrap(api.PyObject_Unicode(None)) == u"<NULL>"
        assert space.unwrap(api.PyObject_Unicode(space.wrap([]))) == u"[]"
        assert space.unwrap(api.PyObject_Unicode(space.wrap("e"))) == u"e"
        assert api.PyObject_Unicode(space.wrap("\xe9")) is None
        api.PyErr_Clear()

    def test_dir(self, space, api):
        w_dir = api.PyObject_Dir(space.sys)
        assert space.isinstance_w(w_dir, space.w_list)
        assert space.contains_w(w_dir, space.wrap('modules'))

    def test_format(self, space, api):
        w_int = space.wrap(42)
        fmt = space.str_w(api.PyObject_Format(w_int, space.wrap('#b')))
        assert fmt == '0b101010'

class AppTestObject(AppTestCpythonExtensionBase):
    def setup_class(cls):
        AppTestCpythonExtensionBase.setup_class.im_func(cls)
        tmpname = str(py.test.ensuretemp('out', dir=0))
        if cls.runappdirect:
            cls.tmpname = tmpname
        else:
            cls.w_tmpname = cls.space.wrap(tmpname)

    def test_object_malloc(self):
        module = self.import_extension('foo', [
            ("malloctest", "METH_NOARGS",
             """
                 PyObject *obj = PyObject_MALLOC(sizeof(PyIntObject));
                 obj = PyObject_Init(obj, &PyInt_Type);
                 if (obj != NULL)
                     ((PyIntObject *)obj)->ob_ival = -424344;
                 return obj;
             """)])
        x = module.malloctest()
        assert type(x) is int
        assert x == -424344

    @pytest.mark.skipif(True, reason='realloc not fully implemented')
    def test_object_realloc(self):
        module = self.import_extension('foo', [
            ("realloctest", "METH_NOARGS",
             """
                 PyObject * ret;
                 char *copy, *orig = PyObject_MALLOC(12);
                 memcpy(orig, "hello world", 12);
                 copy = PyObject_REALLOC(orig, 15);
                 if (copy == NULL)
                     Py_RETURN_NONE;
                 ret = PyString_FromStringAndSize(copy, 12);
                 if (copy != orig)
                     PyObject_Free(copy);
                 PyObject_Free(orig);
                 return ret;  
             """)])
        x = module.realloctest()
        assert x == 'hello world\x00'

    def test_TypeCheck(self):
        module = self.import_extension('foo', [
            ("typecheck", "METH_VARARGS",
             """
                 PyObject *obj = PyTuple_GET_ITEM(args, 0);
                 PyTypeObject *type = (PyTypeObject *)PyTuple_GET_ITEM(args, 1);
                 return PyBool_FromLong(PyObject_TypeCheck(obj, type));
             """)])
        assert module.typecheck(1, int)
        assert module.typecheck('foo', str)
        assert module.typecheck('foo', object)
        assert module.typecheck(1L, long)
        assert module.typecheck(True, bool)
        assert module.typecheck(1.2, float)
        assert module.typecheck(int, type)

    def test_print(self):
        module = self.import_extension('foo', [
            ("dump", "METH_VARARGS",
             """
                 PyObject *fname = PyTuple_GetItem(args, 0);
                 PyObject *obj = PyTuple_GetItem(args, 1);

                 FILE *fp = fopen(PyString_AsString(fname), "wb");
                 int ret;
                 if (fp == NULL)
                     Py_RETURN_NONE;
                 ret = PyObject_Print(obj, fp, Py_PRINT_RAW);
                 fclose(fp);
                 if (ret < 0)
                     return NULL;
                 Py_RETURN_TRUE;
             """)])
        assert module.dump(self.tmpname, None)
        assert open(self.tmpname).read() == 'None'

    def test_issue1970(self):
        module = self.import_extension('foo', [
            ("ismapping", "METH_O",
             """
                 PyObject* collections_mod =
                     PyImport_ImportModule("collections");
                 PyObject* mapping_t = PyObject_GetAttrString(
                     collections_mod, "Mapping");
                 Py_DECREF(collections_mod);
                 if (PyObject_IsInstance(args, mapping_t)) {
                     Py_DECREF(mapping_t);
                     Py_RETURN_TRUE;
                 } else {
                     Py_DECREF(mapping_t);
                     Py_RETURN_FALSE;
                 }
             """)])
        import collections
        assert isinstance(dict(), collections.Mapping)
        assert module.ismapping(dict())


class AppTestPyBuffer_FillInfo(AppTestCpythonExtensionBase):
    """
    PyBuffer_FillInfo populates the fields of a Py_buffer from its arguments.
    """
    def test_fillWithoutObject(self):
        """
        PyBuffer_FillInfo populates the C{buf} and C{length}fields of the
        Py_buffer passed to it.
        """
        module = self.import_extension('foo', [
                ("fillinfo", "METH_VARARGS",
                 """
    Py_buffer buf;
    PyObject *str = PyString_FromString("hello, world.");
    PyObject *result;

    if (PyBuffer_FillInfo(&buf, NULL, PyString_AsString(str), 13, 0, 0)) {
        return NULL;
    }

    /* Check a few things we want to have happened.
     */
    if (buf.buf != PyString_AsString(str)) {
        PyErr_SetString(PyExc_ValueError, "buf field not initialized");
        return NULL;
    }

    if (buf.len != 13) {
        PyErr_SetString(PyExc_ValueError, "len field not initialized");
        return NULL;
    }

    if (buf.obj != NULL) {
        PyErr_SetString(PyExc_ValueError, "obj field not initialized");
        return NULL;
    }

    /* Give back a new string to the caller, constructed from data in the
     * Py_buffer.
     */
    if (!(result = PyString_FromStringAndSize(buf.buf, buf.len))) {
        return NULL;
    }

    /* Free that string we allocated above.  result does not share storage with
     * it.
     */
    Py_DECREF(str);

    return result;
                 """)])
        result = module.fillinfo()
        assert "hello, world." == result


    def test_fillWithObject(self):
        """
        PyBuffer_FillInfo populates the C{buf}, C{length}, and C{obj} fields of
        the Py_buffer passed to it and increments the reference count of the
        object.
        """
        module = self.import_extension('foo', [
                ("fillinfo", "METH_VARARGS",
                 """
    Py_buffer buf;
    PyObject *str = PyString_FromString("hello, world.");
    PyObject *result;

    if (PyBuffer_FillInfo(&buf, str, PyString_AsString(str), 13, 0, 0)) {
        return NULL;
    }

    /* Get rid of our own reference to the object, but the Py_buffer should
     * still have a reference.
     */
    Py_DECREF(str);

    /* Give back a new string to the caller, constructed from data in the
     * Py_buffer.  It better still be valid.
     */
    if (!(result = PyString_FromStringAndSize(buf.buf, buf.len))) {
        return NULL;
    }

    /* Now the data in the Py_buffer is really no longer needed, get rid of it
     *(could use PyBuffer_Release here, but that would drag in more code than
     * necessary).
     */
    Py_DECREF(buf.obj);

    /* Py_DECREF can't directly signal error to us, but if it makes a reference
     * count go negative, it will set an error.
     */
    if (PyErr_Occurred()) {
        return NULL;
    }

    return result;
                 """)])
        result = module.fillinfo()
        assert "hello, world." == result


    def test_fillReadonly(self):
        """
        PyBuffer_FillInfo fails if WRITABLE is passed but object is readonly.
        """
        module = self.import_extension('foo', [
                ("fillinfo", "METH_VARARGS",
                 """
    Py_buffer buf;
    PyObject *str = PyString_FromString("hello, world.");
    PyObject *result;

    if (PyBuffer_FillInfo(&buf, str, PyString_AsString(str), 13,
                          1, PyBUF_WRITABLE)) {
        Py_DECREF(str);
        return NULL;
    }
    Py_DECREF(str);
    PyBuffer_Release(&buf);
    Py_RETURN_NONE;
                 """)])
        raises((BufferError, ValueError), module.fillinfo)


class AppTestPyBuffer_Release(AppTestCpythonExtensionBase):
    """
    PyBuffer_Release releases the resources held by a Py_buffer.
    """
    def test_decrefObject(self):
        """
        The PyObject referenced by Py_buffer.obj has its reference count
        decremented by PyBuffer_Release.
        """
        module = self.import_extension('foo', [
                ("release", "METH_VARARGS",
                 """
    Py_buffer buf;
    buf.obj = PyString_FromString("release me!");
    buf.buf = PyString_AsString(buf.obj);
    buf.len = PyString_Size(buf.obj);

    /* The Py_buffer owns the only reference to that string.  Release the
     * Py_buffer and the string should be released as well.
     */
    PyBuffer_Release(&buf);
    assert(!buf.obj);
    PyBuffer_Release(&buf);   /* call again, should not have any more effect */
    PyBuffer_Release(&buf);
    PyBuffer_Release(&buf);

    Py_RETURN_NONE;
                 """)])
        assert module.release() is None

