/*
   +----------------------------------------------------------------------+
   | PHP version 4.0                                                      |
   +----------------------------------------------------------------------+
   | Copyright (c) 1997, 1998, 1999, 2000 The PHP Group                   |
   +----------------------------------------------------------------------+
   | This source file is subject to version 2.02 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available at through the world-wide-web at                           |
   | http://www.php.net/license/2_02.txt.                                 |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
   | Author: David Eriksson <david@2good.com>                            |
   +----------------------------------------------------------------------+
 */

/*
 * $Id: object.c,v 1.4 2000/11/30 22:01:57 eriksson Exp $
 * vim: syntax=c tabstop=2 shiftwidth=2
 */

/* -----------------------------------------------------------------------
 *
 * OrbitObject class
 *
 * There are three ways to create an object
 *
 * (1) OrbitObject_Constructor, for new OrbitObject(...) in PHP
 * (2) OrbitObject_Create, used when a CORBA method returns an object
 * (3) OrbitObject_Wakeup, used on "unserialization"
 *
 * -----------------------------------------------------------------------
 */
#include "class.h"
#include "corba.h"
#include "common.h"
#include "object.h"
#include "typemanager.h"
#include "namedvalue_to_zval.h"
#include "zval_to_namedvalue.h"
#include "typecode.h" /* for satellite_release_typecode */

#define PROFILE	0

#if PROFILE
#include <sys/time.h>
#include <unistd.h>
#endif

struct _OrbitObject
{
	CORBA_Object mCorbaObject;
	InterfaceType * mpInterface;
};

static void OrbitObject_Wakeup(INTERNAL_FUNCTION_PARAMETERS);
static void OrbitObject_Sleep(INTERNAL_FUNCTION_PARAMETERS);

static zend_function_entry OrbitObject_class_functions[] =
{
	{"__sleep", OrbitObject_Sleep},
	{"__wakeup", OrbitObject_Wakeup},
	{NULL, NULL}
};

#define MY_IMPLEMENT_CLASS(name, flags)\
IMPLEMENT_DECLARATIONS(name, flags)		\
IMPLEMENT_FUNCTION_CALL(name, flags)	\
IMPLEMENT_PUT_PROPERTY(name, flags)		\
IMPLEMENT_GET_PROPERTY(name, flags)		\
IMPLEMENT_INIT_EX(name, flags, ##name##_class_functions, _##name##_FunctionCall, _##name##_GetProperty, _##name##_PutProperty)\
IMPLEMENT_DATA_HELPERS(name, flags)
	
MY_IMPLEMENT_CLASS(OrbitObject, ~0);

CORBA_Object OrbitObject_GetCorbaObject(OrbitObject * pObject)
{
	if (pObject == NULL)
		return FALSE;
	else
		return pObject->mCorbaObject;
}
#define IOR_PROPERTY_KEY		"IOR"

/*
 * prepare for serialization
 *
 * this means creating a property containing the object IOR and returning an 
 * array containing the names of properties we want serialized
 *
 * TODO: save environment as property?!
 * 
 */
static void OrbitObject_Sleep(INTERNAL_FUNCTION_PARAMETERS)
{
	CORBA_char * p_ior = NULL;
	/* get object data */
	OrbitObject * p_object = OrbitObject_RetrieveData(this_ptr);
	
	/* validate data */
	if (p_object == NULL)
	{
		goto error;
	}

	/* get IOR from object */
	p_ior = CORBA_ORB_object_to_string(
			orbit_get_orb(), p_object->mCorbaObject, orbit_get_environment());
	if (p_ior == NULL || orbit_caught_exception())
		goto error;
	
	/* add property to zval */
	add_property_string(this_ptr, IOR_PROPERTY_KEY, p_ior, TRUE);
	
	/* create array */
	array_init(return_value);

	/* add name of property IOR to array */
	add_next_index_string(return_value, IOR_PROPERTY_KEY, TRUE);

	return;

error:
	RETURN_NULL();
}

/*
 * initialize OrbitObject structure
 */
static zend_bool OrbitObject_InitializeData(OrbitObject * pObject, const char * pIor)
{
	/* get object */
	pObject->mCorbaObject = CORBA_ORB_string_to_object(
			orbit_get_orb(), 
			pIor, 
			orbit_get_environment());

	if (pObject->mCorbaObject == NULL || orbit_caught_exception())
	{
		zend_error(E_WARNING, "(Satellite) Unable to resolve IOR");
		goto error;
	}

	/* find type info */
	pObject->mpInterface = 
		TypeManager_FindInterface(pObject->mCorbaObject->type_id);

	if (pObject->mpInterface == NULL)
	{
		zend_error(E_WARNING, "(Satellite) unknown interface '%s'",
				pObject->mCorbaObject->type_id);
		/* TODO: set exception */
		goto error;
	}

	return TRUE;

error:
	return FALSE;
}

/*
 * recover from serialization
 *
 * this means reading the property IOR and reconstructing the object!
 *
 */
static void OrbitObject_Wakeup(INTERNAL_FUNCTION_PARAMETERS)
{
	zval ** pp_ior = NULL;
	
	/* create data object */
	OrbitObject * p_object = orbit_new(OrbitObject);

	/* find IOR property */
	if (zend_hash_find(
				this_ptr->value.obj.properties, 
				IOR_PROPERTY_KEY, 
				sizeof(IOR_PROPERTY_KEY), 
				(void**)&pp_ior) != SUCCESS)
	{
		/* TODO: set exception */
		goto error;
	}

	if ((*pp_ior)->type != IS_STRING)
	{
		/* TODO: set exception */
		goto error;
	}

	/* initialize data */
	if (!OrbitObject_InitializeData(
				p_object, 
				(*pp_ior)->value.str.val))
	{
		goto error;
	}

	/* save data to zval */
	OrbitObject_SaveData(this_ptr, p_object);

	return;

error:
	zend_error(E_WARNING, "(Satellite) Invalid serialized object");
	OrbitObject_Destructor(p_object);
}


/* instansiate a class */
zend_bool OrbitObject_Create(CORBA_Object source, zval * pDestination)
{
	OrbitObject * p_object = NULL;
	
	/* source might be NULL */
	if (source == NULL)
	{
		ZVAL_NULL(pDestination);
		return TRUE;
	}
	
	/* allocate object */
	p_object = orbit_new(OrbitObject);

	/* save corba object */
	p_object->mCorbaObject = source;
	
	/* find type info */
	p_object->mpInterface = 
		TypeManager_FindInterface(p_object->mCorbaObject->type_id);

	if (p_object->mpInterface == NULL)
	{
		zend_error(E_WARNING, "(Satellite) unknown interface '%s'",
				p_object->mCorbaObject->type_id);
		goto error;
	}

	/* set zval members */
	pDestination->type = IS_OBJECT;
	pDestination->is_ref = 1;
	pDestination->refcount = 1;

	pDestination->value.obj.ce = &OrbitObject_class_entry;
	pDestination->value.obj.properties = orbit_new(HashTable);

	zend_hash_init(
			pDestination->value.obj.properties,	/* hash table */
			0, 																	/* size */
			NULL, 															/* hash function */
			ZVAL_PTR_DTOR, 											/* destructor */
			0);																	/* persistent */

	/* save orbit data */
	OrbitObject_SaveData(pDestination, p_object);

	return TRUE;
	
error:
	OrbitObject_Destructor(p_object);
	return FALSE;
}



/*
 * constructor
 *
 * parameters: IOR, OrbitEnvironment
 */
zend_bool  OrbitObject_Constructor(OrbitObject  ** ppObject, 
		int parameterCount, const zval ** ppParameters)
{
	OrbitObject * p_object = NULL;
	
	/* allocate object */
	p_object = orbit_new(OrbitObject);

	/* check parameter count */
	if (parameterCount != 1)
	{
		wrong_param_count();
		goto error;
	}

	/* validate parameter types */
	if (ppParameters[0]->type != IS_STRING)
	{
		zend_error(E_WARNING, "(Satellite) IOR is not a string");
		goto error;
	}

	/* initialize data object */
	if ( !OrbitObject_InitializeData(
			p_object,
			ppParameters[0]->value.str.val) )
	{
		goto error;
	}
	
	*ppObject = p_object;
	return TRUE;

error:
	OrbitObject_Destructor(p_object);
	*ppObject = NULL;

	/* TODO: all errors above should set exceptions! */

	return FALSE;
}

/*
 * destructor
 */
zend_bool  OrbitObject_Destructor(OrbitObject  * pObject)
{
	InterfaceType_release(pObject->mpInterface);
	
	if (pObject->mCorbaObject != NULL)
		CORBA_Object_release((CORBA_Object)pObject->mCorbaObject, NULL);

	orbit_delete(pObject);

	return TRUE;
}

/* 
 * prepare a function call result 
 */
static CORBA_NamedValue * OrbitObject_PrepareResult(OrbitObject * pObject, 
		OperationType * pOperation)
{
	CORBA_NamedValue * p_result = orbit_new(CORBA_NamedValue);

	p_result->argument._type = OperationType_GetReturnType(pOperation);

	if (p_result->argument._type == NULL)
	{
		orbit_delete(p_result);
		p_result = NULL;
	}
	else
	{
		orbit_zval_to_namedvalue(NULL, p_result);
	}
	
	return p_result;
}

/*
 * add an argument to a function call
 */
static zend_bool OrbitObject_AddSingleArgument(OrbitObject * pObject, 
		CORBA_Request request, ParameterType * pParameter, const zval * pSource, CORBA_NamedValue * pDestination)
{
	pDestination->argument._type = ParameterType_GetType(pParameter);
	pDestination->arg_modes 		 = ParameterType_GetPassingMode(pParameter);

	/* if the argument is output only, don't care about input value */
	if (pDestination->arg_modes == CORBA_ARG_OUT)
		pSource = NULL;

	if (!orbit_zval_to_namedvalue(pSource, pDestination))
		return FALSE;

	/* add parameter to request */
	CORBA_Request_add_arg(
		request,											/* request  */
		NULL,													/* arg_name */
		pDestination->argument._type,		/* type     */
		pDestination->argument._value,		/* value    */
		pDestination->len,								/* length   */
		pDestination->arg_modes,					/* flags    */
		orbit_get_environment()
	);

	if (orbit_caught_exception())
		return FALSE;

	return TRUE;
}

/*
 * add the function call arguments
 */
static zend_bool OrbitObject_AddArguments(OrbitObject * pObject, 
		CORBA_Request request, OperationType * pOperation, int argumentCount, 
		const zval ** ppArguments, CORBA_NamedValue ** ppNamedValue)
{
	ParameterType * p_parameter = OperationType_GetFirstParameter(pOperation);
	int i = 0;
	zend_bool success;

	if (argumentCount < 1)
		return TRUE;	/* nothing to do */

	do
	{
		ppNamedValue[i] = satellite_new(CORBA_NamedValue);
		success = OrbitObject_AddSingleArgument( pObject, 
				request, p_parameter, ppArguments[i], ppNamedValue[i]);

		if (!success)
			return FALSE;

		i++;
	} while (i < argumentCount && ParameterType_GetNext(p_parameter));

	/* i should equal argument count and there should be no more parameters */
	if (i != argumentCount || ParameterType_GetNext(p_parameter))
	{
/*		printf("%i, %i, %i\n", i, argumentCount, ParameterType_IsValid(p_parameter));*/
		
		/* bad number of arguments */
		wrong_param_count();
		return FALSE;
	}

	return TRUE;
}

/*
 * release a namedvalue that we have allocated
 *
 * (move to another file?)
 */
static void satellite_release_namedvalue(CORBA_NamedValue * pNamedValue)
{
	if (pNamedValue == NULL)
		return;
	
	/* clear value */
	if (pNamedValue->argument._value != NULL)
	{
		/* allocated with ORBit_alloc_tcval */
		CORBA_free(pNamedValue->argument._value);
	}

	/* clear typecode */
	satellite_release_typecode(pNamedValue->argument._type);

	/* allocated with satellite_new */
	satellite_delete(pNamedValue);
}

static void satellite_release_namedvalue_list(
		CORBA_NamedValue ** ppNamedValue, int length)
{
	int i;
	
	if (ppNamedValue == NULL)
		return;
	
	for (i = 0; i < length; i++)
	{
		/* release named values */
		satellite_release_namedvalue(ppNamedValue[i]);
		ppNamedValue[i] = NULL;
	}

	satellite_delete(ppNamedValue);
}

/*
 * post-process arguments (take care of output parameters, release memory)
 */
static zend_bool OrbitObject_ReleaseArguments(OrbitObject * pObject, 
		CORBA_Request request, OperationType * pOperation, int argumentCount, 
		const zval ** ppArguments, CORBA_NamedValue ** ppNamedValue)
{
	/* TODO: handle output parameters */

	return TRUE;
}



/*
 * call a function
 */
zend_bool  OrbitObject_CallFunction(OrbitObject  * pObject, 
		const char * pFunctionName, int parameterCount, const zval ** ppParameters, zval * pReturnValue)
{
	zend_bool success;
	CORBA_Request request = NULL;
	OperationType * p_operation = NULL;
	CORBA_NamedValue ** pp_arguments = NULL;
	CORBA_NamedValue * p_result = NULL;
	CORBA_TypeCode * p_exception_list = NULL;
	
#if PROFILE
	int i;
	struct timeval tv[4];
	gettimeofday(&tv[0], NULL);
#endif
	
	/* clear exception */
	CORBA_exception_free(orbit_get_environment());
	
	p_operation = InterfaceType_FindOperation(pObject->mpInterface, pFunctionName);

	if (p_operation == NULL)
	{
		/* no such operation */
		zend_error(E_WARNING, "(Satellite) unknown operation name '%s' in interface '%s'",
				pFunctionName, pObject->mCorbaObject->type_id);
		goto error;
	}

	p_exception_list = OperationType_GetExceptionList(p_operation);
	
	/* XXX: it's ok if this returns NULL, because the operation may be void! */
	p_result = OrbitObject_PrepareResult(pObject, p_operation);

	/* create the request */
	CORBA_Object_create_request2(
		pObject->mCorbaObject,									/* object      */
		NULL,																		/* context     */
		OperationType_GetName(p_operation),			/* name        */
		NULL,																		/* arg_list    */
		p_result,																/* result      */
		p_exception_list,												/* user exception list */
		&request,																/* request     */
		CORBA_OUT_LIST_MEMORY,									/* flags       */
		orbit_get_environment()									/* environment */
	);

	/* check for exception */
	if (orbit_caught_exception())
		goto error;

	/* add parameters */
	pp_arguments = satellite_new_n(CORBA_NamedValue*, parameterCount);
	success = OrbitObject_AddArguments(pObject, 
			request, p_operation, parameterCount, ppParameters, pp_arguments);

	if (!success)
	{
		/* bad arguments */
		goto error;
	}

#if PROFILE
	gettimeofday(&tv[1], NULL);
#endif

	/* send request and get response */
	CORBA_Request_invoke(request, 0, orbit_get_environment());

#if PROFILE
	gettimeofday(&tv[2], NULL);
#endif

	if (orbit_caught_exception())
		goto error;

	/* release arguments */
	success = OrbitObject_ReleaseArguments(pObject, 
			request, p_operation, parameterCount, ppParameters, pp_arguments);
	if (!success)
		goto error;
	
	/* take care of return value */
	if (p_result != NULL)
	{
		orbit_namedvalue_to_zval(p_result, pReturnValue);
	}

#if PROFILE
	gettimeofday(&tv[3], NULL);
	printf("%s\n", OperationType_GetName(p_operation));
	for(i = 0; i < 4; i++)
		printf("%i:%i\n", tv[i].tv_sec, tv[i].tv_usec);
#endif

	success = TRUE;
	goto exit;
	
error:
	/* TODO: all errors above should set exceptions! */
	success = FALSE;

exit:
	CORBA_Object_release((CORBA_Object)request, orbit_get_environment());
	satellite_release_namedvalue(p_result);	
	satellite_release_namedvalue_list(pp_arguments, parameterCount);
	orbit_delete(p_operation);
	TypeCodeList_release(p_exception_list);
	
	return success;
}

#define GET_PREFIX "_get_"
#define SET_PREFIX "_set_"
#define GET_PREFIX_LENGTH (sizeof(GET_PREFIX)-1)
#define SET_PREFIX_LENGTH (sizeof(SET_PREFIX)-1)


/*
 * add an argument to a function call
 */
static zend_bool OrbitObject_AddAttributeArgument(OrbitObject * pObject, 
		CORBA_Request request, AttributeType * pAttribute, const zval * pSource)
{
	CORBA_NamedValue destination;
	memset(&destination, 0, sizeof(CORBA_NamedValue));
	
	destination.argument._type = AttributeType_GetType(pAttribute);
	destination.arg_modes 		 = CORBA_ARG_IN;

	if (!orbit_zval_to_namedvalue(pSource, &destination))
		return FALSE;

	/* add parameter to request */
	CORBA_Request_add_arg(
		request,											/* request  */
		NULL,													/* arg_name */
		destination.argument._type,		/* type     */
		destination.argument._value,		/* value    */
		destination.len,								/* length   */
		destination.arg_modes,					/* flags    */
		orbit_get_environment()
	);

	if (orbit_caught_exception())
		return FALSE;

	return TRUE;
}



/*
 * set a php property, or rather a corba attribute
 */
zend_bool  OrbitObject_PutProperty(OrbitObject  * pObject, 
		const char * pPropertyName, const zval * pValue)
{
	zend_bool success;
	char * p_name = NULL;
	CORBA_Request request = NULL;
	AttributeType * p_attribute = NULL;

	/* clear exception */
	CORBA_exception_free(orbit_get_environment());

	/* find attribute type */
	p_attribute = InterfaceType_FindAttribute(pObject->mpInterface, pPropertyName);

	if (p_attribute == NULL)
	{
		/*printf("InterfaceType_FindAttribute failed for property %s\n", pPropertyName);*/
		/* no such atttribute */
		zend_error(E_WARNING, "(Satellite) unknown attribute name '%s' in interface '%s'",
				pPropertyName, pObject->mCorbaObject->type_id);
		goto OrbitObject_PutProperty_error;
	}

	if (AttributeType_IsReadonly(p_attribute))
	{
		/* can't set a readonly attribute! */
		goto OrbitObject_PutProperty_error;
	}

	/* create operation name */
	p_name = orbit_new_n(char, strlen(pPropertyName) + SET_PREFIX_LENGTH + 1);
	strcpy(p_name, SET_PREFIX);
	strcat(p_name, AttributeType_GetName(p_attribute));

	/* create the request */
	CORBA_Object_create_request(
		pObject->mCorbaObject,									/* object      */
		NULL,																		/* context     */
		p_name,																	/* name        */
		NULL,																		/* arg_list    */
		NULL,																		/* result      */
		&request,																/* request     */
		CORBA_OUT_LIST_MEMORY,									/* flags       */
		orbit_get_environment()									/* environment */
	);

	/* check for exception */
	if (orbit_caught_exception())
		goto OrbitObject_PutProperty_error;

	if (request == NULL)
		goto OrbitObject_PutProperty_error;

	success = OrbitObject_AddAttributeArgument(
			pObject, request, p_attribute, pValue);

	if (!success)
		goto OrbitObject_PutProperty_error;

	/* send request and get response */
	CORBA_Request_invoke(request, 0, orbit_get_environment());

	if (orbit_caught_exception())
		goto OrbitObject_PutProperty_error;
	
	success = TRUE;
	goto OrbitObject_PutProperty_exit;
	
OrbitObject_PutProperty_error:
	/* TODO: all errors above should set exceptions! */
	success = FALSE;
	
OrbitObject_PutProperty_exit:
	CORBA_Object_release((CORBA_Object)request, orbit_get_environment());
	orbit_delete(p_attribute);
	orbit_delete(p_name);
	
	return success;
}

/* 
 * prepare a function call result 
 */
static CORBA_NamedValue * OrbitObject_PrepareAttributeResult(OrbitObject * pObject, 
		AttributeType * pAttribute)
{
	CORBA_NamedValue * p_result = orbit_new(CORBA_NamedValue);

	p_result->argument._type = AttributeType_GetType(pAttribute);

	if (p_result->argument._type == NULL)
	{
/*		printf("AttributeType_GetType failed for attribute %s\n", 
				AttributeType_GetName(pAttribute));*/
		orbit_delete(p_result);
		p_result = NULL;
	}
	else
	{
		orbit_zval_to_namedvalue(NULL, p_result);
	}
	
	return p_result;
}


/*
 * get a php property, equal to a corba attribute
 */
zend_bool  OrbitObject_GetProperty(OrbitObject  * pObject, 
		const char * pPropertyName, zval * pReturnValue)
{
	zend_bool success;
	char * p_name = NULL;
	CORBA_Request request = NULL;
	AttributeType * p_attribute = NULL;
	CORBA_NamedValue * p_result = NULL;

#if PROFILE
	int i;
	struct timeval tv[4];
	gettimeofday(&tv[0], NULL);
#endif
	
	/* clear exception */
	CORBA_exception_free(orbit_get_environment());

	/* find attribute type */
	p_attribute = InterfaceType_FindAttribute(pObject->mpInterface, pPropertyName);

	if (p_attribute == NULL)
	{
		/*printf("InterfaceType_FindAttribute failed for property %s\n", pPropertyName);*/
		/* no such atttribute */
		zend_error(E_WARNING, "(Satellite) unknown attribute name '%s' in interface '%s'",
				pPropertyName, pObject->mCorbaObject->type_id);
		goto OrbitObject_GetProperty_error;
	}

	/* prepare result */
	p_result = OrbitObject_PrepareAttributeResult(pObject, p_attribute);

	if (p_result == NULL)
	{
		/* probably bad return type */
		goto OrbitObject_GetProperty_error;
	}

	/* create operation name */
	p_name = orbit_new_n(char, strlen(pPropertyName) + GET_PREFIX_LENGTH + 1);
	strcpy(p_name, GET_PREFIX);
	strcat(p_name, AttributeType_GetName(p_attribute));

	/* create the request */
	CORBA_Object_create_request(
		pObject->mCorbaObject,									/* object      */
		NULL,																		/* context     */
		p_name,																	/* name        */
		NULL,																		/* arg_list    */
		p_result,																/* result      */
		&request,																/* request     */
		CORBA_OUT_LIST_MEMORY,									/* flags       */
		orbit_get_environment()									/* environment */
	);

	/* check for exception */
	if (orbit_caught_exception())
		goto OrbitObject_GetProperty_error;

	if (request == NULL)
		goto OrbitObject_GetProperty_error;

#if PROFILE
	gettimeofday(&tv[1], NULL);
#endif

	/* send request and get response */
	CORBA_Request_invoke(request, 0, orbit_get_environment());

#if PROFILE
	gettimeofday(&tv[2], NULL);
#endif
	
	if (orbit_caught_exception())
		goto OrbitObject_GetProperty_error;
	
	/* take care of return value */
	orbit_namedvalue_to_zval(p_result, pReturnValue);

#if 0 /* PROFILE */
	gettimeofday(&tv[3], NULL);
	printf("%s\n", p_name);
	for(i = 0; i < 4; i++)
		printf("%i:%i\n", tv[i].tv_sec, tv[i].tv_usec);
#endif
	
	success = TRUE;
	goto OrbitObject_GetProperty_exit;
	
OrbitObject_GetProperty_error:
	/* TODO: all errors above should set exceptions! */
	success = FALSE;
	
OrbitObject_GetProperty_exit:
	CORBA_Object_release((CORBA_Object)request, orbit_get_environment());
	orbit_delete(p_attribute);
	orbit_delete(p_name);
	satellite_release_namedvalue(p_result);
	
	return success;
}

