/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  ORBit: A CORBA v2.2 ORB
 *
 *  Copyright (C) 1998 Richard H. Porter and Red Hat Software
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  Author: Phil Dawes <philipd@parallax.co.uk>
 *	    Elliot Lee <sopwith@redhat.com>
 *
 */

/*
 *   ORBit specific POA funcitons.
 *
 */

#include <string.h>
#include "orbit.h"
#include "orbit_poa_type.h"
#include "orbit_poa.h"

static void ORBit_POAManager_release(PortableServer_POAManager poa_mgr,
				     CORBA_Environment *ev);

static void ORBit_POA_release(PortableServer_POA poa,
			      CORBA_Environment *ev);

static PortableServer_Servant
ORBit_POA_ServantManager_use_servant(PortableServer_POA poa,
				     GIOPRecvBuffer *recv_buffer,
				     PortableServer_ServantLocator_Cookie *the_cookie,
				     CORBA_Environment *ev);
static void
ORBit_POA_ServantManager_unuse_servant(PortableServer_Servant servant,
				       PortableServer_POA poa,
				       GIOPRecvBuffer *recv_buffer,
				       PortableServer_ServantLocator_Cookie cookie,
				       CORBA_Environment *ev);
					   


static const ORBit_RootObject_Interface CORBA_POAManager_epv =
{
	(gpointer)ORBit_POAManager_release,
};

static const ORBit_RootObject_Interface CORBA_POA_epv =
{
	(gpointer)ORBit_POA_release,
};

guint
g_sequence_octet_hash(CORBA_sequence_octet *so)
{
	guint retval = 0, i, n;

	n = so->_length/4;
	for(i = 0; i < n; i++)
		retval ^= ((CORBA_unsigned_long *)so->_buffer)[i];

	return retval;
}

gint
g_sequence_octet_compare(CORBA_sequence_octet *s1, CORBA_sequence_octet *s2)
{
	if(s2->_length != s1->_length)
		return FALSE;

	return !memcmp(s1->_buffer, s2->_buffer, s1->_length);
}

PortableServer_POAManager 
ORBit_POAManager_new(CORBA_Environment *ev)
{
	PortableServer_POAManager poa_mgr;

	poa_mgr = g_new0(struct PortableServer_POAManager_type, 1);

	if(poa_mgr == NULL) {
		CORBA_exception_set_system(ev, ex_CORBA_NO_MEMORY, CORBA_COMPLETED_NO);
		goto error;
	}

	/* Initialise poa manager */

	ORBit_pseudo_object_init(ORBIT_PSEUDO_OBJECT(poa_mgr),
				 ORBIT_PSEUDO_POAMANAGER, ev);
	ORBIT_ROOT_OBJECT(poa_mgr)->refs = 0;
	ORBit_RootObject_set_interface(ORBIT_ROOT_OBJECT(poa_mgr),
				       (gpointer)&CORBA_POAManager_epv, ev);

	poa_mgr->poa_collection = NULL;
	poa_mgr->state = PortableServer_POAManager_HOLDING;

	return poa_mgr;

error:
	if(poa_mgr != NULL){
		ORBit_POAManager_release(poa_mgr, ev);
	}
	return NULL;
}

static void
ORBit_POAManager_release(PortableServer_POAManager poa_mgr,
			 CORBA_Environment *ev)
{

	if(--(ORBIT_ROOT_OBJECT(poa_mgr)->refs) > 0)
		return;

	if(poa_mgr != NULL) {
		if(poa_mgr->poa_collection != NULL)  {
			g_slist_free(poa_mgr->poa_collection);
			poa_mgr->poa_collection = NULL;
		}
		g_free(poa_mgr);
		poa_mgr = NULL;
	}
}

static void
ORBit_POAManager_register_poa(PortableServer_POAManager poa_mgr, 
			      PortableServer_POA poa,
			      CORBA_Environment *ev)
{
	poa_mgr->poa_collection = g_slist_remove(poa_mgr->poa_collection, poa);
	poa_mgr->poa_collection = 
		g_slist_append(poa_mgr->poa_collection, poa);
}

static void
ORBit_POAManager_unregister_poa(PortableServer_POAManager poa_mgr, 
				PortableServer_POA poa,
				CORBA_Environment *ev)
{
	poa_mgr->poa_collection = g_slist_remove(poa_mgr->poa_collection, poa);
}

static void
ORBit_POA_set_policy(PortableServer_POA poa,
		     CORBA_Policy policy,
		     CORBA_Environment *ev)
{
	switch(policy->policy_type) {
	case PortableServer_THREAD_POLICY_ID:
		poa->thread = ((PortableServer_ThreadPolicy)policy)->value;
		break;
	case PortableServer_LIFESPAN_POLICY_ID:
		poa->lifespan = ((PortableServer_LifespanPolicy)policy)->value;
		break;
	case PortableServer_ID_UNIQUENESS_POLICY_ID:
		poa->id_uniqueness = ((PortableServer_IdUniquenessPolicy)policy)->value;
		break;
	case PortableServer_ID_ASSIGNMENT_POLICY_ID:
		poa->id_assignment = ((PortableServer_IdAssignmentPolicy)policy)->value;
		break;
	case PortableServer_IMPLICIT_ACTIVATION_POLICY_ID:
	  poa->implicit_activation = ((PortableServer_ImplicitActivationPolicy)policy)->value;
		break;
	case PortableServer_SERVANT_RETENTION_POLICY_ID:
	  poa->servant_retention = ((PortableServer_ServantRetentionPolicy)policy)->value;
		break;
	case PortableServer_REQUEST_PROCESSING_POLICY_ID:
		poa->request_processing = ((PortableServer_ServantRetentionPolicy)policy)->value;
		break;
	default:
		g_warning("Unknown policy type, cannot set it on this POA");
	}
}


static void
ORBit_POA_check_policy_conflicts(PortableServer_POA poa,
		     CORBA_Environment *ev)
{

  /* Check for those policy combinations that aren't allowed */
  if ((poa->servant_retention == PortableServer_NON_RETAIN &&
       poa->request_processing == PortableServer_USE_ACTIVE_OBJECT_MAP_ONLY) ||
      
      (poa->request_processing == PortableServer_USE_DEFAULT_SERVANT &&
       poa->id_uniqueness == PortableServer_UNIQUE_ID) ||
      
      (poa->implicit_activation == PortableServer_IMPLICIT_ACTIVATION &&
       (poa->id_assignment == PortableServer_USER_ID ||
	poa->servant_retention == PortableServer_NON_RETAIN))
      )
    {
      CORBA_exception_set(ev, CORBA_USER_EXCEPTION,
			  ex_PortableServer_POA_InvalidPolicy,
			  NULL);
    }
}
      

static void
ORBit_POA_set_policylist(PortableServer_POA poa,
			 CORBA_PolicyList *policies,
			 CORBA_Environment *ev)
{
	CORBA_unsigned_long i;

	for(i = 0; i < policies->_length; i++) {
		if(ev->_major != CORBA_NO_EXCEPTION)
			break;
		ORBit_POA_set_policy(poa, policies->_buffer[i], ev);
	}
}

PortableServer_POA 
ORBit_POA_new(CORBA_ORB orb,
	      CORBA_char *adapter_name,
	      PortableServer_POAManager the_POAManager,
	      CORBA_PolicyList *policies,
	      CORBA_Environment *ev)
{
	PortableServer_POA poa;

	/* Create the object */
	poa = (PortableServer_POA) g_new0(struct PortableServer_POA_type, 1); 
	if(poa == NULL) {
		CORBA_exception_set_system(ev, ex_CORBA_NO_MEMORY, CORBA_COMPLETED_NO);
		goto error;
	}

	ORBit_pseudo_object_init(ORBIT_PSEUDO_OBJECT(poa), ORBIT_PSEUDO_POA, ev);

	ORBIT_ROOT_OBJECT(poa)->refs = 0;
	ORBit_RootObject_set_interface(ORBIT_ROOT_OBJECT(poa),
				       (gpointer)&CORBA_POA_epv, ev);

	if(ev->_major != CORBA_NO_EXCEPTION) goto error;

	/* If no POAManager was specified, create one */
	if(the_POAManager == NULL) {
	  the_POAManager = ORBit_POAManager_new(ev);
	}	  

	/* Register this poa with the poa manager */
	if(the_POAManager != NULL)
		ORBit_POAManager_register_poa(the_POAManager,poa,ev);
	if(ev->_major != CORBA_NO_EXCEPTION) goto error;

	/* Wire up the poa_manager */
	poa->the_POAManager = the_POAManager;

	/* Initialise the child poas table */
	poa->child_POAs = NULL;      /* initialise the slist */

	poa->held_requests = NULL;

	poa->poaID = orb->poas->len;
	g_ptr_array_set_size(orb->poas, orb->poas->len + 1);
	g_ptr_array_index(orb->poas, poa->poaID) = poa;

	poa->orb = orb;

	g_return_val_if_fail(ev->_major == CORBA_NO_EXCEPTION, NULL);

	/* Need to initialise poa policies etc.. here */
	poa->thread = PortableServer_ORB_CTRL_MODEL;
	poa->lifespan = PortableServer_TRANSIENT;
	poa->id_uniqueness = PortableServer_UNIQUE_ID;
	poa->id_assignment = PortableServer_SYSTEM_ID;
	poa->servant_retention = PortableServer_RETAIN;
	poa->request_processing = PortableServer_USE_ACTIVE_OBJECT_MAP_ONLY;
	poa->implicit_activation = PortableServer_NO_IMPLICIT_ACTIVATION;
	if (policies) {
	  ORBit_POA_set_policylist(poa, policies, ev);
	  ORBit_POA_check_policy_conflicts(poa, ev);
	  if(ev->_major != CORBA_NO_EXCEPTION) goto error;
	}

	/* copy the name  */
	poa->the_name = CORBA_string_dup(adapter_name);

	poa->active_object_map = g_hash_table_new((GHashFunc)g_sequence_octet_hash,
						  (GCompareFunc)g_sequence_octet_compare);
	poa->objnum_to_obj = g_ptr_array_new();
	g_ptr_array_set_size(poa->objnum_to_obj, 1);
	g_ptr_array_index(poa->objnum_to_obj, 0) = NULL;

	return poa;

error:
	if(poa && poa->the_name){
		CORBA_free(poa->the_name);
	}

	if(poa != NULL){
		ORBit_POA_release(poa, NULL);
	}
	return NULL;
}

static void
ORBit_POA_release(PortableServer_POA poa,
		  CORBA_Environment *ev)
{
	ORBIT_ROOT_OBJECT_UNREF(poa);

	if(ORBIT_ROOT_OBJECT(poa)->refs <= 0) {
		CORBA_free(poa->the_name);

		g_slist_foreach(poa->child_POAs, (GFunc)CORBA_Object_release,
				ev);

		if(poa->parent_poa)
			ORBit_POA_remove_child(poa->parent_poa, poa, ev);

		ORBit_POAManager_unregister_poa(poa->the_POAManager, 
						poa, ev);

		g_ptr_array_index(poa->orb->poas, poa->poaID) = NULL;

		g_free(poa);
	}
}

void
ORBit_POA_add_child(PortableServer_POA poa,
		    PortableServer_POA child_poa,
		    CORBA_Environment *ev)

{
	g_return_if_fail(poa != NULL);
	g_return_if_fail(child_poa != NULL);

	poa->child_POAs = g_slist_prepend(poa->child_POAs, child_poa);
}

void
ORBit_POA_remove_child(PortableServer_POA poa,
		       PortableServer_POA child_poa,
		       CORBA_Environment *ev)
{
	g_return_if_fail(poa != NULL);
	g_return_if_fail(child_poa != NULL);

	poa->child_POAs = g_slist_remove(poa->child_POAs, child_poa);
}

void
ORBit_POA_handle_request(GIOPRecvBuffer *recv_buffer,
			 PortableServer_POA poa)
{
	PortableServer_ServantBase *servant;
	PortableServer_ServantLocator_Cookie cookie;
	ORBit_POAObject *obj_impl;
	ORBitSkeleton skel;
	gpointer imp;
	CORBA_Environment ev;
	GIOPSendBuffer *send_buffer;

	g_assert(poa);
	g_assert(recv_buffer);

	CORBA_exception_init(&ev);
	
	g_assert(poa);

	switch(poa->the_POAManager->state) {
	case PortableServer_POAManager_HOLDING:
		poa->held_requests = g_slist_prepend(poa->held_requests,
						     recv_buffer);
		return;
		break;
	case PortableServer_POAManager_DISCARDING:
		send_buffer = giop_send_reply_buffer_use(GIOP_MESSAGE_BUFFER(recv_buffer)->connection,
							 NULL,
							 recv_buffer->message.u.request.request_id,
							 CORBA_SYSTEM_EXCEPTION);
		CORBA_exception_set_system(&ev,
					    ex_CORBA_TRANSIENT,
					    CORBA_COMPLETED_NO);
		ORBit_send_system_exception(send_buffer, &ev);
		giop_send_buffer_write(send_buffer);
		giop_recv_buffer_unuse(recv_buffer);
		giop_send_buffer_unuse(send_buffer);
		CORBA_exception_free(&ev);
		return;
		break;
	case PortableServer_POAManager_INACTIVE:
		send_buffer = giop_send_reply_buffer_use(GIOP_MESSAGE_BUFFER(recv_buffer)->connection,
							 NULL,
							 recv_buffer->message.u.request.request_id,
							 CORBA_SYSTEM_EXCEPTION);
		CORBA_exception_set_system(&ev,
					    ex_CORBA_OBJ_ADAPTER,
					    CORBA_COMPLETED_NO);
		ORBit_send_system_exception(send_buffer, &ev);
		giop_send_buffer_write(send_buffer);
		giop_recv_buffer_unuse(recv_buffer);
		giop_send_buffer_unuse(send_buffer);
		CORBA_exception_free(&ev);
		return;
		break;
	case PortableServer_POAManager_ACTIVE:
	default:
		break;
	}

	servant = NULL;

	if(poa->servant_retention == PortableServer_RETAIN) {
		gpointer nptr;
		CORBA_unsigned_long onum;

		if(recv_buffer->message.u.request.object_key._length
		   != sizeof(CORBA_unsigned_long) * 2) {
			CORBA_exception_set_system(&ev,
						   ex_CORBA_OBJECT_NOT_EXIST,
						   CORBA_COMPLETED_NO);
			goto errout;
		}

		nptr = recv_buffer->message.u.request.object_key._buffer;

		onum = *((CORBA_unsigned_long *)((guchar *)nptr + sizeof(CORBA_unsigned_long)));
		obj_impl = g_ptr_array_index(poa->objnum_to_obj, onum);
		if(obj_impl
		   && GPOINTER_TO_UINT(obj_impl) > poa->objnum_to_obj->len)
			servant = obj_impl->servant;
	}

	if(!servant) {
		switch(poa->request_processing) {
		case PortableServer_USE_SERVANT_MANAGER:
			servant = ORBit_POA_ServantManager_use_servant(poa,
								       recv_buffer,
								       &cookie,
								       &ev);
			break;
		case PortableServer_USE_DEFAULT_SERVANT:
			servant = poa->default_servant;
			if(servant == NULL)
				CORBA_exception_set_system(&ev,
							   ex_CORBA_OBJ_ADAPTER,
							   CORBA_COMPLETED_NO);
			break;
		case PortableServer_USE_ACTIVE_OBJECT_MAP_ONLY:
		default:
			CORBA_exception_set_system(&ev,
						   ex_CORBA_OBJECT_NOT_EXIST,
						   CORBA_COMPLETED_NO);
			goto errout;
		}
	}

	g_assert(poa);
	g_assert(servant);

	skel = ORBIT_OBJECT_KEY(servant->_private)->class_info->relay_call(servant, recv_buffer, &imp);

	skel(servant, recv_buffer, &ev, imp);

	if(poa->request_processing == PortableServer_USE_SERVANT_MANAGER) {
		ORBit_POA_ServantManager_unuse_servant(servant,
						       poa,
						       recv_buffer,
						       cookie, &ev);
	}

 errout:
	/* XXX send an exception back */
	CORBA_exception_free(&ev);
}

PortableServer_POA
ORBit_POA_find_POA_for_object_key(PortableServer_POA root_poa,
				  CORBA_sequence_octet *key)
{
	CORBA_unsigned_long pid;

	pid = *((CORBA_unsigned_long *)key->_buffer);
	if(pid < root_poa->orb->poas->len)
		return g_ptr_array_index(root_poa->orb->poas, pid);
	else
		return NULL;
}

void
ORBit_POA_find_object_key_for_oid(PortableServer_POA poa,
				  ORBit_POAObject *obj,
				  CORBA_sequence_octet *retval)
{
	g_return_if_fail(poa && obj);

	retval->_length = sizeof(CORBA_unsigned_long) * 2;
	retval->_buffer = CORBA_octet_allocbuf(retval->_length);
	CORBA_sequence_set_release(retval, CORBA_TRUE);
	*((CORBA_unsigned_long *)retval->_buffer) = poa->poaID;
	*((CORBA_unsigned_long *)(retval->_buffer + sizeof(CORBA_unsigned_long))) = obj->objnum;
}

DEFINE_LOCK(id_assignment_counter);
static int id_assignment_counter = 0;

PortableServer_ObjectId *
ORBit_POA_allocate_oid(PortableServer_POA poa,
		       const char *basis)
{
	PortableServer_ObjectId *new_objid;
	char buf[512];
	int len;

	new_objid = (PortableServer_ObjectId *)CORBA_sequence_octet__alloc();

	GET_LOCK(id_assignment_counter);
	g_snprintf(buf, sizeof(buf), "%s%d", basis?basis:"Object",
		   id_assignment_counter);
	id_assignment_counter++;
	RELEASE_LOCK(id_assignment_counter);

	len = strlen(buf)+1;
	new_objid->_buffer = CORBA_octet_allocbuf(len);
	new_objid->_length = len;
	new_objid->_release = CORBA_TRUE;

	strcpy(new_objid->_buffer, buf);

	return new_objid;
}

static PortableServer_Servant
ORBit_POA_ServantManager_use_servant(PortableServer_POA poa,
				     GIOPRecvBuffer *recv_buffer,
				     PortableServer_ServantLocator_Cookie *the_cookie,
				     CORBA_Environment *ev)
{
	PortableServer_ObjectId oid;

	memcpy(&oid, &recv_buffer->message.u.request.object_key, sizeof(oid));
	oid._buffer += sizeof(CORBA_unsigned_long);
	oid._length -= sizeof(CORBA_unsigned_long);

	if(poa->servant_retention == PortableServer_RETAIN) {
		POA_PortableServer_ServantActivator *sm;
		POA_PortableServer_ServantActivator__epv *epv;
		
		sm = (POA_PortableServer_ServantActivator *)poa->servant_manager;
		epv = sm->vepv->PortableServer_ServantActivator_epv;
		return epv->incarnate(sm, &oid, poa, ev);
	} else {
		POA_PortableServer_ServantLocator *sm;
		POA_PortableServer_ServantLocator__epv *epv;

		sm = (POA_PortableServer_ServantLocator *)poa->servant_manager;
		epv = sm->vepv->PortableServer_ServantLocator_epv;
		return epv->preinvoke(sm, &oid,
				      poa, recv_buffer->message.u.request.operation,
				      the_cookie,
				      ev);
	}
}

static void
ORBit_POA_ServantManager_unuse_servant(PortableServer_Servant servant,
				       PortableServer_POA poa,
				       GIOPRecvBuffer *recv_buffer,
				       PortableServer_ServantLocator_Cookie cookie,
				       CORBA_Environment *ev)
{
	PortableServer_ObjectId oid;
	POA_PortableServer_ServantLocator *sm;
	POA_PortableServer_ServantLocator__epv *epv;

	if(poa->servant_retention != PortableServer_NON_RETAIN)
		return;

	memcpy(&oid, &recv_buffer->message.u.request.object_key, sizeof(oid));
	oid._buffer += sizeof(CORBA_unsigned_long);
	oid._length -= sizeof(CORBA_unsigned_long);


	sm = (POA_PortableServer_ServantLocator *)poa->servant_manager;
	epv = sm->vepv->PortableServer_ServantLocator_epv;
	
	epv->postinvoke(sm, &oid,
			poa, recv_buffer->message.u.request.operation,
			cookie, servant, ev);
}

typedef struct {
	PortableServer_POA poa;
	CORBA_Environment *ev;
} EtherealizeInfo;

void
ORBit_POA_etherealize_object(PortableServer_ObjectId *oid,
			     ORBit_POAObject *obj_impl,
			     EtherealizeInfo *ei)
{
	POA_PortableServer_ServantActivator__epv *epv;
	POA_PortableServer_ServantActivator *sm;

	g_assert(ei->poa->servant_manager);

	g_hash_table_remove(ei->poa->active_object_map,
			    obj_impl->object_id);

	sm = (POA_PortableServer_ServantActivator *)ei->poa->servant_manager;
	epv = sm->vepv->PortableServer_ServantActivator_epv;
	epv->etherealize(sm, obj_impl->object_id, ei->poa,
			 obj_impl->servant,
			 CORBA_TRUE, CORBA_FALSE, ei->ev);
}

void
ORBit_POA_etherealize_objects(PortableServer_POA poa,
			      CORBA_Environment *ev)
{
	EtherealizeInfo ei;

	ei.poa = poa;
	ei.ev = ev;

	if(poa->servant_retention == PortableServer_RETAIN
	   && poa->request_processing == PortableServer_USE_SERVANT_MANAGER) {

		g_hash_table_foreach(poa->active_object_map,
				     (GHFunc)ORBit_POA_etherealize_object,
				     &ei);
	}
}
