//--------------------------------------------------------------------------

static PyObject* wxPyGetMethod(PyObject* py, char* name)
{
    if (!PyObject_HasAttrString(py, name))
        return NULL;
    PyObject* o = PyObject_GetAttrString(py, name);
    if (!PyMethod_Check(o) && !PyCFunction_Check(o)) {
        Py_DECREF(o);
        return NULL;
    }
    return o;
}

#define wxPyBlock_t_default PyGILState_UNLOCKED


// This class can wrap a Python file-like object and allow it to be used
// as a wxInputStream.
class wxPyInputStream : public wxInputStream
{
public:

    // Make sure there is at least a read method
    static bool Check(PyObject* fileObj)
    {
        PyObject* method = wxPyGetMethod(fileObj, "read");
        bool rval = method != NULL;
        Py_XDECREF(method);
        return rval;
    }

    wxPyInputStream(PyObject* fileObj, bool block=true)
    {
        m_block = block;
        wxPyThreadBlocker blocker(m_block);

        m_read = wxPyGetMethod(fileObj, "read");
        m_seek = wxPyGetMethod(fileObj, "seek");
        m_tell = wxPyGetMethod(fileObj, "tell");
    }

    virtual ~wxPyInputStream()
    {
        wxPyThreadBlocker blocker(m_block);
        Py_XDECREF(m_read);
        Py_XDECREF(m_seek);
        Py_XDECREF(m_tell);
    }

    wxPyInputStream(const wxPyInputStream& other)
    {
        wxPyThreadBlocker blocker;
        m_read  = other.m_read;
        m_seek  = other.m_seek;
        m_tell  = other.m_tell;
        m_block = other.m_block;
        Py_INCREF(m_read);
        Py_INCREF(m_seek);
        Py_INCREF(m_tell);
    }

protected:

    // implement base class virtuals

    wxFileOffset GetLength() const
    {
        wxPyInputStream* self = (wxPyInputStream*)this; // cast off const
        if (m_seek && m_tell) {
            wxFileOffset temp = self->OnSysTell();
            wxFileOffset ret = self->OnSysSeek(0, wxFromEnd);
            self->OnSysSeek(temp, wxFromStart);
            return ret;
        }
        else
            return wxInvalidOffset;
    }

    size_t OnSysRead(void *buffer, size_t bufsize)
    {
        if (bufsize == 0)
            return 0;

        wxPyThreadBlocker blocker;
        PyObject* arglist = Py_BuildValue("(i)", bufsize);
        PyObject* result = PyEval_CallObject(m_read, arglist);
        Py_DECREF(arglist);

        size_t o = 0;
        if ((result != NULL) && PyBytes_Check(result)) {
            o = PyBytes_Size(result);
            if (o == 0)
                m_lasterror = wxSTREAM_EOF;
            if (o > bufsize)
                o = bufsize;
            memcpy((char*)buffer, PyBytes_AsString(result), o);  // strings only, not unicode...
            Py_DECREF(result);
        }
        else
            m_lasterror = wxSTREAM_READ_ERROR;
        return o;
    }

    size_t OnSysWrite(const void *buffer, size_t bufsize)
    {
        m_lasterror = wxSTREAM_WRITE_ERROR;
        return 0;
    }

    wxFileOffset OnSysSeek(wxFileOffset off, wxSeekMode mode)
    {
        wxPyThreadBlocker blocker;
        PyObject* arglist = PyTuple_New(2);

        if (sizeof(wxFileOffset) > sizeof(long))
            // wxFileOffset is a 64-bit value...
            PyTuple_SET_ITEM(arglist, 0, PyLong_FromLongLong(off));
        else
            PyTuple_SET_ITEM(arglist, 0, wxPyInt_FromLong(off));

        PyTuple_SET_ITEM(arglist, 1, wxPyInt_FromLong(mode));


        PyObject* result = PyEval_CallObject(m_seek, arglist);
        Py_DECREF(arglist);
        Py_XDECREF(result);
        return OnSysTell();
    }

    wxFileOffset OnSysTell() const
    {
        wxPyThreadBlocker blocker;
        PyObject* arglist = Py_BuildValue("()");
        PyObject* result = PyEval_CallObject(m_tell, arglist);
        Py_DECREF(arglist);
        wxFileOffset o = 0;
        if (result != NULL) {
            if (PyLong_Check(result))
                o = PyLong_AsLongLong(result);
            else
                o = wxPyInt_AsLong(result);
            Py_DECREF(result);
        };
        return o;
    }

    bool IsSeekable() const
    {
        return (m_seek != NULL);
    }


private:
    PyObject* m_read;
    PyObject* m_seek;
    PyObject* m_tell;
    bool      m_block;
};

//--------------------------------------------------------------------------
