/** \file gameengine/Expressions/ListValue.cpp
 *  \ingroup expressions
 */
// ListValue.cpp: implementation of the CListValue class.
//
//////////////////////////////////////////////////////////////////////
/*
 * Copyright (c) 1996-2000 Erwin Coumans <coockie@acm.org>
 *
 * Permission to use, copy, modify, distribute and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.  Erwin Coumans makes no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 *
 */

#include <stdio.h>

#include "ListValue.h"
#include "StringValue.h"
#include "VoidValue.h"
#include <algorithm>
#include "BoolValue.h"

#include "BLO_sys_types.h" /* for intptr_t support */


//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CListValue::CListValue()
: CPropValue()
{
	m_bReleaseContents=true;
}



CListValue::~CListValue()
{

	if (m_bReleaseContents) {
		for (unsigned int i=0;i<m_pValueArray.size();i++) {
			m_pValueArray[i]->Release();
		}
	}
}


static STR_String gstrListRep=STR_String("List");

const STR_String & CListValue::GetText()
{
	gstrListRep = "[";
	STR_String commastr = "";

	for (int i=0;i<GetCount();i++)
	{
		gstrListRep += commastr;
		gstrListRep += GetValue(i)->GetText();
		commastr = ",";
	}
	gstrListRep += "]";

	return gstrListRep;
}



CValue* CListValue::GetReplica()
{
	CListValue* replica = new CListValue(*this);

	replica->ProcessReplica();

	replica->m_bReleaseContents=true; // for copy, complete array is copied for now...
	// copy all values
	int numelements = m_pValueArray.size();
	unsigned int i=0;
	replica->m_pValueArray.resize(numelements);
	for (i=0;i<m_pValueArray.size();i++)
		replica->m_pValueArray[i] = m_pValueArray[i]->GetReplica();


	return replica;
};



void CListValue::SetValue(int i, CValue *val)
{
	assertd(i < m_pValueArray.size());
	m_pValueArray[i]=val;
}



void CListValue::Resize(int num)
{
	m_pValueArray.resize(num);
}



void CListValue::Remove(int i)
{
	assertd(i<m_pValueArray.size());
	m_pValueArray.erase(m_pValueArray.begin()+i);
}



void CListValue::ReleaseAndRemoveAll()
{
	for (unsigned int i=0;i<m_pValueArray.size();i++)
		m_pValueArray[i]->Release();
	m_pValueArray.clear();//.Clear();
}



CValue* CListValue::FindValue(const STR_String & name)
{
	for (int i=0; i < GetCount(); i++)
		if (GetValue(i)->GetName() == name)
			return GetValue(i);

	return NULL;
}

CValue* CListValue::FindValue(const char * name)
{
	for (int i=0; i < GetCount(); i++)
		if (GetValue(i)->GetName() == name)
			return GetValue(i);

	return NULL;
}

bool CListValue::SearchValue(CValue *val)
{
	for (int i=0;i<GetCount();i++)
		if (val == GetValue(i))
			return true;
	return false;
}



void CListValue::SetReleaseOnDestruct(bool bReleaseContents)
{
	m_bReleaseContents = bReleaseContents;
}



bool CListValue::RemoveValue(CValue *val)
{
	bool result=false;

	for (int i=GetCount()-1;i>=0;i--)
		if (val == GetValue(i))
		{
			Remove(i);
			result=true;
		}
	return result;
}



void CListValue::MergeList(CListValue *otherlist)
{

	int numelements = this->GetCount();
	int numotherelements = otherlist->GetCount();


	Resize(numelements+numotherelements);

	for (int i=0;i<numotherelements;i++)
	{
		SetValue(i+numelements,otherlist->GetValue(i)->AddRef());
	}
}

bool CListValue::CheckEqual(CValue* first,CValue* second)
{
	bool result = false;

	CValue* eqval =  ((CValue*)first)->Calc(VALUE_EQL_OPERATOR,(CValue*)second);

	if (eqval==NULL)
		return false;
	const STR_String& text = eqval->GetText();
	if (&text==&CBoolValue::sTrueString)
	{
		result = true;
	}
	eqval->Release();
	return result;

}


/* ---------------------------------------------------------------------
 * Some stuff taken from the header
 * --------------------------------------------------------------------- */
CValue* CListValue::Calc(VALUE_OPERATOR op,CValue *val)
{
	//assert(false); // todo: implement me!
	static int error_printed =  0;
	if (error_printed==0) {
		fprintf(stderr, "CValueList::Calc not yet implemented\n");
		error_printed = 1;
	}
	return NULL;
}

CValue* CListValue::CalcFinal(VALUE_DATA_TYPE dtype,
							  VALUE_OPERATOR op,
							  CValue* val)
{
	//assert(false); // todo: implement me!
	static int error_printed =  0;
	if (error_printed==0) {
		fprintf(stderr, "CValueList::CalcFinal not yet implemented\n");
		error_printed = 1;
	}
	return NULL;
}



void CListValue::Add(CValue* value)
{
	m_pValueArray.push_back(value);
}



double CListValue::GetNumber()
{
	return -1;
}



void CListValue::SetModified(bool bModified)
{
	CValue::SetModified(bModified);
	int numels = GetCount();

	for (int i=0;i<numels;i++)
		GetValue(i)->SetModified(bModified);
}



bool CListValue::IsModified()
{
	bool bmod = CValue::IsModified(); //normal own flag
	int numels = GetCount();

	for (int i=0;i<numels;i++)
		bmod = bmod || GetValue(i)->IsModified();

	return bmod;
}

#ifdef WITH_PYTHON

/* --------------------------------------------------------------------- */
/* Python interface ---------------------------------------------------- */
/* --------------------------------------------------------------------- */

static Py_ssize_t listvalue_bufferlen(PyObject *self)
{
	CListValue *list= static_cast<CListValue *>(BGE_PROXY_REF(self));
	if (list==NULL)
		return 0;
	
	return (Py_ssize_t)list->GetCount();
}

static PyObject *listvalue_buffer_item(PyObject *self, Py_ssize_t index)
{
	CListValue *list= static_cast<CListValue *>(BGE_PROXY_REF(self));
	CValue *cval;
	
	if (list==NULL) {
		PyErr_SetString(PyExc_SystemError, "val = CList[i], "BGE_PROXY_ERROR_MSG);
		return NULL;
	}
	
	int count = list->GetCount();
	
	if (index < 0)
		index = count+index;
	
	if (index < 0 || index >= count) {
		PyErr_SetString(PyExc_IndexError, "CList[i]: Python ListIndex out of range in CValueList");
		return NULL;
	}
	
	cval= list->GetValue(index);
	
	PyObject *pyobj = cval->ConvertValueToPython();
	if (pyobj)
		return pyobj;
	else
		return cval->GetProxy();
}


/* just slice it into a python list... */
static PyObject *listvalue_buffer_slice(CListValue *list, Py_ssize_t start, Py_ssize_t stop)
{
	PyObject *newlist;
	Py_ssize_t i, j;

	/* caller needs to validate negative index */
#if 0
	Py_ssize_t len = list->GetCount();

	if (start > len) start = len;
	if (stop  > len) stop  = len;
#endif

	newlist = PyList_New(stop - start);
	if (!newlist)
		return NULL;

	for (i = start, j = 0; i < stop; i++, j++) {
		PyObject *pyobj = list->GetValue(i)->ConvertValueToPython();
		if (!pyobj) {
			pyobj = list->GetValue(i)->GetProxy();
		}
		PyList_SET_ITEM(newlist, j, pyobj);
	}
	return newlist;
}


static PyObject *listvalue_mapping_subscript(PyObject *self, PyObject *key)
{
	CListValue *list= static_cast<CListValue *>(BGE_PROXY_REF(self));
	if (list==NULL) {
		PyErr_SetString(PyExc_SystemError, "value = CList[i], "BGE_PROXY_ERROR_MSG);
		return NULL;
	}
	
	if (PyUnicode_Check(key)) {
		CValue *item = ((CListValue*) list)->FindValue(_PyUnicode_AsString(key));
		if (item) {
			PyObject *pyobj = item->ConvertValueToPython();
			if (pyobj)
				return pyobj;
			else
				return item->GetProxy();
		}
	}
	else if (PyIndex_Check(key)) {
		Py_ssize_t index = PyLong_AsSsize_t(key);
		return listvalue_buffer_item(self, index); /* wont add a ref */
	}
	else if (PySlice_Check(key)) {
		Py_ssize_t start, stop, step, slicelength;

		if (PySlice_GetIndicesEx(key, list->GetCount(), &start, &stop, &step, &slicelength) < 0)
			return NULL;

		if (slicelength <= 0) {
			return PyList_New(0);
		}
		else if (step == 1) {
			return listvalue_buffer_slice(list, start, stop);
		}
		else {
			PyErr_SetString(PyExc_TypeError, "CList[slice]: slice steps not supported");
			return NULL;
		}
	}

	PyErr_Format(PyExc_KeyError,
	             "CList[key]: '%R' key not in list", key);
	return NULL;
}

/* clist + list, return a list that python owns */
static PyObject *listvalue_buffer_concat(PyObject *self, PyObject *other)
{
	CListValue *listval= static_cast<CListValue *>(BGE_PROXY_REF(self));
	Py_ssize_t i, numitems, numitems_orig;
	
	if (listval==NULL) {
		PyErr_SetString(PyExc_SystemError, "CList+other, "BGE_PROXY_ERROR_MSG);
		return NULL;
	}
	
	numitems_orig= listval->GetCount();
	
	// for now, we support CListValue concatenated with items
	// and CListValue concatenated to Python Lists
	// and CListValue concatenated with another CListValue
	
	/* Shallow copy, don't use listval->GetReplica(), it will screw up with KX_GameObjects */
	CListValue* listval_new = new CListValue();
	
	if (PyList_Check(other))
	{
		CValue* listitemval;
		bool error = false;
		
		numitems = PyList_GET_SIZE(other);
		
		/* copy the first part of the list */
		listval_new->Resize(numitems_orig + numitems);
		for (i=0;i<numitems_orig;i++)
			listval_new->SetValue(i, listval->GetValue(i)->AddRef());
		
		for (i=0;i<numitems;i++)
		{
			listitemval = listval->ConvertPythonToValue(PyList_GetItem(other,i), "cList + pyList: CListValue, ");
			
			if (listitemval) {
				listval_new->SetValue(i+numitems_orig, listitemval);
			} else {
				error= true;
				break;
			}
		}
		
		if (error) {
			listval_new->Resize(numitems_orig+i); /* resize so we don't try release NULL pointers */
			listval_new->Release();
			return NULL; /* ConvertPythonToValue above sets the error */ 
		}
	
	}
	else if (PyObject_TypeCheck(other, &CListValue::Type)) {
		// add items from otherlist to this list
		CListValue* otherval = static_cast<CListValue *>(BGE_PROXY_REF(other));
		if (otherval==NULL) {
			listval_new->Release();
			PyErr_SetString(PyExc_SystemError, "CList+other, "BGE_PROXY_ERROR_MSG);
			return NULL;
		}
		
		numitems = otherval->GetCount();
		
		/* copy the first part of the list */
		listval_new->Resize(numitems_orig + numitems); /* resize so we don't try release NULL pointers */
		for (i=0;i<numitems_orig;i++)
			listval_new->SetValue(i, listval->GetValue(i)->AddRef());
		
		/* now copy the other part of the list */
		for (i=0;i<numitems;i++)
			listval_new->SetValue(i+numitems_orig, otherval->GetValue(i)->AddRef());
		
	}
	return listval_new->NewProxy(true); /* python owns this list */
}

static int listvalue_buffer_contains(PyObject *self_v, PyObject *value)
{
	CListValue *self = static_cast<CListValue *>(BGE_PROXY_REF(self_v));
	
	if (self == NULL) {
		PyErr_SetString(PyExc_SystemError, "val in CList, "BGE_PROXY_ERROR_MSG);
		return -1;
	}
	
	if (PyUnicode_Check(value)) {
		if (self->FindValue((const char *)_PyUnicode_AsString(value))) {
			return 1;
		}
	}
	else if (PyObject_TypeCheck(value, &CValue::Type)) { /* not dict like at all but this worked before __contains__ was used */
		CValue *item= static_cast<CValue *>(BGE_PROXY_REF(value));
		for (int i=0; i < self->GetCount(); i++)
			if (self->GetValue(i) == item) // Com
				return 1;
		
	} // not using CheckEqual
	
	return 0;
}


static  PySequenceMethods listvalue_as_sequence = {
	listvalue_bufferlen,//(inquiry)buffer_length, /*sq_length*/
	listvalue_buffer_concat, /*sq_concat*/
	NULL, /*sq_repeat*/
	listvalue_buffer_item, /*sq_item*/
// TODO, slicing in py3
	NULL, // listvalue_buffer_slice, /*sq_slice*/
	NULL, /*sq_ass_item*/
	NULL, /*sq_ass_slice*/
	(objobjproc)listvalue_buffer_contains,	/* sq_contains */
	(binaryfunc) NULL, /* sq_inplace_concat */
	(ssizeargfunc) NULL, /* sq_inplace_repeat */
};



/* Is this one used ? */
static  PyMappingMethods instance_as_mapping = {
	listvalue_bufferlen, /*mp_length*/
	listvalue_mapping_subscript, /*mp_subscript*/
	NULL /*mp_ass_subscript*/
};



PyTypeObject CListValue::Type = {
	PyVarObject_HEAD_INIT(NULL, 0)
	"CListValue",			/*tp_name*/
	sizeof(PyObjectPlus_Proxy), /*tp_basicsize*/
	0,				/*tp_itemsize*/
	/* methods */
	py_base_dealloc,			/*tp_dealloc*/
	0,				/*tp_print*/
	0, 			/*tp_getattr*/
	0, 			/*tp_setattr*/
	0,			/*tp_compare*/
	py_base_repr, /*tp_repr*/
	0,			        /*tp_as_number*/
	&listvalue_as_sequence, /*tp_as_sequence*/
	&instance_as_mapping,	        /*tp_as_mapping*/
	0,			        /*tp_hash*/
	0,				/*tp_call */
	0,
	NULL,
	NULL,
	0,
	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
	0,0,0,0,0,0,0,
	Methods,
	0,
	0,
	&CValue::Type,
	0,0,0,0,0,0,
	py_base_new
};

PyMethodDef CListValue::Methods[] = {
	/* List style access */
	{"append", (PyCFunction)CListValue::sPyappend,METH_O},
	{"reverse", (PyCFunction)CListValue::sPyreverse,METH_NOARGS},
	{"index", (PyCFunction)CListValue::sPyindex,METH_O},
	{"count", (PyCFunction)CListValue::sPycount,METH_O},

	/* Dict style access */
	{"get", (PyCFunction)CListValue::sPyget,METH_VARARGS},

	/* Own cvalue funcs */
	{"from_id", (PyCFunction)CListValue::sPyfrom_id,METH_O},

	{NULL,NULL} //Sentinel
};

PyAttributeDef CListValue::Attributes[] = {
	{ NULL }	//Sentinel
};

PyObject *CListValue::Pyappend(PyObject *value)
{
	CValue* objval = ConvertPythonToValue(value, "CList.append(i): CValueList, ");

	if (!objval) /* ConvertPythonToValue sets the error */
		return NULL;

	if (!BGE_PROXY_PYOWNS(m_proxy)) {
		PyErr_SetString(PyExc_TypeError, "CList.append(i): this CValueList is used internally for the game engine and can't be modified");
		return NULL;
	}

	Add(objval);

	Py_RETURN_NONE;
}

PyObject *CListValue::Pyreverse()
{
	std::reverse(m_pValueArray.begin(),m_pValueArray.end());
	Py_RETURN_NONE;
}

PyObject *CListValue::Pyindex(PyObject *value)
{
	PyObject *result = NULL;

	CValue* checkobj = ConvertPythonToValue(value, "val = cList[i]: CValueList, ");
	if (checkobj==NULL)
		return NULL; /* ConvertPythonToValue sets the error */

	int numelem = GetCount();
	for (int i=0;i<numelem;i++)
	{
		CValue* elem = GetValue(i);
		if (checkobj==elem || CheckEqual(checkobj,elem))
		{
			result = PyLong_FromLong(i);
			break;
		}
	}
	checkobj->Release();

	if (result==NULL) {
		PyErr_SetString(PyExc_ValueError, "CList.index(x): x not in CListValue");
	}
	return result;

}



PyObject *CListValue::Pycount(PyObject *value)
{
	int numfound = 0;

	CValue* checkobj = ConvertPythonToValue(value, ""); /* error ignored */

	if (checkobj==NULL) { /* in this case just return that there are no items in the list */
		PyErr_Clear();
		return PyLong_FromLong(0);
	}

	int numelem = GetCount();
	for (int i=0;i<numelem;i++)
	{
		CValue* elem = 			GetValue(i);
		if (checkobj==elem || CheckEqual(checkobj,elem))
		{
			numfound ++;
		}
	}
	checkobj->Release();

	return PyLong_FromLong(numfound);
}

/* Matches python dict.get(key, [default]) */
PyObject *CListValue::Pyget(PyObject *args)
{
	char *key;
	PyObject *def = Py_None;

	if (!PyArg_ParseTuple(args, "s|O:get", &key, &def))
		return NULL;

	CValue *item = FindValue((const char *)key);
	if (item) {
		PyObject *pyobj = item->ConvertValueToPython();
		if (pyobj)
			return pyobj;
		else
			return item->GetProxy();
	}
	Py_INCREF(def);
	return def;
}


PyObject *CListValue::Pyfrom_id(PyObject *value)
{
	uintptr_t id= (uintptr_t)PyLong_AsVoidPtr(value);

	if (PyErr_Occurred())
		return NULL;

	int numelem = GetCount();
	for (int i=0;i<numelem;i++)
	{
		if (reinterpret_cast<uintptr_t>(m_pValueArray[i]->m_proxy) == id)
			return GetValue(i)->GetProxy();
	}
	PyErr_SetString(PyExc_IndexError, "from_id(#): id not found in CValueList");
	return NULL;

}

#endif // WITH_PYTHON
