#include "Python.h"
/**
 * syobject.c - holds the implementation of the Sython object type.
 * This object contains two important data values--the object id and the
 * inner object type.  Please see Sython documentation for more information.
 * @author Michael Gaiman
 * @version 2/2/2005
 */

/**
 * Allocates and instantiates a new syobj.
 * ID and type must be set by the caller.
 */
static PyObject *
syobj_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    syobj *self;

    self = (syobj *)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->id=NULL;
        self->type=0;
    }

    return (PyObject *)self;
}

/**
 * A Higher level syobj allocator.
 * ID and type are supplied by the caller and are set internally.
 */
PyObject *
Sy_syobj_New(PyObject *id, char type){
    syobj* obj;
    obj=(syobj*)PyType_GenericNew(&SyObj_Type,NULL,NULL);
    if(obj==NULL){
        PyErr_SetString(PyExc_MemoryError,"Could not allocate sython object");
        return NULL;
    }
    if(!id){
        Py_DECREF(obj);
        PyErr_SetString(PyExc_TypeError,"Invalid syvarID parameter");
        return NULL;
    }
    obj->id=id;
    
    if(type=='i'){
        obj->type=SYINT;
    }
    else if(type=='s'){
        obj->type=SYSTR;
    }
    else{
        Py_DECREF(obj);
        PyErr_SetString(PyExc_TypeError,"Invalid type parameter");
        return NULL;
    }
    return (PyObject*)obj;
}

/**
 * Deallocate the syobj.
 * The is done by decreasing the reference count of id and then calling
 * the generic tp_free method.
 */
static void
syobj_dealloc(syobj* self)
{
    Py_XDECREF(self->id);
    self->ob_type->tp_free((PyObject*)self);
}

/**
 * An OP support function.
 * checks to make sure this is a supported type combination--
 * this function is for one sython one non sython combinations.
 */
static char
check_types_sup(char intsup, char strsup,int sytype, PyObject* other){
    if(intsup && sytype==SYINT){
        /*check to make sure it is an int or long*/
        if(!PyObject_TypeCheck(other,&PyInt_Type) && 
                !PyObject_TypeCheck(other,&PyLong_Type)){
            return 0;
        }
    }
    else if(strsup && sytype==SYSTR){
        /*check to make sure it is a string*/
        if(!PyObject_TypeCheck(other,&PyString_Type)){
            return 0;
        }
    }
    else{
        /*the types don't work...*/
        return 0;
    }
    return 1;
}

/**
 * An OP support function.
 * returns either the method name for the sython.comm.* method,
 * or NULL if the operation isn't support with the supplied parameters
 * If it returns non-NULL, then arg1 and arg2 will contain the two arguments
 * to be passed to the function.
 */
static char*
testparameters(char intsup,char strsup,
                PyObject* v,char vs, int vt,
                PyObject* w,char ws, int wt,
                PyObject** arg1, PyObject** arg2){
    char *methodname=NULL;
    if(vs && ws){
        /*both are sython variables*/
        if(vt!=wt){
            /*operation not supported between two different sython types*/
            return NULL;
        }
        else if(intsup && !strsup && vt==SYSTR){
            /*they're both strings for an int type...*/
            return NULL;
        }
        else if(strsup && !intsup && vt==SYINT){
            /*they're both ints for a string type...*/
            return NULL;
        }
        methodname="op2Sy";
        *arg1=((syobj*)v)->id;
        *arg2=((syobj*)w)->id;
    }
    else if(vs){
        /*the first is a sython variable, but not the second*/
        if(!check_types_sup(intsup,strsup,vt,w)){
            return NULL;
        }
        methodname="op2MixedL";
        *arg1=((syobj*)v)->id;
        *arg2=w;
    }
    else if(ws){
        /*the second is a sython variable, but not the first*/
        /*the first is a sython variable, but not the second*/
        if(!check_types_sup(intsup,strsup,wt,v)){
            return NULL;
        }
        methodname="op2MixedR";
        *arg1=((syobj*)w)->id;
        *arg2=v;
    }
    return methodname;
}

/**
 * An OP support function.
 * Builds the argument tuple and calls the sython.comm.methodname.
 */
static PyObject*
buildandcall(char *methodname, char *op, PyObject* arg1, PyObject* arg2){
    PyObject *arglist, *ret;
    /*build the argument list for the python function now*/
    if(arg2==NULL){
        arglist=Py_BuildValue("(sO)",op,arg1);
    }
    else{
        arglist=Py_BuildValue("(sOO)",op,arg1,arg2);
    }
    if(arglist==NULL){
        return NULL;
    }
    ret=Sy_CallObject("sython.comm",methodname,arglist);
    Py_DECREF(arglist);
    return ret;
}

/**
 * An OP support function.
 * Calls the sython.comm.methodname after putting together the arguments.
 * returns a new syobj with a correct ID.  Note: syobj->type isn't set for this
 * object.
 * If this is a single argument method, set arg2 to NULL.
 */
static syobj*
getopcall(char *methodname, char *op, PyObject* arg1, PyObject* arg2){
    PyObject *ret;
    syobj *obj;
    /*build the argument list for the python function now*/
    ret=buildandcall(methodname,op,arg1,arg2);
    if(ret==NULL){
        return NULL;
    }
    /*build a syobj now with this new syvarID*/
    obj=(syobj*)PyType_GenericNew(&SyObj_Type,NULL,NULL);
    Py_INCREF(ret);
    obj->id=ret;
    return obj;
}

/**
 * An OP support function.
 * Does most of the work for binary operations. just tell the operation,
 * and whether it supports int, chars, or both and it will return you the
 * stuff needed.
 */
static PyObject*
doop_binary(char* op,PyObject* v, PyObject* w,char intsup, char strsup){
    char *methodname;
    PyObject *arg1, *arg2;
    syobj *obj;
    int vt=0,wt=0; /*sython type*/
    char vs=0,ws=0;
    
    /*check to see if they're sython variables*/
    if(PyObject_TypeCheck(v,&SyObj_Type)){
        vs=1;
        vt=((syobj*)v)->type;
    }
    if(PyObject_TypeCheck(w,&SyObj_Type)){
        ws=1;
        wt=((syobj*)w)->type;
    }
    
    /*get the methodname, and make sure this is a valid pair of operands*/
    methodname=testparameters(intsup,strsup,v,vs,vt,w,ws,wt,&arg1,&arg2);
    if(!methodname){/*unsupported combination...*/
        Py_INCREF(Py_NotImplemented);
        return Py_NotImplemented;
    }
    
    obj=getopcall(methodname,op,arg1,arg2);
    if(obj==NULL){
        return NULL;
    }
    if(vs){/*adopt the type of the sython variable*/
        obj->type=vt;
    }
    else{
        obj->type=wt;
    }
    return (PyObject*)obj;
}

/**
 * An OP support function.
 * Does most of the work for unary operations. just tell the operation,
 * and whether it supports int, chars, or both and it will return you the
 * stuff needed.
 */
static PyObject*
doop_unary(char* op,PyObject* v,char intsup, char strsup){
    PyObject *arg;
    syobj *obj;
    int vt=0; /*sython type*/
    
    /*check to see if they're sython variables*/
    if(PyObject_TypeCheck(v,&SyObj_Type)){
        vt=((syobj*)v)->type;
        arg=((syobj*)v)->id;
    }
    else{/*not a sython variable--hmmm*/
        PyErr_SetString(PyExc_TypeError,"unsupported operand type for operation");
        return NULL;
    }
    
    /*check to make sure it is a supported operation*/
    if(intsup && !strsup && vt==SYSTR){
        /*string for an int type...*/
        PyErr_SetString(PyExc_TypeError,"unsupported operand type for operation");
        return NULL;
    }
    else if(strsup && !intsup && vt==SYINT){
        /*int for a string type...*/
        PyErr_SetString(PyExc_TypeError,"unsupported operand type for operation");
        return NULL;
    }
    
    obj=getopcall("op",op,arg,NULL);
    if(obj==NULL){
        return NULL;
    }
    /*adopt the type of the sython variable*/
    obj->type=vt;
    return (PyObject*)obj;
}

/**
 * The + Operation.
 * This function does all of the x + y work, for both addition and appending.
 * This makes the whole process a little more tedious as it must do work to check
 * for strings, too.
 * Supports both ints and strings.
 */
static PyObject *
sy_plus(PyObject *v, PyObject *w)
{
    /*supports both ints and strings*/
    return doop_binary("+",v,w,1,1);
}

/**
 * The - Operation.
 * This function performs subtraction.
 * Supports ints.
 */
static PyObject *
sy_minus(PyObject *v, PyObject *w)
{
    /*only supports ints*/
    return doop_binary("-",v,w,1,0);
}

/**
 * The * Operation.
 * This function performs multiplication.
 * Supports only ints, this is due to the difficulty of securing ints when 
 * multiplying them with strings as the length will then more or less give away
 * the value of the int...
 */
static PyObject *
sy_asterisk(PyObject *v, PyObject *w)
{
    /*onlys supports ints*/
    return doop_binary("*",v,w,1,0);
}

/**
 * The / Operation.
 * This function performs division.
 * Supports ints.
 */
static PyObject *
sy_forwardslash(PyObject *v, PyObject *w)
{
    /*only supports ints*/
    return doop_binary("/",v,w,1,0);
}

/**
 * The % Operation.
 * This function performs modulus.
 * Supports ints.
 */
static PyObject *
sy_percentsign(PyObject *v, PyObject *w)
{
    /*only supports ints*/
    return doop_binary("%",v,w,1,0);
}

/* taken out.
 * Because calculating pow with large values is slow, 
 * the responsiveness will could give away the value of the variable.
 */
/*static PyObject *
sy_pow(PyObject *v, PyObject *w, PyObject *z)
{
    as pow is either v**w or (v**w) % z we'll do it the long way
    only supports ints
    
    if(z==Py_None){
        return doop_binary("pow",v,w,1,0);
    }
    else{
        PyObject *x,*y;
        x=doop_binary("pow",v,w,1,0);
        if(x==NULL || x==Py_NotImplemented){
            return x;
        }
        y=doop_binary("%",x,z,1,0);
        Py_DECREF(x);
        return y;
    }
}*/

/**
 * The + Unary Operation.
 * This function performs the positive operation.
 * Supports ints.
 */
static PyObject *
sy_positive(PyObject *v)
{
    /*only supports ints*/
    return doop_unary("+",v,1,0);
}

/**
 * The - Unary Operation.
 * This function performs the negation operation.
 * Supports ints.
 */
static PyObject *
sy_negative(PyObject *v)
{
    /*only supports ints*/
    return doop_unary("-",v,1,0);
}

/**
 * The abs Unary Operation.
 * This function performs the absolute value operation.
 * Supports ints.
 */
static PyObject *
sy_abs(PyObject *v)
{
    /*only supports ints*/
    return doop_unary("abs",v,1,0);
}

/**
 * Data structure that sets up the numeric operations
 * syobj supports.
 */
static PyNumberMethods sy_as_number = {
	(binaryfunc)sy_plus,            /*nb_add*/
	(binaryfunc)sy_minus,           /*nb_subtract*/
	(binaryfunc)sy_asterisk,	/*nb_multiply*/
	(binaryfunc)sy_forwardslash,    /*nb_divide*/
	(binaryfunc)sy_percentsign,	/*nb_remainder*/
	0,      /*nb_divmod*/
	0,/*(ternaryfunc)sy_pow,*/            /*nb_power*/
	(unaryfunc)sy_negative,         /*nb_negative*/
	(unaryfunc)sy_positive,         /*nb_positive*/
	(unaryfunc)sy_abs,              /*nb_absolute*/
	0,                      /*nb_nonzero*/
	0,	/*nb_invert*/
	0,	/*nb_lshift*/
	0,	/*nb_rshift*/
	0,	/*nb_and*/
	0,	/*nb_xor*/
	0,	/*nb_or*/
	0,                      /*nb_coerce*/
	0,                      /*nb_int*/
	0,                      /*nb_long*/
	0,                      /*nb_float*/
	0,                      /*nb_oct*/
	0,                      /*nb_hex*/
	0,			/*nb_inplace_add*/
	0,			/*nb_inplace_subtract*/
	0,			/*nb_inplace_multiply*/
	0,			/*nb_inplace_divide*/
	0,			/*nb_inplace_remainder*/
	0,			/*nb_inplace_power*/
	0,			/*nb_inplace_lshift*/
	0,			/*nb_inplace_rshift*/
	0,			/*nb_inplace_and*/
	0,			/*nb_inplace_xor*/
	0,			/*nb_inplace_or*/
	0,                      /* nb_floor_divide */
	0,                      /* nb_true_divide */
	0,			/* nb_inplace_floor_divide */
	0,			/* nb_inplace_true_divide */
};

/**
 * The len Unary Operation.
 * This function performs the string length operation.
 * Supports strings.
 */
static int
sy_str_len(PyObject *v)
{
    /*because this is the only function that returns an int instead of a
     *PyObject* we're it all here. */
    PyObject *arg;
    PyObject *obj;
    int vt=0; /*sython type*/
    /*check to see if they're sython variables*/
    if(PyObject_TypeCheck(v,&SyObj_Type)){
        vt=((syobj*)v)->type;
        arg=((syobj*)v)->id;
    }
    else{/*not a sython variable--hmmm*/
        return -1;
    }
    if(vt!=SYSTR){
        return -1;
    }
    obj=buildandcall("op","len",arg,NULL);
    if(obj==NULL){
        return -1;
    }
    return (int)PyInt_AsLong(obj);
}

/**
 * Data structure that sets up the string operations
 * syobj supports.  Note: syobj supports concat, but it is handled
 * transpartently by the int + OP.
 */
static PySequenceMethods sy_as_sequence = {
	(inquiry)sy_str_len, /*sq_length*/
	0, /*sq_concat*/ /* <- we do support concat, but through sy_plus*/
	0, /*sq_repeat*/ /* <- we do support repeat, but through sy_asterisk*/
	0, /*sq_item*/
	0, /*sq_slice*/
	0,		/*sq_ass_item*/
	0,		/*sq_ass_slice*/
	0 /*sq_contains*/
};

/**
 * A syobj python method.
 * Returns PyObject 1 if the syobj is a string, 0 otherwise.
 */
static PyObject *
syobj_isstr(syobj* self){
    return PyInt_FromLong((long)(self->type==SYSTR));

}

/**
 * A syobj python method.
 * Returns PyObject 1 if the syobj is a int, 0 otherwise.
 */
static PyObject *
syobj_isint(syobj* self){
    return PyInt_FromLong((long)(self->type==SYINT));
}

/**
 * Data structure that sets up the .methods that syobj supports.
 */
static PyMethodDef syobj_methods[] = {
    {"isstr", (PyCFunction)syobj_isstr, METH_NOARGS,
     "Returns True if this syobj represents a string."
    },
    {"isint", (PyCFunction)syobj_isint, METH_NOARGS,
     "Returns True if this syobj represents an int."
    },
    {NULL}  /* Sentinel */
};

/**
 * Data structure that sets up the syobj.
 */
PyTypeObject SyObj_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                         /*ob_size*/
    "syobj",                   /*tp_name*/
    sizeof(syobj),             /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor)syobj_dealloc, /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_compare*/
    0,                         /*tp_repr*/
    &sy_as_number,             /*tp_as_number*/
    &sy_as_sequence,           /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_CHECKTYPES,/*tp_flags*/
    "Sython object",           /* tp_doc */
    0,		               /* tp_traverse */
    0,		               /* tp_clear */
    0,		               /* tp_richcompare */
    0,		               /* tp_weaklistoffset */
    0,		               /* tp_iter */
    0,		               /* tp_iternext */
    syobj_methods,             /* tp_methods */
    0,                         /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    0,                         /* tp_init */
    0,                         /* tp_alloc */
    syobj_new,                 /* tp_new */
};

