/*
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * Contributor(s): Campbell Barton
 *
 * ***** END GPL LICENSE BLOCK *****
 * Readonly sequence wrapper for lookups on logic bricks
 */

/** \file gameengine/Ketsji/KX_PythonSeq.cpp
 *  \ingroup ketsji
 */


#ifdef WITH_PYTHON

#include "KX_PythonSeq.h"
#include "KX_GameObject.h"
#include "BL_ArmatureObject.h"
#include "SCA_ISensor.h"
#include "SCA_IController.h"
#include "SCA_IActuator.h"


PyObject *KX_PythonSeq_CreatePyObject( PyObject *base, short type )
{
	KX_PythonSeq *seq = PyObject_GC_New(KX_PythonSeq, &KX_PythonSeq_Type);
	seq->base = base;
	Py_INCREF(base); /* so we can always access to check if its valid */
	seq->type = type;
	seq->iter = -1; /* init */
	return (PyObject *)seq;
}

static int KX_PythonSeq_traverse(KX_PythonSeq *self, visitproc visit, void *arg)
{
	Py_VISIT(self->base);
	return 0;
}

static int KX_PythonSeq_clear(KX_PythonSeq *self)
{
	Py_CLEAR(self->base);
	return 0;
}

static void KX_PythonSeq_dealloc(KX_PythonSeq *self)
{
	KX_PythonSeq_clear(self);
	PyObject_GC_Del(self);
}

static Py_ssize_t KX_PythonSeq_len( PyObject *self )
{
	PyObjectPlus *self_plus= BGE_PROXY_REF(((KX_PythonSeq *)self)->base);

	if (self_plus==NULL) {
		PyErr_SetString(PyExc_SystemError, "len(seq): " BGE_PROXY_ERROR_MSG);
		return -1;
	}
	
	switch (((KX_PythonSeq *)self)->type) {
		case KX_PYGENSEQ_CONT_TYPE_SENSORS:
			return ((SCA_IController *)self_plus)->GetLinkedSensors().size();
		case KX_PYGENSEQ_CONT_TYPE_ACTUATORS:
			return ((SCA_IController *)self_plus)->GetLinkedActuators().size();
		case KX_PYGENSEQ_OB_TYPE_SENSORS:
			return ((KX_GameObject *)self_plus)->GetSensors().size();
		case KX_PYGENSEQ_OB_TYPE_CONTROLLERS:
			return ((KX_GameObject *)self_plus)->GetControllers().size();
		case KX_PYGENSEQ_OB_TYPE_ACTUATORS:
			return ((KX_GameObject *)self_plus)->GetActuators().size();
		case KX_PYGENSEQ_OB_TYPE_CONSTRAINTS:
			return ((BL_ArmatureObject *)self_plus)->GetConstraintNumber();
		case KX_PYGENSEQ_OB_TYPE_CHANNELS:
			return ((BL_ArmatureObject *)self_plus)->GetChannelNumber();
		default:
			/* Should never happen */
			PyErr_SetString(PyExc_SystemError, "invalid type, internal error");
			return -1;
	}
}

static PyObject *KX_PythonSeq_getIndex(PyObject *self, Py_ssize_t index)
{
	PyObjectPlus *self_plus= BGE_PROXY_REF(((KX_PythonSeq *)self)->base);
	 
	if (self_plus==NULL) {
		PyErr_SetString(PyExc_SystemError, "val = seq[i]: " BGE_PROXY_ERROR_MSG);
		return NULL;
	}
	
	switch (((KX_PythonSeq *)self)->type) {
		case KX_PYGENSEQ_CONT_TYPE_SENSORS:
		{
			vector<SCA_ISensor*>& linkedsensors = ((SCA_IController *)self_plus)->GetLinkedSensors();
			if (index<0) index += linkedsensors.size();
			if (index<0 || index>= linkedsensors.size()) {
				PyErr_SetString(PyExc_IndexError, "seq[i]: index out of range");
				return NULL;
			}
			return linkedsensors[index]->GetProxy();
		}
		case KX_PYGENSEQ_CONT_TYPE_ACTUATORS:
		{
			vector<SCA_IActuator*>& linkedactuators = ((SCA_IController *)self_plus)->GetLinkedActuators();
			if (index<0) index += linkedactuators.size();
			if (index<0 || index>= linkedactuators.size()) {
				PyErr_SetString(PyExc_IndexError, "seq[i]: index out of range");
				return NULL;
			}
			return linkedactuators[index]->GetProxy();
		}
		case KX_PYGENSEQ_OB_TYPE_SENSORS:
		{
			SCA_SensorList& linkedsensors= ((KX_GameObject *)self_plus)->GetSensors();
			if (index<0) index += linkedsensors.size();
			if (index<0 || index>= linkedsensors.size()) {
				PyErr_SetString(PyExc_IndexError, "seq[i]: index out of range");
				return NULL;
			}
			return linkedsensors[index]->GetProxy();
		}
		case KX_PYGENSEQ_OB_TYPE_CONTROLLERS:
		{
			SCA_ControllerList& linkedcontrollers= ((KX_GameObject *)self_plus)->GetControllers();
			if (index<0) index += linkedcontrollers.size();
			if (index<0 || index>= linkedcontrollers.size()) {
				PyErr_SetString(PyExc_IndexError, "seq[i]: index out of range");
				return NULL;
			}
			return linkedcontrollers[index]->GetProxy();
		}
		case KX_PYGENSEQ_OB_TYPE_ACTUATORS:
		{
			SCA_ActuatorList& linkedactuators= ((KX_GameObject *)self_plus)->GetActuators();
			if (index<0) index += linkedactuators.size();
			if (index<0 || index>= linkedactuators.size()) {
				PyErr_SetString(PyExc_IndexError, "seq[i]: index out of range");
				return NULL;
			}
			return linkedactuators[index]->GetProxy();
		}
		case KX_PYGENSEQ_OB_TYPE_CONSTRAINTS:
		{
			int nb_constraint = ((BL_ArmatureObject *)self_plus)->GetConstraintNumber();
			if (index<0) 
				index += nb_constraint;
			if (index<0 || index>= nb_constraint) {
				PyErr_SetString(PyExc_IndexError, "seq[i]: index out of range");
				return NULL;
			}
			return ((BL_ArmatureObject *)self_plus)->GetConstraint(index)->GetProxy();
		}
		case KX_PYGENSEQ_OB_TYPE_CHANNELS:
		{
			int nb_channel = ((BL_ArmatureObject *)self_plus)->GetChannelNumber();
			if (index<0) 
				index += nb_channel;
			if (index<0 || index>= nb_channel) {
				PyErr_SetString(PyExc_IndexError, "seq[i]: index out of range");
				return NULL;
			}
			return ((BL_ArmatureObject *)self_plus)->GetChannel(index)->GetProxy();
		}

	}
	
	PyErr_SetString(PyExc_SystemError, "invalid sequence type, this is a bug");
	return NULL;
}

static PyObjectPlus *KX_PythonSeq_subscript__internal(PyObject *self, const char *key)
{
	PyObjectPlus *self_plus= BGE_PROXY_REF(((KX_PythonSeq *)self)->base);
	
	switch (((KX_PythonSeq *)self)->type) {
		case KX_PYGENSEQ_CONT_TYPE_SENSORS:
		{
			vector<SCA_ISensor*>& linkedsensors = ((SCA_IController *)self_plus)->GetLinkedSensors();
			SCA_ISensor* sensor;
			for (unsigned int index=0;index<linkedsensors.size();index++) {
				sensor = linkedsensors[index];
				if (sensor->GetName() == key)
					return static_cast<PyObjectPlus *>(sensor);
				
			}
			break;
		}
		case KX_PYGENSEQ_CONT_TYPE_ACTUATORS:
		{
			vector<SCA_IActuator*>& linkedactuators = ((SCA_IController *)self_plus)->GetLinkedActuators();
			SCA_IActuator* actuator;
			for (unsigned int index=0;index<linkedactuators.size();index++) {
				actuator = linkedactuators[index];
				if (actuator->GetName() == key)
					return static_cast<PyObjectPlus *>(actuator);
			}
			break;
		}
		case KX_PYGENSEQ_OB_TYPE_SENSORS:
		{
			SCA_SensorList& linkedsensors= ((KX_GameObject *)self_plus)->GetSensors();
			SCA_ISensor *sensor;
			for (unsigned int index=0;index<linkedsensors.size();index++) {
				sensor= linkedsensors[index];
				if (sensor->GetName() == key)
					return static_cast<PyObjectPlus *>(sensor);
			}
			break;
		}
		case KX_PYGENSEQ_OB_TYPE_CONTROLLERS:
		{
			SCA_ControllerList& linkedcontrollers= ((KX_GameObject *)self_plus)->GetControllers();
			SCA_IController *controller;
			for (unsigned int index=0;index<linkedcontrollers.size();index++) {
				controller= linkedcontrollers[index];
				if (controller->GetName() == key)
					return static_cast<PyObjectPlus *>(controller);
			}
			break;
		}
		case KX_PYGENSEQ_OB_TYPE_ACTUATORS:
		{
			SCA_ActuatorList& linkedactuators= ((KX_GameObject *)self_plus)->GetActuators();
			SCA_IActuator *actuator;
			for (unsigned int index=0;index<linkedactuators.size();index++) {
				actuator= linkedactuators[index];
				if (actuator->GetName() == key)
					return static_cast<PyObjectPlus *>(actuator);
			}
			break;
		}
		case KX_PYGENSEQ_OB_TYPE_CONSTRAINTS:
		{
			return ((BL_ArmatureObject*)self_plus)->GetConstraint(key);
		}
		case KX_PYGENSEQ_OB_TYPE_CHANNELS:
		{
			return ((BL_ArmatureObject*)self_plus)->GetChannel(key);
		}
	}
	
	return NULL;
}


static PyObject *KX_PythonSeq_subscript(PyObject *self, PyObject *key)
{
	PyObjectPlus *self_plus= BGE_PROXY_REF(((KX_PythonSeq *)self)->base);
	
	if (self_plus==NULL) {
		PyErr_SetString(PyExc_SystemError, "val = seq[key], KX_PythonSeq: " BGE_PROXY_ERROR_MSG);
		return NULL;
	}
	
	if (PyIndex_Check(key)) {
		return KX_PythonSeq_getIndex(self, PyLong_AsSsize_t(key));
	}
	else if ( PyUnicode_Check(key) ) {
		const char *name = _PyUnicode_AsString(key);
		PyObjectPlus *ret = KX_PythonSeq_subscript__internal(self, name);
		
		if (ret) {
			return ret->GetProxy();
		} else {
			PyErr_Format( PyExc_KeyError, "requested item \"%s\" does not exist", name);
			return NULL;
		}
	}
	else {
		PyErr_SetString( PyExc_TypeError, "expected a string or an index" );
		return NULL;
	}
}


static int KX_PythonSeq_contains(PyObject *self, PyObject *key)
{
	PyObjectPlus *self_plus= BGE_PROXY_REF(((KX_PythonSeq *)self)->base);
	
	if (self_plus==NULL) {
		PyErr_SetString(PyExc_SystemError, "key in seq, KX_PythonSeq: " BGE_PROXY_ERROR_MSG);
		return -1;
	}
	if (!PyUnicode_Check(key)) {
		PyErr_SetString(PyExc_SystemError, "key in seq, KX_PythonSeq: key must be a string");
		return -1;
	}
	
	if (KX_PythonSeq_subscript__internal(self, _PyUnicode_AsString(key)))
		return 1;
	
	return 0;
}

/* Matches python dict.get(key, [default]) */
static PyObject *KX_PythonSeq_get(PyObject *self, PyObject *args)
{
	char *key;
	PyObject *def = Py_None;
	PyObjectPlus* ret_plus;

	if (!PyArg_ParseTuple(args, "s|O:get", &key, &def))
		return NULL;
	
	if ((ret_plus = KX_PythonSeq_subscript__internal(self, key)))
		return ret_plus->GetProxy();
	
	Py_INCREF(def);
	return def;
}

static PySequenceMethods KX_PythonSeq_as_sequence = {
	NULL,		/* Cant set the len otherwise it can evaluate as false */
	NULL,		/* sq_concat */
	NULL,		/* sq_repeat */
	NULL,		/* sq_item */
	NULL,		/* sq_slice */
	NULL,		/* sq_ass_item */
	NULL,		/* sq_ass_slice */
	(objobjproc)KX_PythonSeq_contains,	/* sq_contains */
	(binaryfunc) NULL, /* sq_inplace_concat */
	(ssizeargfunc) NULL, /* sq_inplace_repeat */
};

static PyMappingMethods KX_PythonSeq_as_mapping = {
	KX_PythonSeq_len,	/* mp_length */
	KX_PythonSeq_subscript,	/* mp_subscript */
	0,	/* mp_ass_subscript */
};

static PyMethodDef KX_PythonSeq_methods[] = {
	// dict style access for props
	{"get",(PyCFunction) KX_PythonSeq_get, METH_VARARGS},
	{NULL,NULL} //Sentinel
};

/*
 * Initialize the iterator index
 */

static PyObject *KX_PythonSeq_getIter(KX_PythonSeq *self)
{
	if (BGE_PROXY_REF(self->base)==NULL) {
		PyErr_SetString(PyExc_SystemError, "for i in seq: " BGE_PROXY_ERROR_MSG);
		return NULL;
	}
	
	/* create a new iterator if were already using this one */
	if (self->iter == -1) {
		self->iter = 0;
		Py_INCREF(self);
		return (PyObject *)self;
	} else {
		return KX_PythonSeq_CreatePyObject(self->base, self->type);
	}
}


/*
 * Return next KX_PythonSeq iter.
 */
 
static PyObject *KX_PythonSeq_nextIter(KX_PythonSeq *self)
{
	PyObject *object = KX_PythonSeq_getIndex((PyObject *)self, self->iter);
	
	self->iter++;
	if ( object==NULL ) {
		self->iter= -1; /* for reuse */
		PyErr_SetNone(PyExc_StopIteration);
	}
	return object; /* can be NULL for end of iterator */
}


static int KX_PythonSeq_compare(KX_PythonSeq *a, KX_PythonSeq *b)
{
	return (a->type == b->type && a->base == b->base) ? 0 : -1;
}

static PyObject *KX_PythonSeq_richcmp(PyObject *a, PyObject *b, int op)
{
	PyObject *res;
	int ok= -1; /* zero is true */

	if (BPy_KX_PythonSeq_Check(a) && BPy_KX_PythonSeq_Check(b))
		ok= KX_PythonSeq_compare((KX_PythonSeq *)a, (KX_PythonSeq *)b);
	
	switch (op) {
	case Py_NE:
		ok = !ok;
		/* fall-through */
	case Py_EQ:
		res = ok ? Py_False : Py_True;
		break;

	case Py_LT:
	case Py_LE:
	case Py_GT:
	case Py_GE:
		res = Py_NotImplemented;
		break;
	default:
		PyErr_BadArgument();
		return NULL;
	}
	
	Py_INCREF(res);
	return res;
}


/*
 * repr function
 * convert to a list and get its string value
 */
static PyObject *KX_PythonSeq_repr(KX_PythonSeq *self)
{
	PyObject *list = PySequence_List((PyObject *)self);
	PyObject *repr = PyObject_Repr(list);
	Py_DECREF(list);
	return repr;
}


/*****************************************************************************/
/* Python KX_PythonSeq_Type structure definition:                               */
/*****************************************************************************/
PyTypeObject KX_PythonSeq_Type = {
	PyVarObject_HEAD_INIT(NULL, 0)
	/*  For printing, in format "<module>.<name>" */
	"KX_PythonSeq",           /* char *tp_name; */
	sizeof( KX_PythonSeq ),       /* int tp_basicsize; */
	0,                          /* tp_itemsize;  For allocation */

	/* Methods to implement standard operations */

	( destructor ) KX_PythonSeq_dealloc, /* destructor tp_dealloc; */
	NULL,                       /* printfunc tp_print; */
	NULL,                       /* getattrfunc tp_getattr; */
	NULL,                       /* setattrfunc tp_setattr; */
	NULL,						/* cmpfunc tp_compare; */
	( reprfunc ) KX_PythonSeq_repr,   /* reprfunc tp_repr; */

	/* Method suites for standard classes */

	NULL,                       /* PyNumberMethods *tp_as_number; */
	&KX_PythonSeq_as_sequence,	    /* PySequenceMethods *tp_as_sequence; */
	&KX_PythonSeq_as_mapping,                       /* PyMappingMethods *tp_as_mapping; */

	/* More standard operations (here for binary compatibility) */

	NULL,                       /* hashfunc tp_hash; */
	NULL,                       /* ternaryfunc tp_call; */
	NULL,                       /* reprfunc tp_str; */
	NULL,                       /* getattrofunc tp_getattro; */
	NULL,                       /* setattrofunc tp_setattro; */

	/* Functions to access object as input/output buffer */
	NULL,                       /* PyBufferProcs *tp_as_buffer; */

  /*** Flags to define presence of optional/expanded features ***/
	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* long tp_flags; */

	NULL,                       /*  char *tp_doc;  Documentation string */
  /*** Assigned meaning in release 2.0 ***/
	/* call function for all accessible objects */
	(traverseproc)KX_PythonSeq_traverse,	/* traverseproc tp_traverse; */

	/* delete references to contained objects */
	(inquiry)KX_PythonSeq_clear,	/* inquiry tp_clear; */

  /***  Assigned meaning in release 2.1 ***/
  /*** rich comparisons ***/
	(richcmpfunc)KX_PythonSeq_richcmp,	/* richcmpfunc tp_richcompare; */

  /***  weak reference enabler ***/
	0,                          /* long tp_weaklistoffset; */

  /*** Added in release 2.2 ***/
	/*   Iterators */
	( getiterfunc) KX_PythonSeq_getIter, /* getiterfunc tp_iter; */
	( iternextfunc ) KX_PythonSeq_nextIter, /* iternextfunc tp_iternext; */

  /*** Attribute descriptor and subclassing stuff ***/
	KX_PythonSeq_methods,       /* struct PyMethodDef *tp_methods; */
	NULL,                       /* struct PyMemberDef *tp_members; */
	NULL,       /* struct PyGetSetDef *tp_getset; */
	NULL,                       /* struct _typeobject *tp_base; */
	NULL,                       /* PyObject *tp_dict; */
	NULL,                       /* descrgetfunc tp_descr_get; */
	NULL,                       /* descrsetfunc tp_descr_set; */
	0,                          /* long tp_dictoffset; */
	NULL,                       /* initproc tp_init; */
	NULL,                       /* allocfunc tp_alloc; */
	NULL,                       /* newfunc tp_new; */
	/*  Low-level free-memory routine */
	NULL,                       /* freefunc tp_free;  */
	/* For PyObject_IS_GC */
	NULL,                       /* inquiry tp_is_gc;  */
	NULL,                       /* PyObject *tp_bases; */
	/* method resolution order */
	NULL,                       /* PyObject *tp_mro;  */
	NULL,                       /* PyObject *tp_cache; */
	NULL,                       /* PyObject *tp_subclasses; */
	NULL,                       /* PyObject *tp_weaklist; */
	NULL
};

#endif // WITH_PYTHON
