// **********************************************************************
//
// Copyright (c) 1999
// Object Oriented Concepts, Inc.
// Billerica, MA, USA
//
// All Rights Reserved
//
// **********************************************************************

#include <OB/Basic.h>
#include <OB/Except.h>
#include <OB/Template.h>
#include <OB/TemplateI.h>
#include <OB/Declarations.h>
#include <OB/Any.h>
#include <OB/IOP.h>
#include <OB/Object.h>
#include <OB/Policy.h>
#include <OB/IntRep.h>
#include <OB/ORB.h>
#include <OB/ImplRep.h>
#include <OB/OCI.h>
#include <OB/OBPolicies.h>

#include <PolicyValidator.h>

// ----------------------------------------------------------------------
// External, non-inline duplicate/release for templates
// ----------------------------------------------------------------------

void
OBDuplicate(OBClient_ptr p)
{
    if(p)
	p -> _OB_incRef();
}

void
OBRelease(OBClient_ptr p)
{
    if(p)
	p -> _OB_decRef();
}

void
OBDuplicate(CORBA_Object_ptr p)
{
    if(p)
	p -> _OB_incRef();
}

void
OBRelease(CORBA_Object_ptr p)
{
    if(p)
	p -> _OB_decRef();
}

// ----------------------------------------------------------------------
// Template instantiations
// ----------------------------------------------------------------------

#ifndef HAVE_NO_EXPLICIT_TEMPLATES
template class OBObjVar< OBClient >;
template class OBObjForSeq< OBClient >;
template class OBObjSeq< OBClient >;
template class OBSeqVar< OBObjSeq< OBClient > >;

template class OBObjVar< CORBA_Object >;
template class OBObjForSeq< CORBA_Object >;
template class OBObjSeq< CORBA_Object >;
template class OBSeqVar< OBObjSeq< CORBA_Object > >;
#else
#ifdef HAVE_PRAGMA_DEFINE
#pragma define(OBObjVar< OBClient >)
#pragma define(OBObjForSeq< OBClient >)
#pragma define(OBObjSeq< OBClient >)
#pragma define(OBSeqVar< OBObjSeq< OBClient > >)

#pragma define(OBObjVar< CORBA_Object >)
#pragma define(OBObjForSeq< CORBA_Object >)
#pragma define(OBObjSeq< CORBA_Object >)
#pragma define(OBSeqVar< OBObjSeq< CORBA_Object > >)
#endif
#endif

// ----------------------------------------------------------------------
// Object constructor and destructor
// ----------------------------------------------------------------------

CORBA_Object::CORBA_Object()
    : _ob_tout_(-1), _ob_retry_(false)
{
}

CORBA_Object::~CORBA_Object()
{
    if(!CORBA_is_nil(_ob_clt_))
    {
	CORBA_ORB_ptr orb = CORBA_ORB::_OB_instance_nodup();
	//
	// If the ORB was already destroyed, don't care about decrementing
	// the client usage - the ORB's destructor destroyed all clients
	// anyway.
	//
	if(!CORBA_is_nil(orb))
	    orb -> _OB_decClientUsage(_ob_clt_);
    }
}

// ----------------------------------------------------------------------
// Object public member implementation
// ----------------------------------------------------------------------

CORBA_ImplementationDef_ptr
CORBA_Object::_get_implementation()
{
    if(CORBA_is_nil(_ob_clt_))
        throw CORBA_NO_IMPLEMENT();

    CORBA_ULong _ob_off = _ob_clt_ -> offset(this, "_implementation");
    
    OBBuffer _ob_buf(_ob_off);
    
    bool _ob_sw, _ob_ex, _ob_fo;
    _ob_off = _ob_clt_ -> request(this, "_implementation", _ob_buf,
				  _ob_sw, _ob_fo, _ob_ex,
				  _ob_tout_, _ob_retry_);
    
    const CORBA_Octet* _ob_co = _ob_buf.data + _ob_off;

    if(_ob_fo)
    {
	_OB_forward(_ob_co, _ob_sw);
	return _get_implementation();
    }

    if(_ob_ex)
        throw CORBA_UNKNOWN();

    CORBA_ImplementationDef_ptr _ob_r = CORBA_ImplementationDef::_nil();
    OBUnmarshal(_ob_r, _ob_co, _ob_sw);
	
    return _ob_r;
}

CORBA_InterfaceDef_ptr
CORBA_Object::_get_interface()
{
    if(CORBA_is_nil(_ob_clt_))
        throw CORBA_NO_IMPLEMENT();
    
    CORBA_ULong _ob_off = _ob_clt_ -> offset(this, "_interface");
    
    OBBuffer _ob_buf(_ob_off);
    
    bool _ob_sw, _ob_ex, _ob_fo;
    _ob_off = _ob_clt_ -> request(this, "_interface", _ob_buf,
				  _ob_sw, _ob_fo, _ob_ex,
				  _ob_tout_, _ob_retry_);
    
    const CORBA_Octet* _ob_co = _ob_buf.data + _ob_off;

    if(_ob_fo)
    {
	_OB_forward(_ob_co, _ob_sw);
	return _get_interface();
    }

    if(_ob_ex)
        throw CORBA_UNKNOWN();

    CORBA_InterfaceDef_ptr _ob_r = CORBA_InterfaceDef::_nil();
    OBUnmarshal(_ob_r, _ob_co, _ob_sw);
	
    return _ob_r;
}

CORBA_Boolean
CORBA_Object::_is_a(const char* _ob_id)
{
    assert_nca(_ob_id, OBNCANullString);
    
    if(_OB_narrowHelp(_ob_id))
    {
	return true;
    }
    else
    {
	return _OB_remoteIsA(_ob_id);
    }
}

CORBA_Boolean
CORBA_Object::_non_existent()
{
    if(CORBA_is_nil(_ob_clt_))
        throw CORBA_NO_IMPLEMENT();
    
    CORBA_ULong _ob_off = _ob_clt_ -> offset(this, "_non_existent");
    
    OBBuffer _ob_buf(_ob_off);
    
    bool _ob_sw, _ob_ex, _ob_fo;
    _ob_off = _ob_clt_ -> request(this, "_non_existent", _ob_buf,
				  _ob_sw, _ob_fo, _ob_ex,
				  _ob_tout_, _ob_retry_);
    
    const CORBA_Octet* _ob_co = _ob_buf.data + _ob_off;

    if(_ob_fo)
    {
	_OB_forward(_ob_co, _ob_sw);
	return _non_existent();
    }

    if(_ob_ex)
        throw CORBA_UNKNOWN();
    
    CORBA_Boolean _ob_r;
    OBUnmarshal(_ob_r, _ob_co, _ob_sw);
    return _ob_r;
}

CORBA_Boolean
CORBA_Object::_is_equivalent(CORBA_Object_ptr p)
{
    //
    // Direct pointer comparison
    //
    if(p == this)
	return true;

    //
    // Local objects are never equivalent if the pointer
    // comparison fails
    //
    if(_is_local() || p -> _is_local())
	return false;

    //
    // Compare the IORs
    //
    CORBA_ORB_ptr orb = CORBA_ORB::_OB_instance_nodup();
    if(CORBA_is_nil(orb))
	throw CORBA_INITIALIZE("ORB not initialized");
    return orb -> _OB_equivalent(_OB_ior(), p -> _OB_ior());
}

CORBA_ULong
CORBA_Object::_hash(CORBA_ULong maximum)
{
    if(_is_local())
    {
	//
	// Calculate a local hash value
	//
	return ((CORBA_ULong)(unsigned long)this / 256 ) % (maximum + 1);
    }
    else
    {
	//
	// Get hash via the ORB
	//
	CORBA_ORB_ptr orb = CORBA_ORB::_OB_instance_nodup();
	if(CORBA_is_nil(orb))
	    throw CORBA_INITIALIZE("ORB not initialized");
	return orb -> _OB_hash(_OB_ior(), maximum);
    }
}

/*
 *
 * _request() and _create_request() implementations in DII.cpp
 *
 */

CORBA_Policy_ptr
CORBA_Object::_get_policy(CORBA_PolicyType policy_type)
{
    return _get_effective_policy_override(policy_type);
}

CORBA_Policy_ptr
CORBA_Object::_get_effective_policy_override(CORBA_PolicyType policy_type)
{
    if(_is_local())
        throw CORBA_BAD_OPERATION();

    CORBA_Policy_var policy = _OB_getPolicy(policy_type);
    if(CORBA_is_nil(policy))
        throw CORBA_INV_POLICY();
    return policy._retn();
}

CORBA_Object_ptr
CORBA_Object::_set_policy_overrides(const CORBA_PolicyList& np,
                                    CORBA_SetOverrideType set_add)
{
    if(_is_local())
        throw CORBA_BAD_OPERATION();

    //
    // Create a new object and copy the IOR
    //
    CORBA_Object_var p = new CORBA_Object;
    p -> _OB_ior(_OB_ior());

    //
    // Create the new policy list
    //
    CORBA_PolicyList newPolicies;
    if(set_add == CORBA_SET_OVERRIDE)
    {
        newPolicies = np;
    }
    else
    {
        newPolicies = _ob_policies_;

        CORBA_ULong npLen = np.length();
	for(CORBA_ULong i = 0 ; i < npLen ; ++i)
	{
	    CORBA_ULong len = newPolicies.length();
	    CORBA_ULong j;

	    for(j = 0 ; j < len ; ++j)
		if(newPolicies[j] -> policy_type() == np[i] -> policy_type())
		    break;

	    if(j < len)
		newPolicies[j] = np[i];
	    else
		newPolicies.append(np[i]);
	}
    }
    PolicyValidator::validatePolicies(newPolicies);
    p -> _OB_policies(newPolicies);

    //
    // Change object since the policies have changed
    //
    CORBA_ORB_ptr orb = CORBA_ORB::_OB_instance_nodup();
    if(CORBA_is_nil(orb))
	throw CORBA_INITIALIZE("ORB not initialized");
    orb -> _OB_changeObject(p, _ob_ior_);
    
    return p._retn();
}

CORBA_PolicyList*
CORBA_Object::_get_policy_overrides(const CORBA_PolicyTypeSeq& types)
{
    CORBA_ULong len = _ob_policies_.length();
    CORBA_ULong typesLen = types.length();

    CORBA_PolicyList_var policies = new CORBA_PolicyList(typesLen);

    for(CORBA_ULong i = 0 ; i < typesLen; ++i)
    {
        CORBA_PolicyType currType = types[i];
        for(CORBA_ULong j = 0 ; j < len ; ++j)
            if(_ob_policies_[j] -> policy_type() == currType)
                policies -> append(_ob_policies_[j]);
    }
    return policies._retn();
}

CORBA_Boolean
CORBA_Object::_validate_policies(const CORBA_PolicyList&)
{
    //
    // For now all policy combinations are valid.
    //
    return CORBA_TRUE;
}

CORBA_Boolean
CORBA_Object::_is_local()
{
    //
    // Remote objects have a client, local objects don't
    //
    return CORBA_is_nil(_ob_clt_);
}

CORBA_Boolean
CORBA_Object::_is_dynamic()
{
    if(CORBA_is_nil(_ob_clt_))
        throw CORBA_NO_IMPLEMENT();
    
    CORBA_ULong _ob_off = _ob_clt_ -> offset(this, "_is_dynamic");
    
    OBBuffer _ob_buf(_ob_off);
    
    bool _ob_sw, _ob_ex, _ob_fo;
    _ob_off = _ob_clt_ -> request(this, "_is_dynamic", _ob_buf,
				  _ob_sw, _ob_fo, _ob_ex,
				  _ob_tout_, _ob_retry_);
    
    const CORBA_Octet* _ob_co = _ob_buf.data + _ob_off;
    
    if(_ob_fo)
    {
	_OB_forward(_ob_co, _ob_sw);
	return _non_existent();
    }
    
    if(_ob_ex)
        throw CORBA_UNKNOWN();
    
    CORBA_Boolean _ob_r;
    OBUnmarshal(_ob_r, _ob_co, _ob_sw);
    return _ob_r;
}

CORBA_Long
CORBA_Object::_timeout()
{
    return _ob_tout_;
}

void
CORBA_Object::_timeout(CORBA_Long timeout)
{
    //
    // This needs to update the set of policies for this object
    //
    CORBA_ORB_ptr orb = CORBA_ORB::_OB_instance_nodup();
    if(CORBA_is_nil(orb))
	throw CORBA_INITIALIZE("ORB not initialized");

    CORBA_PolicyType policyType = OB_TIMEOUT_POLICY;
    CORBA_Policy_var policy;

    //
    // If timeout < 0 then remove the timeout policy, if present
    //
    if(timeout >= 0)
    {
        //
        // Create a new timeout policy
        //
        CORBA_Any any;
        any <<= (CORBA_ULong)timeout;
        policy  = orb -> create_policy(policyType, any);
    }
    else
    {
        timeout = -1;
    }
        
    //
    // Find the policy
    //
    CORBA_ULong policiesLen = _ob_policies_.length();
    CORBA_ULong i;
    for(i = 0; i < policiesLen; ++i)
	if(_ob_policies_[i] -> policy_type() == policyType)
            break;

    //
    // If timeout >= 0 then we want to either add a new timeout
    // policy, or replace the existing one
    //
    if(timeout >= 0)
    {
        if(i >= policiesLen)
            _ob_policies_.append(policy);
        else
            _ob_policies_[i] = policy;
    }
    //
    // Else we want to remove any existing timeout policy
    //
    else if(i < policiesLen)
        _ob_policies_.remove(i);

    //
    // Update the set of policies and associated state variables
    //
    _OB_policies(_ob_policies_);

    //
    // This should have updated the timeout
    //
    assert(_ob_tout_ == timeout);
}

bool
CORBA_Object::_retry()
{
    return _ob_retry_;
}

void
CORBA_Object::_retry(bool retry)
{
    //
    // This needs to update the set of policies for this object
    //
    CORBA_ORB_ptr orb = CORBA_ORB::_OB_instance_nodup();
    if(CORBA_is_nil(orb))
	throw CORBA_INITIALIZE("ORB not initialized");

    //
    // Create a new reconnect policy
    //
    CORBA_Any any;
    any <<= CORBA_Any::from_boolean(retry);
    CORBA_PolicyType policyType = OB_RECONNECT_POLICY;
    CORBA_Policy_var policy  = orb -> create_policy(policyType, any);

    //
    // Find the policy
    //
    CORBA_ULong policiesLen = _ob_policies_.length();
    CORBA_ULong i;
    for(i = 0; i < policiesLen; ++i)
	if(_ob_policies_[i] -> policy_type() == policyType)
            break;

    //
    // Either add a new reconnect policy, or replace the existing one
    //
    if(i >= policiesLen)
        _ob_policies_.append(policy);
    else
        _ob_policies_[i] = policy;

    //
    // Update the set of policies and associated state variables
    //
    _OB_policies(_ob_policies_);

    //
    // This should have updated the retry flag
    //
    assert(_ob_retry_ == retry);
}

OCI_TransportInfo_ptr
CORBA_Object::_get_oci_transport_info()
{
    //
    // Remote objects have a client, local objects don't
    //
    if(CORBA_is_nil(_ob_clt_))
        throw CORBA_BAD_OPERATION();

    OCI_Transport_var transport = _ob_clt_ -> transport();
    if(CORBA_is_nil(transport))
	return OCI_TransportInfo::_nil();

    return transport -> get_info();
}

OCI_ConnectorInfo_ptr
CORBA_Object::_get_oci_connector_info()
{
    //
    // Remote objects have a client, local objects don't
    //
    if(CORBA_is_nil(_ob_clt_))
        throw CORBA_BAD_OPERATION();

    OCI_Connector_var connector = _ob_clt_ -> connector();
    if(CORBA_is_nil(connector))
	return OCI_ConnectorInfo::_nil();

    return connector -> get_info();
}

OBClient_ptr
CORBA_Object::_OB_client()
{
    return OBClient::_duplicate(_ob_clt_);
}

void
CORBA_Object::_OB_client(OBClient_ptr clt)
{
    _ob_key_.length(0); // Invalidate key cache

    CORBA_ORB_ptr orb = CORBA_ORB::_OB_instance_nodup();
    if(CORBA_is_nil(orb))
	throw CORBA_INITIALIZE("ORB not initialized");

    if(!CORBA_is_nil(clt))
	orb -> _OB_incClientUsage(clt);

    if(!CORBA_is_nil(_ob_clt_))
	orb -> _OB_decClientUsage(_ob_clt_);

    _ob_clt_ = OBClient::_duplicate(clt);
}

const CORBA_PolicyList&
CORBA_Object::_OB_policies()
{
    return _ob_policies_;
}

//
// This method sets the policies to the list represented by np. It
// also updates internal state variables from the set of policies.
//
void
CORBA_Object::_OB_policies(const CORBA_PolicyList& np)
{
    _ob_policies_ = np;

    OB_TimeoutPolicy_var toutPolicy;
    CORBA_Policy_var policy = _OB_getPolicy(OB_TIMEOUT_POLICY);
    if(!CORBA_is_nil(policy))
    {
        toutPolicy = OB_TimeoutPolicy::_narrow(policy);
        assert(!CORBA_is_nil(toutPolicy));
    }
    
    OB_ReconnectPolicy_var reconnectPolicy;
    policy = _OB_getPolicy(OB_RECONNECT_POLICY);
    if(!CORBA_is_nil(policy))
    {
        reconnectPolicy = OB_ReconnectPolicy::_narrow(policy);
        assert(!CORBA_is_nil(reconnectPolicy));
    }
    
    _ob_tout_ = CORBA_is_nil(toutPolicy)
	? -1
	: (CORBA_Long)toutPolicy -> value();

    _ob_retry_ = CORBA_is_nil(reconnectPolicy)
	? false
	: (reconnectPolicy -> value() ? CORBA_TRUE : CORBA_FALSE);
}

CORBA_Policy_ptr
CORBA_Object::_OB_getPolicy(CORBA_PolicyType policy_type)
{
    CORBA_ULong policiesLen = _ob_policies_.length();
    for(CORBA_ULong i = 0; i < policiesLen; ++i)
	if(_ob_policies_[i] -> policy_type() == policy_type)
	    return CORBA_Policy::_duplicate(_ob_policies_[i]);
    return CORBA_Policy::_nil();
}

const char*
CORBA_Object::_OB_typeId() const
{
    return "IDL:omg.org/CORBA/Object:1.0";
}

void*
CORBA_Object::_OB_narrowHelp(const char* _ob_id) const
{
    if(strcmp("IDL:omg.org/CORBA/Object:1.0", _ob_id) == 0)
        return (void*)this;
    else
        return 0;
}

OBDispatchStatus
CORBA_Object::_OB_dispatch(const char*, OBBuffer&, bool, CORBA_ULong,
			   CORBA_ULong)
{
    //
    // Calling _OB_dispatch on a non-skeleton is an error
    //
    assert(false);
    return OBDispatchStatusNoObject;
}

CORBA_Boolean
CORBA_Object::_OB_remoteIsA(const char* id)
{
    if(_is_local())
    {
	//
	// If this is a local object, return false now
	//
	return false;
    }
    else
    {
	//
	// Check the type_id in the IOR
	//
	if(strcmp(id, _ob_ior_.type_id) == 0)
	{
	    return true;
	}

	//
	// TODO: Some kind of is-a chache should be consulted
	// here
	//

	//
	// Check implementation object
	//
	if(CORBA_is_nil(_ob_clt_))
	    throw CORBA_NO_IMPLEMENT();
	
	CORBA_ULong _ob_off = _ob_clt_ -> offset(this, "_is_a");
	CORBA_ULong _ob_cnt = _ob_off;
	OBMarshalCount(id, _ob_cnt);
	
	OBBuffer _ob_buf(_ob_cnt);
	CORBA_Octet* _ob_o = _ob_buf.data + _ob_off;
	OBMarshal(id, _ob_o);
	
	bool _ob_sw, _ob_ex, _ob_fo;
	_ob_off =  _ob_clt_ -> request(this, "_is_a", _ob_buf,
				       _ob_sw, _ob_fo, _ob_ex,
				       _ob_tout_, _ob_retry_);
	
	const CORBA_Octet* _ob_co = _ob_buf.data + _ob_off;
	
	if(_ob_fo)
	{
	    _OB_forward(_ob_co, _ob_sw);
	    return _OB_remoteIsA(id);
	}
	
	if(_ob_ex)
	    throw CORBA_UNKNOWN();
	
	CORBA_Boolean _ob_r;
	OBUnmarshal(_ob_r, _ob_co, _ob_sw);
	
	return _ob_r;
    }
}

void
CORBA_Object::_OB_copyFrom(CORBA_Object_ptr p)
{
    _OB_ior(p -> _OB_ior());
    OBClient_var clt = p -> _OB_client();
    _OB_client(clt);
    _OB_key(p -> _OB_key());
    _OB_policies(p -> _OB_policies());
}

void
CORBA_Object::_OB_forward(const CORBA_Octet*& coct, bool swap)
{
    IOP_IOR ior;
    OBUnmarshal(ior, coct, swap);
    CORBA_ORB_ptr orb = CORBA_ORB::_OB_instance_nodup();
    if(CORBA_is_nil(orb))
	throw CORBA_INITIALIZE("ORB not initialized");
    orb -> _OB_changeObject(this, ior);
}

// ----------------------------------------------------------------------
// Object CDR functions
// ----------------------------------------------------------------------

void
OBMarshal(CORBA_Object_ptr val, CORBA_Octet*& oct)
{
    if(CORBA_is_nil(val))
    {
	IOP_IOR ior;
	ior.type_id = CORBA_string_dup("");
	OBMarshal(ior, oct);
    }
    else
    {
	if(val -> _is_local() && val -> _ob_ior_.profiles.length() == 0)
	{
	    CORBA_ORB_ptr orb = CORBA_ORB::_OB_instance_nodup();
	    if(CORBA_is_nil(orb))
		throw CORBA_INITIALIZE("ORB not initialized");
	    orb -> connect(val);
	}

	OBMarshal(val -> _ob_ior_, oct);
    }
}

void
OBMarshalCount(CORBA_Object_ptr val, CORBA_ULong& count)
{
    if(CORBA_is_nil(val))
    {
	IOP_IOR ior;
	ior.type_id = CORBA_string_dup("");
	OBMarshalCount(ior, count);
    }
    else
    {
	if(val -> _is_local() && val -> _ob_ior_.profiles.length() == 0)
	{
	    CORBA_ORB_ptr orb = CORBA_ORB::_OB_instance_nodup();
	    if(CORBA_is_nil(orb))
		throw CORBA_INITIALIZE("ORB not initialized");
	    orb -> connect(val);
	}

	OBMarshalCount(val -> _ob_ior_, count);
    }
}

void
OBUnmarshal(CORBA_Object_ptr& val, const CORBA_Octet*& coct, bool swap)
{
    CORBA_Object_var old = val;

    IOP_IOR ior;
    OBUnmarshal(ior, coct, swap);
    CORBA_ORB_ptr orb = CORBA_ORB::_OB_instance_nodup();
    if(CORBA_is_nil(orb))
	throw CORBA_INITIALIZE("ORB not initialized");
    val = orb -> _OB_createObject(ior);
}

void
OBMarshal(const OBObjSeq< CORBA_Object >& seq, CORBA_Octet*& oct)
{
    OBMarshal(seq.length(), oct);
    for(CORBA_ULong i = 0 ; i < seq.length() ; i++)
        OBMarshal(seq[i], oct);
}

void
OBMarshalCount(const OBObjSeq< CORBA_Object >& seq, CORBA_ULong& count)
{
    OBMarshalCount(seq.length(), count);
    for(CORBA_ULong i = 0 ; i < seq.length() ; i++)
        OBMarshalCount(seq[i], count);
}

void
OBUnmarshal(OBObjSeq< CORBA_Object >& seq, const CORBA_Octet*& oct, bool swap)
{
    CORBA_ULong len;
    OBUnmarshal(len, oct, swap);
    seq.length(len);
    for(CORBA_ULong i = 0 ; i < len ; i++)
        OBUnmarshal(seq[i].inout(), oct, swap);
}
