/**
 * @file
 * $Id$
 * $Revision$
 * $Author$
 * $Date$
 *
 * This file is part of The iWear Framework.
 *
 * The iWear Framework is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by the
 * Free Software Foundation as in version 2 of the License.

 * 
 * The iWear Framework 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 General Public License for
 * more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * The iWear Framework; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifndef __IWREMOTE_REMOTECONNECTION_H
#define __IWREMOTE_REMOTECONNECTION_H

#include <iwear/threadlocked.h>
#include <iwear/conditional.h>
#include <iwear/conditional.h>
#include <iwremote/remote_enums.h>
#include <iwremote/protocol.h>
#include <iwremote/hostconnector.h>

#include <queue>
#include <vector>
#include <utility>
#include <map>

#include <boost/intrusive_ptr.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/member.hpp>



using namespace boost;
using namespace boost::multi_index;
using std::pair;
using std::map;

namespace iwear
{
    namespace net
    {

class RemoteObject;
class RPCStream;
class CallProvider;
class ConnectionDescriptor;

typedef pair<chanid_t,RPCStream*> channel_stream;

typedef multi_index_container<
    channel_stream,
    indexed_by<
	sequenced<>,
    ordered_non_unique<member<channel_stream,chanid_t,&channel_stream::first>   >
	>
    > stream_container;
    }
}
//We need to include uid *after* this typedef, otherwise it wont compile
#include <iwear/uid.h>
#include <iwear/debugstream.h>
namespace iwear
{
    namespace net
    {

class IWAPIT remote_error: public iwruntime_error
{
protected:
    uid host;
public:
    explicit remote_error( const string& wht, const uid& h ) : iwruntime_error(wht),host(h) { }
    virtual ~remote_error() throw() { }
    virtual void output_additional( std::ostream& o ) const
    {
	o << ANSI_GREEN << "Remote Host: " << ANSI_BLUE << host << ANSI_NORMAL << std::endl;
    }
};

/**
 * This is a class that can wrap around different connection types to form a
 * simple uniform interface to all possible interfaces.
 */
class RemoteConnection : virtual public ThreadLocked
{
    friend class SocketProvider;
    friend class CallManager;
    friend class CallProvider;
    friend class RootArtano;
private:
protected:
    RemoteConnection( const RemoteConnection& ) : ThreadLocked(),cond(mutex) { }
    RemoteConnection& operator=( const RemoteConnection& ) { return *this; }
    friend void intrusive_ptr_add_ref( RemoteConnection* );
    friend void intrusive_ptr_release( RemoteConnection* );

    Conditional cond;
    /**
     * This is the uid of the host we are connected to
     * XXX does it make sense to replace this by hostconnector or otherwise ?
     */
    hostid_t RemoteHost;

    /**
     * Which channels are active on this connection
     */
    set<chanid_t> active_channels;

    /**
     * Which channels are waiting for incoming data.
     */
//    set<chanid_t> waiting_channels;

    map<chanid_t, RemoteObject*> object_on_channel;

    set<chanid_t> preallocated_channels;

    set<chanid_t> freed_channels;

PH2 stream_container stream_queue;

    map<uint32_t, RPCStream*> deferred_queue;

    set<uint32_t> waiting_deferrers;

    /**
     * When we have established this connection, we are the master side and are
     * responsible for channel allocation.
     */
    bool master_side;

    bool is_sequenced;

    CallProvider* prov;

    int8_t actual_flags;

    uint32_t act_seq;

    uint32_t refcount;

    /**
     * Get the next sequence number to be sent
     */
    uint32_t next_sequence( void );

    /**
     * Say that we have received this sequence nr.
     */
    void got_sequence( uint32_t );

    bool has_sequence( uint8_t flgs ) const { return (flgs & MSG_FLAG_SEQ); }
    
    bool has_ssl( uint8_t flgs ) const { return (flgs & MSG_FLAG_SSL); }

    bool has_gzip( uint8_t flgs ) const { return (flgs & MSG_FLAG_GZIP); }

    bool has_deferred( uint8_t flgs ) const { return (flgs & MSG_FLAG_DEFERRED); }

    bool flags_valid( uint8_t flgs ) const { 
	return flgs == (flgs & (MSG_FLAG_SEQ | MSG_FLAG_SSL | MSG_FLAG_GZIP | MSG_FLAG_DEFERRED)); }


    int8_t get_actual_flags( void ) { return actual_flags; }

    uint32_t& get_refcount( void ) { return refcount; }

public:
    RemoteObject* get_object( chanid_t );

    const set<chanid_t>& get_preallocated_channels( void ) const { return preallocated_channels; }

    virtual RPCStream* get_stream( uint32_t est ) = 0;

    /**
     * Streams should not be deleted, but passed to this function so the
     * corresponding remoteconnection can act on it before releasing the
     * memory, or it can decide to keep it.
     */
PH2 virtual void recycle_stream( RPCStream* ) = 0;

    /** 
     * Sends a simple error msg to a specified channel
     */
    void send_simple_error( uint32_t rsn, chanid_t, uint32_t defid = 0 );

    void handle_error( const protocol_error& );

    bool is_master( void ) const { return master_side; }

    RemoteConnection( CallProvider* cp, bool ms );

    virtual ConnectionDescriptor* get_connectiondescriptor( ) = 0;

    CallProvider* get_callprovider( void ) { return prov; }
    Conditional& get_conditional( void ) { return cond; }

    int wait_for_channel( uint16_t chan );

    int wait_for_deferred( uint32_t def );

    virtual ~RemoteConnection();
    const uid& get_uid( void ) const { return RemoteHost; }
    virtual rpc_type get_type( void ) const = 0;

    /**
     * This function extracts the buffer from the given rpcstream and sends it
     * over the connection. The stream must be suitable for the given
     * connection type, the responsibility for this lies at the caller, we will
     * here send everything over the connection.
     */
    virtual void send_rpcpacket( const RPCStream* spc ) = 0;

    virtual bool is_connected( void ) const = 0;

    virtual bool reconnect( void ) = 0;

    virtual void disconnect( void ) = 0;

    virtual void set_connection_policy( void ) = 0;

    /**
     * Receives a packet and calls the set_buffer of the corresponding
     * RPCStream. The buffer will then belong to the RPCStream so this is
     * responsible for proper memory deletion.
     */
PH2 virtual bool receive_rpcpacket( RPCStream& spc, chanid_t channel ) = 0;

PH2 virtual bool receive_rpcpacket( RPCStream& spc, uint32_t defer ) = 0;

    virtual bool has_ipaddress( void ) const = 0;
    virtual string get_ipaddress( void ) const = 0;

    virtual bool has_sslkey( void ) const = 0;
    virtual bool get_sslkey( void ) const = 0;


    /**
     * The Channel Preallocation will lock everything, change the maps and then
     * unlock. From this on the channel cannot be used by anyone else. We will
     * use the channel to setup a real allocation then
     */
PH1  chanid_t preallocate_channel( void );

    /**
     * If were slave-side we preallocate a channel when we get a first
     * associate request. This is checked against all pre-allocated and
     * associated channels
     */
PH1  void preallocate_channel( chanid_t );

    /**
     * Here we really allocate a previously pre-allocated channel
     */
PH1 void allocate_channel( RemoteObject*, chanid_t );
    
    void send_exception( chanid_t chn, uint32_t funcid, uint32_t fver, uint32_t defid, const string& );

    void queue_stream( chanid_t chn, RPCStream* rpcs );

    virtual HostConnector get_connector( void ) = 0;

};
/**
 * This class is to be used for the boost::shared_ptr as a deleter that will
 * delete the stream
 */
class StreamRecycler
{
    RemoteConnection* rcon;
public:
    StreamRecycler( RemoteConnection* rc ) : rcon(rc) { }
    void operator()( RPCStream* s ) { rcon->recycle_stream(s); }
};
 
typedef boost::intrusive_ptr<RemoteConnection> RemoteConnectionPtr;

inline void intrusive_ptr_release( RemoteConnection* rc )
{
    /*
    d_dbg << ANSI_VIOLET << "intrusive_ptr_release( RemoteConnection* 0x" << hex << (void*)rc << dec << ")" << endl;
    d_dbg << "Old: " << rc->refcount << endl;
    d_dbg << "New: " << rc->refcount-1 << ANSI_NORMAL << endl;
*/
    if( ! rc ) return; // We dont care if the ptr is 0
    RemoteConnection* rcd = 0;
    {
	ThreadLocker tl(rc->mutex);
	--(rc->refcount);
	if( rc->refcount == 0 )
	{
	    rcd = rc;
	}
    }
    delete rcd;
}

inline void intrusive_ptr_add_ref( RemoteConnection* rc )
{
    /*
    d_dbg << ANSI_VIOLET << "intrusive_ptr_add_ref( RemoteConnection* 0x" << hex << (void*)rc << dec << endl;
    d_dbg << "Old: " << rc->refcount << endl;
    d_dbg << "New: " << rc->refcount+1 << ANSI_NORMAL << endl;
    */
    if( ! rc ) return; // We dont care if the ptr is 0
    ThreadLocker tl(rc->mutex);
    ++(rc->refcount);
    // Do we need to do more ?
}

}
}

#endif
