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

#include <OB/CORBA.h>
#include <OB/Util.h>
#include <OB/Properties.h>

#include <OB/CosTypedEventChannelAdmin_skel.h>

#include <TEC_TypedEventChannel_impl.h>
#include <ProxyConsumer_impl.h>
#include <ProxyState.h>
#include <TEC_TypedProxyPushConsumer_impl.h>
#include <TEC_TypedSupplierAdmin_impl.h>
#include <EventUtil.h>
#include <Event_impl.h>

//
// This class receives calls for a interface, and forwards the
// event-request to the event channel.
//
class TypedProxyPushConsumerDSI_impl
    : public virtual CORBA_DynamicImplementation
{
    CORBA_ORB_var orb_; // The ORB.
    
    TEC_TypedEventChannel_impl* channel_; // My event channel.

    //
    // This ensures that we always have a valid reference to the event
    // channel.
    //
    CosTypedEventChannelAdmin_TypedEventChannel_var theChannel_;
    
    //
    // The interface description for the typeId_.
    //
    CORBA_InterfaceDef::FullInterfaceDescription_var iface_;
    
    bool destroyed_; // Is this destroyed?

    CORBA_ULong id_; // The ID of the associated proxy.

public:
    
    TypedProxyPushConsumerDSI_impl(
        TEC_TypedEventChannel_impl*,
        CORBA_ORB_ptr,
        CORBA_InterfaceDef::FullInterfaceDescription*,
        CORBA_ULong);
    ~TypedProxyPushConsumerDSI_impl();

    //
    // Standard IDL to C++ Mapping.
    //
    virtual CORBA_Boolean _is_a(const char*);
    
    virtual void invoke(CORBA_ServerRequest_ptr) throw();

    //
    // Internal methods.
    //
    
    void disconnect();
};

// ----------------------------------------------------------------------
// TypedProxyPushConsumerDSI_impl constructor and destructor
// ----------------------------------------------------------------------

TypedProxyPushConsumerDSI_impl::TypedProxyPushConsumerDSI_impl(
    TEC_TypedEventChannel_impl* channel,
    CORBA_ORB_ptr orb,
    CORBA_InterfaceDef::FullInterfaceDescription* iface,
    CORBA_ULong id)
    
    : orb_(CORBA_ORB::_duplicate(orb)),
      channel_(channel),
      theChannel_(CosTypedEventChannelAdmin_TypedEventChannel::
                  _duplicate(channel)),
      iface_(iface),
      destroyed_(false),
      id_(id)
{
}

TypedProxyPushConsumerDSI_impl::~TypedProxyPushConsumerDSI_impl()
{
#ifdef OB_TRACE
    CORBA_String_var msg =
        CORBA_string_dup("Destroy TypedProxyPushConsumerDSI(");
    msg += id_;
    msg += ')';
    OBMessageViewer::instance() -> trace(2, msg);
#endif
}

// ----------------------------------------------------------------------
// TypedProxyPushConsumerDSI_impl public member implementation
// ----------------------------------------------------------------------

CORBA_Boolean
TypedProxyPushConsumerDSI_impl::_is_a(const char* name)
{
    return channel_ -> isCompatible(name);
}

void
TypedProxyPushConsumerDSI_impl::invoke(CORBA_ServerRequest_ptr req)
    throw()
{
    //
    // If the proxy is dead, then don't accept any more method
    // invocations.
    //
    if(destroyed_)
    {
        CORBA_Any* any = new CORBA_Any();
        *any <<= new CORBA_OBJECT_NOT_EXIST();
        req -> exception(any);
        return;
    }
    
    //
    // Rip out the operation name.
    //
    CORBA_String_var opName = req -> op_name();

    //
    // Look up the operation in the IR.
    //
    for(CORBA_ULong i = 0; i < iface_ -> operations.length(); i++)
    {
        if(strcmp(opName, iface_ -> operations[i].name) == 0)
        {
            //
            // Have the appropriate operation.
            //
            const CORBA_OperationDescription& opDesc =
                iface_ -> operations[i];

            //
            // Validate the parameters. All in parameters, and no
            // return value, no exceptions.
            //
            if(opDesc.result -> kind() != CORBA_tk_void)
            {
                CORBA_Any* any = new CORBA_Any();
                *any <<= new CORBA_BAD_OPERATION();
                req -> exception(any);
                return;
            }

            CORBA_ULong j;
            for(j = 0 ; j < opDesc.parameters.length() ; j++)
                if(opDesc.parameters[j].mode != CORBA_PARAM_IN)
                {
                    CORBA_Any* any = new CORBA_Any();
                    *any <<= new CORBA_BAD_OPERATION();
                    req -> exception(any);
                    return;
                }
    
            if(opDesc.exceptions.length() > 0)
            {
                CORBA_Any* any = new CORBA_Any();
                *any <<= new CORBA_BAD_OPERATION();
                req -> exception(any);
                return;
            }

            //
            // Create an NVList to contain the parameters for this
            // method. Firstly create the NVList.
            //
            CORBA_NVList_var params;
            orb_ -> create_list(0, params.out());

            //
            // Now process each parameter.
            //
            for(j = 0 ; j < opDesc.parameters.length() ; j++)
            {
                const CORBA_ParameterDescription& par = opDesc.parameters[j];

                //
                // Get the name.
                //
                char* name = CORBA_string_dup(par.name);

                //
                // Create an any with the appropriate typecode.
                //
                CORBA_Any* any = new CORBA_Any;
                any -> replace(par.type, 0);

                //
                // We've already verified that all parameters are IN.
                //
                assert(par.mode == CORBA_PARAM_IN);

                //
                // Add this parameter.
                //
                CORBA_Flags flags = CORBA_ARG_IN;
                params -> add_value_consume(name, any, flags);
            }

#ifdef OB_TRACE
            if(OBMessageViewer::instance() -> getTraceLevel() >= 2)
            {
                OBMessageViewer* viewer = OBMessageViewer::instance();
                CORBA_String_var msg = CORBA_string_dup("invoke: ");
                msg += opName;
                viewer -> trace(2, msg);
                msg[0] = '\0';
                msg += "params #: ";
                msg += params -> count();
                viewer -> trace(2, msg);
                for(CORBA_ULong i = 0; i < params -> count(); ++i)
                {
                    CORBA_NamedValue_ptr nv = params -> item(i);
                    msg[0] = '\0';
                    msg += "name: ";
                    msg += nv -> name();
                    viewer -> trace(2, msg);
                }
            }
#endif
            CORBA_NVList_ptr p = params._retn();
            req -> params(p);

            //
            // Create a new Event_impl, and forward this to the event
            // channel.
            //
            Event_impl* event = new Event_impl(p, opName, id_);
            channel_ -> receive(event);
            return;
        }
    }

    //
    // No such operation.
    //
    CORBA_Any* any = new CORBA_Any();
    *any <<= new CORBA_BAD_OPERATION();
    req -> exception(any);
}

void
TypedProxyPushConsumerDSI_impl::disconnect()
{
    destroyed_ = true;
    
    orb_ -> disconnect(this);
}

// ----------------------------------------------------------------------
// TEC_TypedProxyPushConsumer_impl constructor/destructor
// ----------------------------------------------------------------------

TEC_TypedProxyPushConsumer_impl::TEC_TypedProxyPushConsumer_impl(
    CORBA_ORB_ptr orb,
    TEC_TypedEventChannel_impl* channel,
    TEC_TypedSupplierAdmin_impl* admin,
    CORBA_InterfaceDef::FullInterfaceDescription* iface,
    CORBA_ULong id)
    : orb_(CORBA_ORB::_duplicate(orb)),
      channel_(channel),
      admin_(admin),
      state_(Disconnected),
      dsiImpl_(new TypedProxyPushConsumerDSI_impl(channel_, orb_, iface, id)),
      id_(id)
{
#ifdef OB_TRACE
    CORBA_String_var desc = EventUtil::getConnectionAddrDescription(orb_);
    CORBA_String_var msg = CORBA_string_dup("Create TypedProxyPushConsumer(");
    msg += id_;
    msg += ") from ";
    msg += desc;
    OBMessageViewer::instance() -> trace(2, msg);
#endif
}

TEC_TypedProxyPushConsumer_impl::~TEC_TypedProxyPushConsumer_impl()
{
#ifdef OB_TRACE
    CORBA_String_var msg =
        CORBA_string_dup("Destroy TypedProxyPushConsumer(");
    msg += id_;
    msg += ')';
    OBMessageViewer::instance() -> trace(2, msg);
#endif

    CORBA_release(dsiImpl_);
}

// ----------------------------------------------------------------------
// TEC_TypedProxyPushConsumer_impl public member implementation
// ----------------------------------------------------------------------

CORBA_Object_ptr
TEC_TypedProxyPushConsumer_impl::get_typed_consumer()
{
    JTCSynchronized sync(*this);

    if(state_ == Destroyed)
        throw CORBA_OBJECT_NOT_EXIST();
    
    return CORBA_Object::_duplicate(dsiImpl_);
}

void
TEC_TypedProxyPushConsumer_impl::connect_push_supplier(
    CosEventComm_PushSupplier_ptr supplier)
{
    JTCSynchronized sync(*this);

    if(state_ == Destroyed)
	throw CORBA_OBJECT_NOT_EXIST();

    if(state_ == Connected)
	throw CosEventChannelAdmin_AlreadyConnected();

    //
    // We don't necessarily have a supplier.
    //
    if(!CORBA_is_nil(supplier))
        supplier_ = CosEventComm_PushSupplier::_duplicate(supplier);

    state_ = Connected;
}

void
TEC_TypedProxyPushConsumer_impl::disconnect_push_consumer()
{
    //
    // We first must check the state. This is to account for the case
    // that disconnect_pull_consumer is called concurrently.
    //
    ProxyState saveState;
    {
	JTCSynchronized sync(*this);
	if(state_ == Destroyed)
	    return;
	saveState = state_;
	state_ = Destroyed;
    }

    admin_ -> removeProxy(this);

    //
    // Since we don't necessarily have a supplier, we need to verify
    // that the object reference is not nil.
    //
    if(saveState == Connected && !CORBA_is_nil(supplier_))
    {
        try
        {
            supplier_ -> disconnect_push_supplier();
        }
        catch(CORBA_SystemException&)
        {
        }
    }

    //
    // Disconnect the DSI implementation.
    //
    dsiImpl_ -> disconnect();
    
    //
    // Disconnect the servant from the ORB.
    //
    orb_ -> disconnect(this);
}

void
TEC_TypedProxyPushConsumer_impl::push(const CORBA_Any&)
{
    throw CORBA_NO_IMPLEMENT();
}

void
TEC_TypedProxyPushConsumer_impl::disconnect()
{
    //
    // Mark the state as destroyed.
    //
    JTCSynchronized sync(*this);

    if(state_ == Destroyed)
        return;

    state_ = Destroyed;
    
    //
    // Ask the DSI implementation to go away.
    //
    dsiImpl_ -> disconnect();

    //
    // Disconnect the servant from the ORB.
    //
    orb_ -> disconnect(this);
}

void
TEC_TypedProxyPushConsumer_impl::release()
{
    CORBA_release(this);
}
