/*
py/pyext - python external object for PD and Max/MSP

Copyright (c)2002-2008 Thomas Grill (gr@grrrr.org)
For information on usage and redistribution, and for a DISCLAIMER OF ALL
WARRANTIES, see the file, "license.txt," in this distribution.  
*/

#include "pyext.h"


PyMethodDef pyext::meth_tbl[] = 
{
/*
    {"__init__", pyext::pyext__init__, METH_VARARGS, "Constructor"},
    {"__del__", pyext::pyext__del__, METH_VARARGS, "Destructor"},
*/
    {"__str__", pyext::pyext__str__, METH_VARARGS, "stringify"},
    {"_outlet", pyext::pyext_outlet, METH_VARARGS,"Send message to outlet"},
#if FLEXT_SYS == FLEXT_SYS_PD
    {"_tocanvas", pyext::pyext_tocanvas, METH_VARARGS,"Send message to canvas" },
#endif

    { "_bind", pyext::pyext_bind, METH_VARARGS,"Bind function to a receiving symbol" },
    { "_unbind", pyext::pyext_unbind, METH_VARARGS,"Unbind function from a receiving symbol" },
#ifdef FLEXT_THREADS
    { "_detach", pyext::pyext_detach, METH_VARARGS,"Set detach flag for called methods" },
    { "_stop", pyext::pyext_stop, METH_VARARGS,"Stop running threads" },
#endif
    { "_invec", pyext::pyext_invec, METH_VARARGS,"Get input vector" },
    { "_outvec", pyext::pyext_outvec, METH_VARARGS,"Get output vector" },
    {NULL, NULL, 0, NULL}        /* Sentinel */
};

PyMethodDef pyext::attr_tbl[] =
{
    { "__setattr__", pyext::pyext_setattr, METH_VARARGS,"Set class attribute" },
    { "__getattr__", pyext::pyext_getattr, METH_VARARGS,"Get class attribute" },
    { NULL, NULL,0,NULL },
};


const char *pyext::pyext_doc =
    "py/pyext - python external object for PD and Max/MSP, (C)2002-2008 Thomas Grill\n"
    "\n"
    "This is the pyext base class. Available methods:\n"
    "_outlet(self,ix,args...): Send a message to an indexed outlet\n"
#if FLEXT_SYS == FLEXT_SYS_PD
    "_tocanvas(self,args...): Send a message to the parent canvas\n"
#endif
    "_bind(self,name,func): Bind a python function to a symbol\n"
    "_unbind(self,name,func): Unbind a python function from a symbol\n"
    "_isthreaded: Query whether threading is enabled\n"
#ifdef FLEXT_THREADS
    "_detach(self,int): Define whether a called Python method has its own thread\n"
    "_stop(self): Stop running threads\n"
    "_shouldexit: Query whether threads should terminate\n"
#endif
;

/*
PyObject* pyext::pyext__init__(PyObject *,PyObject *args)
{
//    post("pyext.__init__ called");

    Py_INCREF(Py_None);
    return Py_None;
}

PyObject* pyext::pyext__del__(PyObject *,PyObject *args)
{
//    post("pyext.__del__ called");

    Py_INCREF(Py_None);
    return Py_None;
}
*/

PyObject* pyext::pyext__str__(PyObject *,PyObject *args)
{
    PyObject *self; 
    if(!PyArg_ParseTuple(args, "O:pyext__str__",&self)) {
        // handle error
        ERRINTERNAL();
        return NULL;
    }

    return PyString_FromFormat("<pyext object %p>",self);
}

PyObject* pyext::pyext_setattr(PyObject *,PyObject *args)
{
    PyObject *self,*name,*val;
    if(!PyArg_ParseTuple(args, "OOO:pyext_setattr", &self,&name,&val)) {
        // handle error
        ERRINTERNAL();
        return NULL;
    }

    bool handled = false;

/*
    if(PyString_Check(name)) {
        char* sname = PyString_AsString(name);
        if (sname) {
//          post("pyext::setattr %s",sname);
        }
    }
*/
    if(!handled) {
        if(PyInstance_Check(self)) 
            PyDict_SetItem(((PyInstanceObject *)self)->in_dict, name,val);
        else
            ERRINTERNAL();
    }

    Py_INCREF(Py_None);
    return Py_None;
}

PyObject* pyext::pyext_getattr(PyObject *,PyObject *args)
{
    PyObject *self,*name,*ret = NULL;
    if(!PyArg_ParseTuple(args, "OO:pyext_getattr", &self,&name)) {
        // handle error
        ERRINTERNAL();
    }

    if(PyString_Check(name)) {
        char* sname = PyString_AS_STRING(name);
        if(sname) {
#ifdef FLEXT_THREADS
            if(!strcmp(sname,"_shouldexit")) {
                pyext *ext = GetThis(self); 
                if(ext)
                    ret = PyLong_FromLong(ext->shouldexit?1:0);
                else {
                    // return true for _shouldexit if association has been removed
                    Py_INCREF(Py_True);
                    ret = Py_True;
                }
            }
            else 
#endif
            if(!strcmp(sname,"_isthreaded")) {
            #ifdef FLEXT_THREADS
                Py_INCREF(Py_True);
                ret = Py_True;
            #else
                Py_INCREF(Py_False);
                ret = Py_False;
            #endif
            }
        }
    }

    if(!ret) { 
#if PY_VERSION_HEX >= 0x02020000
        ret = PyObject_GenericGetAttr(self,name); // new reference (?)
#else
        if(PyInstance_Check(self))
            // borrowed reference
            ret = PyDict_GetItem(((PyInstanceObject *)self)->in_dict,name); 
#endif
    }
    return ret;
}

//! Send message to outlet
PyObject *pyext::pyext_outlet(PyObject *,PyObject *args)
{
    bool ok = false;

    // should always be a tuple!
    FLEXT_ASSERT(PyTuple_Check(args));

    int sz = PyTuple_GET_SIZE(args);

    // borrowed references!
    PyObject *self,*outl;

    if(
        sz >= 2 &&
        (self = PyTuple_GET_ITEM(args,0)) != NULL && PyInstance_Check(self) && 
        (outl = PyTuple_GET_ITEM(args,1)) != NULL && PyInt_Check(outl)
    ) {
        pyext *ext = GetThis(self);
        if(!ext) {    
            PyErr_SetString(PyExc_RuntimeError,"pyext - _outlet: instance not associated with pd object");
            return NULL;
        }

        PyObject *val;
#if 0
        if(sz == 3) {
            val = PyTuple_GET_ITEM(args,2); // borrow reference
            Py_INCREF(val);
            tp = PySequence_Check(val);
        }
        else
            tp = false;

        if(!tp)
            val = PySequence_GetSlice(args,2,sz);  // new ref
#else
        if(sz == 3) {
            val = PyTuple_GET_ITEM(args,2); // borrow reference
            Py_INCREF(val);
        }
        else
            val = PyTuple_GetSlice(args,2,sz);  // new ref
#endif

        int o = PyInt_AS_LONG(outl);
        if(o >= 1 && o <= ext->Outlets()) {
            // offset outlet by signal outlets
            o += ext->sigoutlets;

            if(ext->OutObject(ext,o-1,val))
                ok = true;
            else 
                PyErr_SetString(PyExc_ValueError,"pyext - _outlet: invalid arguments");
        }
        else
            PyErr_SetString(PyExc_ValueError,"pyext - _outlet: index out of range");

        Py_DECREF(val);
    }
    else
        PyErr_SetString(PyExc_SyntaxError,"pyext - Syntax: _outlet(self,outlet,args...)");

    if(!ok) return NULL;
    Py_INCREF(Py_None);
    return Py_None;
}



#ifdef FLEXT_THREADS
//! Detach threads
PyObject *pyext::pyext_detach(PyObject *,PyObject *args)
{
    PyObject *self; 
    int val;
    if(!PyArg_ParseTuple(args, "Oi:pyext_detach",&self,&val)) {
        // handle error
        PyErr_SetString(PyExc_SyntaxError,"pyext - Syntax: _detach(self,[0/1/2])");
        return NULL;
    }
    else if(val < 0 || val > 2) {
        PyErr_SetString(PyExc_ValueError,"pyext - _detach must be in the range 0..2");
        return NULL;
    }
    else {
        pyext *ext = GetThis(self);
        if(!ext) {    
            PyErr_SetString(PyExc_RuntimeError,"pyext - _detach: instance not associated with pd object");
            return NULL;
        }

        ext->detach = val;
    }

    Py_INCREF(Py_None);
    return Py_None;
}

//! Stop running threads
PyObject *pyext::pyext_stop(PyObject *,PyObject *args)
{
    PyObject *self; 
    int val = -1;
    if(!PyArg_ParseTuple(args, "O|i:pyext_stop",&self,&val)) {
        // handle error
        PyErr_SetString(PyExc_SyntaxError,"pyext - Syntax: _stop(self,{wait time})");
        return NULL;
    }
    else if(val < 0) {
        PyErr_SetString(PyExc_ValueError,"pyext - _stop time must be >= 0");
        return NULL;
    }
    else {
        pyext *ext = GetThis(self);
        if(!ext) {    
            PyErr_SetString(PyExc_RuntimeError,"pyext - _stop: instance not associated with pd object");
            return NULL;
        }

        int cnt;
        t_atom at;
        if(val >= 0) cnt = 1,flext::SetInt(at,val);
        else cnt = 0;
        ext->m_stop(cnt,&at);
    }

    Py_INCREF(Py_None);
    return Py_None;
}

#endif


#if FLEXT_SYS == FLEXT_SYS_PD
//! Send message to canvas
PyObject *pyext::pyext_tocanvas(PyObject *,PyObject *args)
{
    FLEXT_ASSERT(PyTuple_Check(args));

    int sz = PyTuple_GET_SIZE(args);

    bool ok = false;
    PyObject *self; // borrowed ref
    if(
        sz >= 1 &&
        (self = PyTuple_GET_ITEM(args,0)) != NULL && PyInstance_Check(self)
    ) {
        pyext *ext = GetThis(self);
        if(!ext) {
            PyErr_SetString(PyExc_RuntimeError,"pyext - _tocanvas: instance not associated with pd object");
            return NULL;
        }

        PyObject *val;

        bool tp = 
            sz == 2 && 
            PySequence_Check(
                val = PyTuple_GET_ITEM(args,1) // borrowed ref
            );

        if(!tp)
            val = PyTuple_GetSlice(args,1,sz);  // new ref

        flext::AtomListStatic<16> lst;
        const t_symbol *sym = GetPyArgs(lst,val);
        if(sym) {
            t_glist *gl = ext->thisCanvas();
            if(gl) {
                // \TODO find a flext-based non-locking method
                sys_lock();
                pd_forwardmess((t_class **)gl,lst.Count(),lst.Atoms());
                sys_unlock();
            }
#ifdef FLEXT_DEBUG
            else
                post("pyext - no parent canvas?!");
#endif
            ok = true;
        }
        else 
            post("py/pyext - No data to send");

        if(!tp) Py_DECREF(val);
    }

    if(!ok) {
        PyErr_SetString(PyExc_SyntaxError,"pyext - Syntax: _tocanvas(self,args...)");
        return NULL;
    }

    Py_INCREF(Py_None);
    return Py_None;
}
#endif

PyObject *pyext::pyext_invec(PyObject *,PyObject *args)
{
    PyObject *self; 
    int val = -1;
    if(!PyArg_ParseTuple(args, "O|i:pyext_invec",&self,&val)) {
        // handle error
        PyErr_SetString(PyExc_SyntaxError,"pyext - Syntax: _invec(self,inlet)");
        return NULL;
    }
    else if(val < 0) {
        PyErr_SetString(PyExc_ValueError,"pyext - _invec: index out of range");
        return NULL;
    }
    else {
        pyext *ext = GetThis(self);
        if(ext) {
            PyObject *b = ext->GetSig(val,true);
            if(b) return b;
        }
        else {
            PyErr_SetString(PyExc_RuntimeError,"pyext - _invec: instance not associated with pd object");
            return NULL;
        }
    }

    Py_INCREF(Py_None);
    return Py_None;
}

PyObject *pyext::pyext_outvec(PyObject *,PyObject *args)
{
    PyObject *self; 
    int val = -1;
    if(!PyArg_ParseTuple(args, "O|i:pyext_outvec",&self,&val)) {
        // handle error
        PyErr_SetString(PyExc_SyntaxError,"pyext - Syntax: _outvec(self,inlet)");
        return NULL;
    }
    else if(val < 0) {
        PyErr_SetString(PyExc_ValueError,"pyext - _outvec: index out of range");
        return NULL;
    }
    else {
        pyext *ext = GetThis(self);
        if(ext) {
            PyObject *b = ext->GetSig(val,false);
            if(b) return b;
        }
        else {
            PyErr_SetString(PyExc_RuntimeError,"pyext - _outvec: instance not associated with pd object");
            return NULL;
        }
    }

    Py_INCREF(Py_None);
    return Py_None;
}
