// **********************************************************************
//
// Copyright (c) 2003-2011 ZeroC, Inc. All rights reserved.
//
// This copy of Ice is licensed to you under the terms described in the
// ICE_LICENSE file included in this distribution.
//
// **********************************************************************

#ifndef ICE_OUTGOING_ASYNC_H
#define ICE_OUTGOING_ASYNC_H

#include <IceUtil/Monitor.h>
#include <IceUtil/Mutex.h>
#include <IceUtil/Timer.h>
#include <IceUtil/Exception.h>
#include <Ice/OutgoingAsyncF.h>
#include <Ice/InstanceF.h>
#include <Ice/ReferenceF.h>
#include <Ice/CommunicatorF.h>
#include <Ice/ConnectionIF.h>
#include <Ice/Current.h>
#include <Ice/BasicStream.h>

#include <memory>

namespace IceInternal
{

class CallbackBase;
typedef IceUtil::Handle<CallbackBase> CallbackBasePtr;

}

namespace Ice
{

class ICE_API AsyncResult : virtual public IceUtil::Shared, private IceUtil::noncopyable
{
public:

    virtual bool operator==(const AsyncResult&) const;
    virtual bool operator<(const AsyncResult&) const;

    virtual Int getHash() const;

    virtual Ice::CommunicatorPtr getCommunicator() const
    {
        return 0;
    }

    virtual Ice::ConnectionPtr getConnection() const
    {
        return 0;
    }

    virtual ObjectPrx getProxy() const
    {
        return 0;
    }

    bool isCompleted() const;
    void waitForCompleted();

    bool isSent() const;
    void waitForSent();

    void throwLocalException() const;

    bool sentSynchronously() const
    {
        return _sentSynchronously; // No lock needed, immutable once __send() is called
    }

    LocalObjectPtr getCookie() const
    {
        return _cookie;	// No lock needed, cookie is immutable
    }

    const std::string& getOperation() const
    {
        return _operation;
    }

    //
    // The following methods are used by begin_ and end_ methods to start or complete
    // the asynchronous invocation.
    //
    IceInternal::BasicStream*
    __getOs()
    {
        return &_os;
    }

    IceInternal::BasicStream*
    __getIs()
    {
        return &_is;
    }

    bool __wait();
    void __throwUserException();
    void __exceptionAsync(const Ice::Exception&);

    static void __check(const AsyncResultPtr&, const ::IceProxy::Ice::Object*, const ::std::string&);
    static void __check(const AsyncResultPtr&, const ::Ice::Connection*, const ::std::string&);
    static void __check(const AsyncResultPtr&, const ::Ice::Communicator*, const ::std::string&);

    void __exception(const Ice::Exception&); // Required to be public for AsynchronousException
    void __sent(); // Required to be public for AsynchronousSent

protected:

    static void __check(const AsyncResultPtr&, const ::std::string&);

    AsyncResult(const IceInternal::InstancePtr&, const std::string&, const IceInternal::CallbackBasePtr&, 
                const LocalObjectPtr&);

    void __sentAsync();
    void __response();

    void __warning(const std::exception&) const;
    void __warning() const;

    virtual ~AsyncResult(); // Must be heap-allocated.

    const IceInternal::InstancePtr _instance;
    const std::string& _operation;
    const IceInternal::CallbackBasePtr _callback;
    const LocalObjectPtr _cookie;
    
    IceUtil::Monitor<IceUtil::Mutex> _monitor;
    IceInternal::BasicStream _is;
    IceInternal::BasicStream _os;

    static const unsigned char OK;
    static const unsigned char Done;
    static const unsigned char Sent;
    static const unsigned char EndCalled;

    unsigned char _state;
    bool _sentSynchronously;
    std::auto_ptr<Exception> _exception;
};

}

namespace IceInternal
{

//
// See comments in OutgoingAsync.cpp
//
extern ICE_API CallbackBasePtr __dummyCallback;

class LocalExceptionWrapper;

//
// This interface is used by the connection to handle OutgoingAsync
// and BatchOutgoingAsync messages.
//
class ICE_API OutgoingAsyncMessageCallback : virtual public IceUtil::Shared
{
public:

    virtual ~OutgoingAsyncMessageCallback()
    {
    }

    //
    // Called by the connection when the message is confirmed sent. The connection is locked
    // when this is called so this method can call the sent callback. Instead, this method
    // returns true if there's a sent callback and false otherwise. If true is returned, the
    // connection will call the __sent() method bellow (which in turn should call the sent
    // callback).
    //
    virtual bool __sent(Ice::ConnectionI*) = 0;

    //
    // Called by the connection to call the user sent callback.
    //
    virtual void __sent() = 0;

    //
    // Called by the connection when the request failed. The boolean indicates whether or
    // not the message was possibly sent (this is useful for retry to figure out whether
    // or not the request can't be retried without breaking at-most-once semantics.)
    //
    virtual void __finished(const Ice::LocalException&, bool) = 0;
};

class ICE_API OutgoingAsync : public OutgoingAsyncMessageCallback, public Ice::AsyncResult, private IceUtil::TimerTask
{
public:

    OutgoingAsync(const Ice::ObjectPrx&, const std::string&, const CallbackBasePtr&, const Ice::LocalObjectPtr&);

    void __prepare(const std::string&, Ice::OperationMode, const Ice::Context*);

    virtual Ice::ObjectPrx
    getProxy() const
    {
        return _proxy;
    }

    virtual bool __sent(Ice::ConnectionI*);
    virtual void __sent();
    virtual void __finished(const Ice::LocalException&, bool);

    void __finished(const LocalExceptionWrapper&);
    void __finished(BasicStream&);

    bool __send(bool);

protected:

    Ice::ObjectPrx _proxy;

private:

    int handleException(const Ice::LocalException&, bool);
    int handleException(const LocalExceptionWrapper&);

    void runTimerTask(); // Implementation of TimerTask::runTimerTask()
    Ice::ConnectionIPtr _timerTaskConnection;

    Handle< IceDelegate::Ice::Object> _delegate;
    int _cnt;
    Ice::OperationMode _mode;
};

class ICE_API BatchOutgoingAsync : public OutgoingAsyncMessageCallback, public Ice::AsyncResult
{
public:

    BatchOutgoingAsync(const InstancePtr&, const std::string&, const CallbackBasePtr&, const Ice::LocalObjectPtr&);

    virtual bool __sent(Ice::ConnectionI*);
    virtual void __sent();
    virtual void __finished(const Ice::LocalException&, bool);
};

class ICE_API ProxyBatchOutgoingAsync : public BatchOutgoingAsync
{
public:

    ProxyBatchOutgoingAsync(const Ice::ObjectPrx&, const std::string&, const CallbackBasePtr&,
                            const Ice::LocalObjectPtr&);

    void __send();

    virtual Ice::ObjectPrx
    getProxy() const
    {
        return _proxy;
    }

private:
    
    Ice::ObjectPrx _proxy;
};

class ICE_API ConnectionBatchOutgoingAsync : public BatchOutgoingAsync
{
public:

    ConnectionBatchOutgoingAsync(const Ice::ConnectionIPtr&, const InstancePtr&, const std::string&,
                                 const CallbackBasePtr&, const Ice::LocalObjectPtr&);

    void __send();

    virtual Ice::ConnectionPtr getConnection() const;

private:

    const Ice::ConnectionIPtr _connection;
};

class ICE_API CommunicatorBatchOutgoingAsync : public BatchOutgoingAsync
{
public:

    CommunicatorBatchOutgoingAsync(const Ice::CommunicatorPtr&, const InstancePtr&, const std::string&,
                                   const CallbackBasePtr&, const Ice::LocalObjectPtr&);

    void flushConnection(const Ice::ConnectionPtr&);
    void ready();

    void completed(const Ice::AsyncResultPtr&);
    void sent(const Ice::AsyncResultPtr&);

    virtual Ice::CommunicatorPtr
    getCommunicator() const
    {
        return _communicator;
    }

private:

    void check(const Ice::AsyncResultPtr&, const Ice::LocalException*, bool);

    const Ice::CommunicatorPtr _communicator;
    int _useCount;
};

//
// Base class for all callbacks.
//
class ICE_API CallbackBase : public IceUtil::Shared
{
public:

    void checkCallback(bool obj, bool cb)
    {
        if(!obj)
        {
            throw IceUtil::IllegalArgumentException(__FILE__, __LINE__, "callback object cannot be null");
        }
        if(!cb)
        {
            throw IceUtil::IllegalArgumentException(__FILE__, __LINE__, "callback cannot be null");
        }
    }

    virtual void __completed(const ::Ice::AsyncResultPtr&) const = 0;
    virtual CallbackBasePtr __verify(::Ice::LocalObjectPtr&) = 0;
    virtual void __sent(const ::Ice::AsyncResultPtr&) const = 0;
    virtual bool __hasSentCallback() const = 0;
};

//
// Base class for generic callbacks.
//
class ICE_API GenericCallbackBase : virtual public CallbackBase
{
};

//
// Generic callback template that requires the caller to down-cast the
// proxy and the cookie that are obtained from the AsyncResult.
//
template<class T>
class AsyncCallback : public GenericCallbackBase
{
public:

    typedef T callback_type;
    typedef IceUtil::Handle<T> TPtr;

    typedef void (T::*Callback)(const ::Ice::AsyncResultPtr&);

    AsyncCallback(const TPtr& instance, Callback cb, Callback sentcb = 0) :
        callback(instance), completed(cb), sent(sentcb)
    {
        checkCallback(instance, cb != 0);
    }

    virtual void __completed(const ::Ice::AsyncResultPtr& result) const
    {
        (callback.get()->*completed)(result);
    }

    virtual CallbackBasePtr __verify(::Ice::LocalObjectPtr&)
    {
        return this; // Nothing to do, the cookie is not type-safe.
    }

    virtual void __sent(const ::Ice::AsyncResultPtr& result) const
    {
	if(sent)
	{
	    (callback.get()->*sent)(result);
	}
    }

    virtual bool __hasSentCallback() const
    {
        return sent != 0;
    }

    TPtr callback;
    Callback completed;
    Callback sent;
};

}

namespace Ice
{

typedef IceUtil::Handle< ::IceInternal::GenericCallbackBase> CallbackPtr;

template<class T> CallbackPtr
newCallback(const IceUtil::Handle<T>& instance,
            void (T::*cb)(const ::Ice::AsyncResultPtr&),
            void (T::*sentcb)(const ::Ice::AsyncResultPtr&) = 0)
{
    return new ::IceInternal::AsyncCallback<T>(instance, cb, sentcb);
}

template<class T> CallbackPtr
newCallback(T* instance,
            void (T::*cb)(const ::Ice::AsyncResultPtr&),
            void (T::*sentcb)(const ::Ice::AsyncResultPtr&) = 0)
{
    return new ::IceInternal::AsyncCallback<T>(instance, cb, sentcb);
}

//
// Operation callbacks are specified in Proxy.h
//

//
// Interfaces for the deprecated AMI mapping.
//

class ICE_API AMISentCallback 
{
public:

    virtual ~AMISentCallback() { }

    virtual void ice_sent() = 0;
};

class ICE_API AMICallbackBase : virtual public IceUtil::Shared
{
public:

    virtual void ice_exception(const Exception&) = 0;

    void __exception(const Exception&);
    void __sent(bool);
};

}

#endif
