/*****************************************************************************
 * $Id: eng_python.hh,v 1.20 2006/08/10 03:18:58 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Python support.
 * @remarks
 *//*
 * LEGAL:	COPYRIGHT (C) 2005 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#ifndef NO_PYTHON
#ifndef ENG_PYTHON_HH
#define ENG_PYTHON_HH 1

////////////////////////////////////////////////////////////////////////////////
// Python class.
class Python
{
PREVENT_COPYING(Python)
public:
				Python( void ) { }
				~Python() { }
	bool		Init( void );
	void		Exit( void );
	PyObject*	NewModule( const char* moduleName );
	PyObject*	NewModule( const char* moduleName, PyMethodDef methods[] );
	bool		Run( const char* pyCode );
	bool		Run( const string& pyCode );
};

////////////////////////////////////////////////////////////////////////////////
/// @brief ObjectPython is a 3D object to be constructed by Python code.
class ObjectPython : public ObjectMesh
{
PREVENT_COPYING(ObjectPython)
private: typedef ObjectMesh Parent;
public:		explicit				ObjectPython( QuadrantPtrc q );
public:		virtual					~ObjectPython();
private:	void					Init( void );
protected:	virtual void			FinalConstruction( void ) { }
public:     virtual bool			Reproducible( void ) { return false; }
public:     virtual void			Draw( void );
public:		virtual const string	GetName( void ) { return string("ObjectPython"); }
public:     virtual uint			GetVertexCnt( void )       { return mVertexCnt; }
public:     virtual const WorldVertex* GetWorldVertexs( void )  { return mWorldVertexs; }
public:		virtual void			SetVertexCnt( uint vcnt )			{ mVertexCnt = vcnt; }
public:		virtual void			SetWorldVertexs( WorldVertex* wv )	{ mWorldVertexs = wv; }

protected:	uint					mVertexCnt;
protected:	WorldVertex*			mWorldVertexs;

public:     DECL_TYPESIG( TYPESIG_OBJECT_PYTHON )
};

////////////////////////////////////////////////////////////////////////////////
/// @brief FixedPython is a 3D object to be constructed by Python code.
class FixedPython : public Fixed<ObjectPython>
{
PREVENT_COPYING(FixedPython)
private: typedef Fixed<ObjectPython> Parent;
public:		explicit		FixedPython( QuadrantPtrc q );
public:						FixedPython( QuadrantPtrc q, const Position& pos );
public:		virtual			~FixedPython();
};

// -----------------------------------------------------------------------------
// Support for writing Python functions in C++ (based on PyCObject or long).
// -----------------------------------------------------------------------------

// .............................................................................
// This example shows the abstraction of how Python contains C pointers.
// PyEng_DynaRotate() is a Python function that is implemented in C++.
// CptrArg is a type that abstracts what holds a C pointer (to a C++ Dyna object).
// The Dyna's pointer originated from C++, was being manipulated by Python code,
// and at this moment was passed back to C++ (to this function).
// PYTHON_CPTR_FORMAT is macro for Python's printf-like-format char for arg parsing.
// PYTHON_CPTR_FROM_ARG() abstracts how the C pointer is extracted from the Python arg.
//
// static PyObject*
// PyEng_DynaRotate( PyObject* nil, PyObject* args )
// {
//     CptrArg     cptr;       // Dyna
//     int         axis;
//     float       deg;
//     if ( PyArg_ParseTuple( args, PYTHON_CPTR_FORMAT "if", &cptr, &axis, &deg ) )
//     {
//         Dyna* dyna = PYTHON_CPTR_FROM_ARG<Dyna*>( cptr );
//         CHECK_TYPESIG( dyna, TYPESIG_DYNA );
//         dyna->Rotate( axis, deg );
//         PYTHON_RETURN_NONE
//     }
//     PYTHON_RETURN_BAD_OBJECT_ERROR
// }
// .............................................................................

// Python provides two ways for C and Python to exchange C pointers.
// One way is based on PyCObject.
// Another is based on C/Python long (Python long is variable-precision int).
// Either way can be conditionally compiled.

#ifndef PYTHON_IFC_PYCOBJECT
#define PYTHON_IFC_PYCOBJECT	1			// 1 = use PyCObject, 0 = use long
#endif

// Don't cast CptrArg to void* !!
// Instead use PYTHON_CPTR_FROM_ARG<>(CptrArg).

#if PYTHON_IFC_PYCOBJECT
	typedef PyObject*			CptrArg;	// type of a C pointer passed by Python
#	define PYTHON_CPTR_FORMAT	"O"			// format string for PyArg_ParseTuple()
#else
	typedef long				CptrArg;
#	define PYTHON_CPTR_FORMAT	"l"
#endif

/// @return A C pointer from an argument passed from Python.
/// This abstracts how Python holds a C pointer.
/// PTR should be a pointer type.
///
/// void* PyCObject_AsVoidPtr( PyObject* self )
/// Returns the object void* that the PyCObject self was created with.
template<typename PTR>
inline PTR
PYTHON_CPTR_FROM_ARG( CptrArg cptrArg )
{
#if PYTHON_IFC_PYCOBJECT
	return reinterpret_cast<PTR>( PyCObject_AsVoidPtr(cptrArg) ); // C pointer from PyObject
#else
	return reinterpret_cast<PTR>( cptrArg );					  // C pointer from C long
#endif
}

/// @return A PyObject from a C pointer.
/// This abstracts how Python holds a C pointer.
///
/// PyObject* PyCObject_FromVoidPtr( void* cobj, void (*destr)(void *) )
/// Creates a PyCObject from the void *cobj. The destr function will be called
/// when the object is reclaimed, unless it is NULL.
static inline PyObject*
PYTHON_PYOBJECT_FROM_CPTR( void* cptr ) // yes, void* cptr
{
#if PYTHON_IFC_PYCOBJECT
	return PyCObject_FromVoidPtr( cptr, NULL );					// make a PyCObject
#else
	return PyLong_FromLong( reinterpret_cast<long>( cptr ) );	// make a Python long
#endif
}

// -----------------------------------------------------------------------------
// Macros for writing Python functions in C/C++.  Note: These are mult-line macros!
// -----------------------------------------------------------------------------

#define PYTHON_RETURN_NONE												\
	Py_INCREF( Py_None );												\
	return Py_None;

#define PYTHON_RETURN_BAD_OBJECT_ERROR									\
	PRINT_PYTHON_ERROR_AT_FUNC();										\
	PyErr_SetString( PyExc_TypeError, "bad object/arg" );				\
	return NULL;

#define PYTHON_RETURN_INDEX_ERROR										\
	PRINT_PYTHON_ERROR_AT_FUNC();										\
	PyErr_SetString( PyExc_IndexError, "index error" );					\
	return NULL;

#define PRINT_PYTHON_ERROR_AT_FUNC()									\
	*gStreamError << "ERROR[Python/C] at " << __FUNCTION__ << endl;

#define PRINT_PYTHON_ERROR( TEXT )										\
	PRINT_PYTHON_ERROR_AT_FUNC();										\
	if ( ! STREMPTY((TEXT)) ) { *gStreamError << (TEXT) << endl; }

// -----------------------------------------------------------------------------
// Macros for creating Python objects/values.
// -----------------------------------------------------------------------------

/// @return PyObject tuple(x,y,z).
static inline PyObject*
PYTHON_TUPLE( float x, float y, float z )
{
	PyObject* tuple = PyTuple_New( 3 );
	PyTuple_SET_ITEM( tuple, 0, PyFloat_FromDouble( x ) );
	PyTuple_SET_ITEM( tuple, 1, PyFloat_FromDouble( y ) );
	PyTuple_SET_ITEM( tuple, 2, PyFloat_FromDouble( z ) );
	return tuple;
}

/// @return PyObject tuple(x,y,z).
static inline PyObject*
PYTHON_TUPLE( const Position& pos )
{
	return PYTHON_TUPLE( pos.x, pos.y, pos.z );
}

/// @return Python/PyObject string from C++ string.
static inline PyObject*
PYTHON_STRING( const string& str )
{
	return PyString_FromStringAndSize( const_cast<char*>(str.c_str()), str.size() );
}

// -----------------------------------------------------------------------------
// Functions to get data from PyObjects.
// -----------------------------------------------------------------------------

static inline bool
PYTHON_GET_POSITION( PyObject* tup, Position& pos )
{
	double x = PyFloat_AS_DOUBLE( PyTuple_GET_ITEM( tup, XX ) );
	double y = PyFloat_AS_DOUBLE( PyTuple_GET_ITEM( tup, YY ) );
	double z = PyFloat_AS_DOUBLE( PyTuple_GET_ITEM( tup, ZZ ) );
	pos.Set( x, y, z );
	return true;
}

// -----------------------------------------------------------------------------
// Abbreviation macros.
// -----------------------------------------------------------------------------

// Don't confuse Object vs. ObjectPython !!!

// Extract a pointer to a Palomino C++ object from Python arg.
#define PYTHON_DEF_FUNC_TYPE_FROM_CPTR( TYPE, TYPE_UCASE )			\
	static inline TYPE*												\
	TYPE##FromCptr( CptrArg cptr )									\
	{																\
		TYPE * o = PYTHON_CPTR_FROM_ARG<TYPE *>( cptr );			\
		if ( TYPESIG_OK( o, TYPESIG_##TYPE_UCASE ) )				\
			return o;												\
        else														\
	        return NULL;											\
	}
// Expand functions using pre-preprocessor: ObjectFromCptr(), DynaFromCptr(), etc.
PYTHON_DEF_FUNC_TYPE_FROM_CPTR( Object,         OBJECT )
PYTHON_DEF_FUNC_TYPE_FROM_CPTR( ObjectPython,   OBJECT_PYTHON )
PYTHON_DEF_FUNC_TYPE_FROM_CPTR( Dyna,           DYNA )
PYTHON_DEF_FUNC_TYPE_FROM_CPTR( Quadrant,       QUADRANT )
PYTHON_DEF_FUNC_TYPE_FROM_CPTR( PolygonAttribs, POLYGON_ATTRIBS )
PYTHON_DEF_FUNC_TYPE_FROM_CPTR( Polygon,        POLYGON )
PYTHON_DEF_FUNC_TYPE_FROM_CPTR( PolygonList,    POLYGON_LIST )

// Multi-line abbreviation macros for declaring and extracting a pointer
// to a C++ Palomino object from a Python arg with error checking.
// Expands to:
//     Dyna* dyna = DYNA_FROM_CPTR( cptrDyna );
//     if ( dyna == NULL ) goto Error;
#define PYTHON_DECL_VAR_OR_ERROR( VAR, CPTR, TYPE )							\
	TYPE * VAR = TYPE##FromCptr( CPTR );									\
	if ( VAR == NULL ) goto Error;
#define         Object_FROM_CPTR_OR_ERROR( VAR, CPTR )	PYTHON_DECL_VAR_OR_ERROR( VAR, CPTR, Object )
#define   ObjectPython_FROM_CPTR_OR_ERROR( VAR, CPTR )	PYTHON_DECL_VAR_OR_ERROR( VAR, CPTR, ObjectPython )
#define           Dyna_FROM_CPTR_OR_ERROR( VAR, CPTR )	PYTHON_DECL_VAR_OR_ERROR( VAR, CPTR, Dyna )
#define       Quadrant_FROM_CPTR_OR_ERROR( VAR, CPTR )	PYTHON_DECL_VAR_OR_ERROR( VAR, CPTR, Quadrant )
#define PolygonAttribs_FROM_CPTR_OR_ERROR( VAR, CPTR )	PYTHON_DECL_VAR_OR_ERROR( VAR, CPTR, PolygonAttribs )
#define        Polygon_FROM_CPTR_OR_ERROR( VAR, CPTR )	PYTHON_DECL_VAR_OR_ERROR( VAR, CPTR, Polygon )
#define    PolygonList_FROM_CPTR_OR_ERROR( VAR, CPTR )	PYTHON_DECL_VAR_OR_ERROR( VAR, CPTR, PolygonList )

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

#endif // ENG_PYTHON_HH
#endif // NO_PYTHON
