/**
 * @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_CALLMANAGER_H
#define __IWREMOTE_CALLMANAGER_H

#include <iwremote/remoteconnection.h>
// we need to includ remoteconnection first otherwise boost::multi_index stuff
// does not work
#include <iwear/utility.h>
#include <iwear/uid.h>
#include <iwear/service.h>
#include <iwear/thread.h>
#include <iwear/shmemory.h>
#include <iwear/triple.h>
#include <iwremote/connectiondescriptor.h>
#include <iwremote/hostconnector.h>
#include <iwremote/remote_enums.h>
#include <boost/intrusive_ptr.hpp>
#include <iwremote/securityprovider.h>
#include <iwremote/protocol.h>
#include <iwear/eventdispatcher.h>
#include <iwear/eventbase.h>
#include <iwear/conditional.h>
#include <iwear/servicemanager.h>

#include <vector>
#include <map>
#include <list>

using std::multimap;
using std::list;
using std::map;
using std::vector;
using std::make_pair;
using std::hex;
using std::dec;
/**
 * Size of the SHM Segment in bytes. This should be big enough to hold most
 * calls but small enough to not be a memory hog. We currently set it to 16kB.
 */
#define SHM_SIZE 16384
namespace iwear
{
class ServiceManager;
class SysVSemaphore;
class Configuration;
    namespace net
    {

class BroadCastHandle;
class CallHandler;
class CallProvider;
class RPCStream;
class RemoteDescriptor;
class RemoteObject;	
class RootArtano;
class Scheduler;
class SearchProvider;

/**
 * The CallManager is responsible for finding the correct object to make a
 * remote call on and to do some basich blacklisitng and whitelisting checks.
 * It is not responsible for host checking and accepting connections, thats the
 * NetworkManager. The NetworkManager will also do a first protocol check if
 * its really a call packet, or a an internal search packet.
 */
class CallManager : public Service, public Thread
{
private:
friend class RootArtano;
    hostid_t sysuid;
protected:

    set<SearchProvider*> searchproviders;

    Conditional cond;
    EventDispatcher* evdis;
    RootArtano* RA;

    int default_recursion_depth;
    /**
     * Map the host ids to informations how to connect to them.
     * Its a multimap because some hosts might be reachable through different
     * mehanisms or addresses.
     */
    multimap<hostid_t, HostConnector > host_list;


    /**
     * This is a cache to see what uid/cid/name pairs a connection has
     */
    multimap<RemoteConnectionPtr, triple<oid_t,uint32_t,string> > connection_cache;

    /**
     * This is a cache to look which uid is on which (active?) RemoteConnection
     *
     * the stuff should be sent over to us in an associate and maybe updated on
     * search requests etc. TODO
     * What shall we do if the connection breaks/disconnects ?
     */
    map<const oid_t*, RemoteConnectionPtr, deref_less<const oid_t*> > oid_cache;
 
    /**
     * Here we map the information on how to find a given oid_t, means on which
     * host. At another place we need to save how to get to this host.
     */
    typedef set<pair<oid_t,RemoteDescriptor> > oid_to_host_cache;

    oid_to_host_cache oid_to_host;

    /**
     * Check all known CallProviders for incoming connection attempts, or
     * re-queued system messages.
     * @sideffect This will alter several internal structures
     * @throw Never
     */
    void check_callproviders( void );

    /**
     * This processes the data which is in the incoming stream
     * @sideffect The usage count of the passed RemoteConnection is lowered
     */
PH1  void process_incoming_stream( RPCStream* rpcs, RemoteConnectionPtr rc );

    set<pair<RemoteConnectionPtr,chanid_t> > unfinished_associations;

    map<pair<RemoteConnectionPtr,chanid_t>, RPCStream* > waiting_associations;

    /**
     * A set of CallProviders which we know about and we ask for.
     */
    set<CallProvider*> cps;

    void object_associate( RemoteConnectionPtr rc, uint16_t chan, const hostid_t& hid, const oid_t& oid, cid_t cid );

    /**
     */
    void check_security_provider( RemoteConnectionPtr, const hostid_t& hid, const oid_t& oid );

    void send_associate_return( const hostid_t&, RemoteConnectionPtr, RemoteObject*, chanid_t );

    set< SecurityProvider* > security_providers;

    void handle_error( RemoteConnectionPtr rc, uint32_t reason, uint16_t chan, RPCStream* rpcs );

    void finish_associate( RemoteConnectionPtr rc, RPCStream* rpcs, uint16_t chan );

    void reject_association( RemoteConnectionPtr rc, RPCStream* rpcs, uint16_t chan );

    void redirect_association( RemoteConnectionPtr rc, RPCStream* rpcs, uint16_t chan );

    void deassociate_request( RemoteConnectionPtr rc, RemoteObject* ro );

    void load_callproviders( const list<string>& );

    void load_searchproviders( const list<string>& );

    void unassociated_call( RemoteConnectionPtr rc, RPCStream* rpcs );

    void unassociated_callreturn( RemoteConnectionPtr rc, RPCStream* rpcs );

    set<HostConnector> known_hosts;

    void save_known_hosts();

    void load_known_hosts();
public:
    Configuration& get_config( void ) { return SerMan->get_config(); }

    HostConnector connector;
    /**
     * This replaces some info about a known host.
     */
    void replace_hostinfo( const HostConnector& hc_old, const HostConnector& hcnew );

    set<HostConnector> get_known_hosts( void ) { return known_hosts; }
    /**
     * This hostid is the one used for when we do not know which uid the host
     * has, or when we do not care. Its for like when we want to tell the
     * system an IPv6 Address of the host to tell it to use it, but we do not
     * actually care which uid it has.
     */
    static hostid_t unknown_host_id;

    /**
     * For cases where an oid has to be specified, but we do not care about the
     * actual object, just the service, we send this one along.
     */
    static oid_t service_only_oid;

PH2 RootArtano* return_ra( void ) { return RA; }

PH2 const RootArtano* return_ra( void ) const { return RA; }

    /**
     * Check if the packet contains any error, and if yes, throw the
     * apporpriate exception, if not, return the type of the packet.
     * This holds for simple protocol errors as well as several other errors
     * like exceptions, call rejects etc.
     */
    uint8_t check_packet_error( RPCStream* rpcs, chanid_t );

    /**
     * This adds the size info to the packet, and anything that might be left.
     */
    void finish_packet( RPCStream* rpcs );

PH2  void prepare_packet( RemoteConnectionPtr rcon, RPCStream* rpcs, 
	chanid_t ch, uint8_t tp, uint32_t defer );

    /**
     * This function is to be called from a client remote object when it wants
     * to do a call. It has to check whether that object is allowed to make a
     * call (e.g. if it really is a client side object) and it has to make sure
     * the cd.rmt.rmcn is properly setup (connected and such).
     */
    void prepare_for_call( RemoteObject* );

    void _debug_print_state( void );

    void _debug_add_known_host( const string& );

    void tell( const hostid_t&, const HostConnector& );

PH1 void add_callprovider( CallProvider* cp );

PH2 void add_searchprovider( SearchProvider* cp );
    /**
     * @return a channel id
     * @warning The RemoteConnection passed might be changed to a new
     * connection or 0, since we might need to establish a new connection.
     */
    chanid_t send_associate_request( RemoteConnectionPtr& rc, const oid_t& id, uint32_t crcid );
     
    /**
     * Here a local _Client RemoteObject calls us with an rpcstream. We now
     * finish the packet and send it to the _Server's RemoteHost and if the
     * call is not marked as oneway, we also wait for returning data.
     * In oneway case or in case of a void function, the returned result is 0,
     * in case of failures, various exceptions may be thrown.
     */
CallHandler* start_resolve_call( RPCStream* rpcs, RemoteObject* ro,
         RemoteConnectionPtr rcon, uint32_t defer, uint32_t );
RPCStream* finish_resolve_call( RemoteConnectionPtr rcon, chanid_t chan,
        bool oneway, uint32_t defer );


     /**
      * Uses the configured scheduler to schedule the call.
      * @param rpcs The actual stream data. Keep in mind that this includes all
      * flags and the defer and sequence ids.
      */
PH2 void schedule_call( RemoteObject* ro, const hostid_t&, RPCStream* rpcs, 
	RemoteConnectionPtr rcon, chanid_t chn, uint32_t func, uint32_t fver );

    /**
     * Return the answer to our association request...
     * @note The current pointer on the RPCStream is on the new channel id for
     * this connection.
     */
    RPCStream& get_association_return( RemoteConnectionPtr, const oid_t&, uint16_t chanid );

    /**
     * Get the connection that is actually responsible for the given object
     * @param object id
     */
    RemoteConnectionPtr get_responsible_connection( const oid_t& ) const;

    /**
     * This searches for the host id and connects to it using a previously
     * somehow gathered connection mechanism
     * @param the host id
     */
PH2  RemoteConnectionPtr get_connection_to_host( const hostid_t& );

PH2  RemoteConnectionPtr get_connection_to_host( HostConnector& );
    
    /**
     * Do a search for information about which host has this object uid
     * available (and internally save also the information about how to reach
     * the host, which is returned in the corresponding HostConnector)
     */
    list<RemoteDescriptor> search_providing_host( const oid_t& o_id, cid_t,  search_behaviour );

    list<RemoteDescriptor> search_providing_host( const oid_t& o_id, search_behaviour );

    virtual void Init( void );
    virtual void Start( void );
    virtual void Stop( void );
    virtual void Reset( void );
    virtual void Suspend( void );
    virtual void Pause( void );
    virtual void Resume( void );

    virtual const string get_name( void );

    virtual service_type get_type( void ) { return service_system; }

    virtual void Run( void );

    /**
     * This function will broadcast a search or other broadcastable message to
     * the configured hosts, and to the LAN too if this is configured.
     * It returns a broadcast handle which can (threadsafe) be asked if the
     * results have alrady arrived, and if they havent, give an estimate on how
     * much is done (wont work on multihop broadcast searches and similar).
     * This is because a broadcast might take quite a while. All the
     * broadcastable messages act highly asynchronous, which means that we do
     * something else and then stuff comes in and then we look what we have.
     */
    BroadCastHandle* broadcast( RPCStream& rpcs );

    CallManager( ServiceManager*, RootArtano*, EventDispatcher* );
    virtual ~CallManager();
};

}
}
#endif

