/* Network-Integrated Multimedia Middleware (NMM)
 * http://www.motama.com/nmm.html
 *  
 * Copyright (C) 2006-2007
 * Motama GmbH, Saarbruecken, Germany
 * http://www.motama.com
 *  
 * This file is part of NMM. 
 * 
 * NMM 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; only version 2 of the License.      
 * 
 * NMM 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 NMM; if not, write to the Free Software Foundation,    
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */
#ifndef NMM_COMPOSITENODE_HPP
#define NMM_COMPOSITENODE_HPP NMM_COMPOSITENODE_HPP

#include "nmm/nmm_prefix.hpp"

#include <set>
#include "nmm/multimedia/Node.hpp"
#include "ICompositeNodeImpl.hpp"

#include "visibility.hpp"

namespace NMM {
  class ClientRegistry;
  class GraphDescription;

  class DLLEXPORT CompositeNode
    : public Node,
      public ICompositeNodeImpl {

  public:
    /**
     * The default constructor.
     * \param reg The ClientRegistry, where the Nodes are from
     * \param name The name the CompositeNode
     */
    CompositeNode(const char* name = "CompositeNode", ClientRegistry* reg = NULL);

    /**
     * The default destructor.
     */
    virtual ~CompositeNode();

    /** @name Event Listener
     */
    //@{
    /** This method registers an event listener. All registered IEventSender receives the event. 
     */
    virtual void registerEventListener(IEventReceiver*);
    
    /** Adds an TED-Object as EventListener. Incoming events with corresponding
     * event-keys are forwarded to the TED-Object
     */
    virtual void registerEventListener(const string&, EDObject* );
    
    /** This method registers an event listener for a specific key. 
     */
    virtual void registerEventListener(const string&, IEventReceiver*);
    
    /** This method removes an event listener.
     */
    virtual void removeEventListener(IEventReceiver*);
    
    /** This method removes an event listener.
     */
    virtual void removeEventListener(const string&, IEventReceiver*);
    
    /** removes a certain TEDObject
     */
    virtual void removeEventListener(EDObject*);
    //@}


   /** @name Event Processing
     */
    //@{
    /** The method is used to send events to this NMMObject. 
     */
    virtual Result receiveEvent(Event&  event, const IEventSender* = 0);
    
    /** Returns an prototype event with an given event key. The caller of this method
     * is responsible to delete this event.
     */
    virtual Event* getEventPrototype(const string& event_key)  const;
    
    /** returns the list with all supported event keys
     */
    virtual list<string> getEventList() const;
    //@}

   /** @name GraphDescription sepcific methods
     */
    //@{
      void setGraphDescription(const GraphDescription& gd);
      void realizeGraph();
    //@}
  protected:
    /************************* ICompositeNode Interface ******************/
    virtual nmm_uint32 addNode(const INode&);

    virtual bool addNodeWithID(const INode&, const unsigned int&);

    virtual nmm_uint32 addNodeWithInterfaces(const INode &, const list<Interface*> &);

    virtual bool addNodeWithIDWithInterfaces(const INode &, const unsigned int&, const list<Interface*> &);

    virtual Result renameInputJackTag(const string & old_cn_jack_tag, const string & new_cn_jack_tag);

    virtual Result renameOutputJackTag(const string & old_cn_jack_tag, const string & new_cn_jack_tag);
    /************************* ICompositeNode Interface ******************/

    /************************* INode Interface ******************/
    /**
     * This method returns the successor node of this node.
     * \param out_jack the name of the output.
     * \return a node or NULL.
     */
    //virtual INode* getSuccessorNode(const string & out_jack) const;
    // ERRRORTODO: getSuccessorNode

    /**
     * This method returns the predecessor node of this node.
     * \param in_jack the name of the input.
     * \return a node or NULL.
     */
    //virtual INode* getPredecessorNode(const string & in_jack) const;
    // ERRRORTODO: getPredecessorNode

    /**
     * This method returns the specified input jack with the 
     * given specification in the parameter Format and connected
     * to a certain jack.
     * \param format the format with the specifications.
     * \param jack the name of the jack.
     * \return An InputJack or NULL.
     */
    virtual IInputJack* getInputJack(const Format & format, const string & jack);

    /**
     * This method returns the specified output jack with the
     * given specification in the parameter Format and connected
     * to a certain jack.
     * \param pFormat the format with the specifications.
     * \param jack the name of the jack.
     * \return An OutputJack or NULL.
     */
    virtual IOutputJack* getOutputJack(const Format & format, const string &jack);
    virtual IOutputJack* getUnintializedOutputJack(const Format& , const string& ) { return NULL; }

    virtual IInputJack* getInputStream(const string &);

    virtual IOutputJack* getOutputStream(const string &);

    /**
     * Obtain the format associated with the named input stream.
     * The Format pointer is
     * guaranteed to remain valid over the connectedness of the node.
     *
     * \param jack_tag The tag of the input stream.
     * \returns a pointer to the corresponding format.
     */
    virtual const Format* getInputFormat(const char * = "default") const;

    /**
     * Obtain the format associated with the named output stream.
     * The Format pointer is
     * guaranteed to remain valid over the connectedness of the node.
     *
     * \param jack_tag The tag of the output stream.
     * \returns a pointer to the corresponding format.
     */
    virtual const Format* getOutputFormat(const char * = "default") const;


    /**
     * This method is used to obtain the names of all the input jack tags
     * of this node. The caller should not attempt to deallocate the
     * list or the strings.
     *
     * \returns a list of strings (char pointers).
     */
    virtual list<string> getInputStreamTags() const;

    /**
     * This method is used to obtain the names of all the output jack tags
     * of this node. The caller should not attempt to deallocate the
     * list or the strings.
     *
     * \returns a list of strings (char pointers).
     */
    virtual list<string> getOutputStreamTags() const;

    /**
     * This method returns a cloned output jack and fills the given format with
     * the intersection format
     * \param jack_tag the name of the jack
     * \param intersec_format the format to be filled with the intersection format
     * \return the cloned output jack
     */
    virtual IOutputJack* cloneOutputJack(Format*& intersec_format, const string& jack_tag = "default");

    // dummy implementations for INode interface methods not supported
    virtual Result sendData() { return FAILURE; }
    virtual Result sendDataFailed() { return FAILURE; }

    virtual Result sendEventUpstr(CEvent *, const char * = "default") { return FAILURE; }
    virtual Result sendEventUpstr(CEvent &, const char * = "default") { return FAILURE; }
    virtual Result sendEventUpstr(Event &, const char * = "default") { return FAILURE; }

    virtual Result sendEventDownstr(CEvent *, const char * = "default") { return FAILURE; }
    virtual Result sendEventDownstr(CEvent &, const char * = "default") { return FAILURE; }
    virtual Result sendEventDownstr(Event &, const char * = "default") { return FAILURE; }


    /**
     * This method will try to reach the speficied state by calling the
     * required state transition methods
     */
    virtual void reachState(const INode::State& state) 
      throw(const StateTransitionFailedException&, const UnsuccessfulRequiredCallException&);

    // inherited from interface INode
    virtual void setPrefetchQueueSize(const nmm_uint32 &);
    virtual void setPrefetchQueueDuration(const Interval&);
    virtual nmm_uint32 getPrefetchQueueSize();
    virtual void waitPrefetchFinished();

    /*************************   Node interface; do-methods   ******************/
    /**
     * This method initializes the CompositeNode.
     */
    virtual Result doInit()
      throw (const StateTransitionFailedException&, 
	     const IllegalStateException&,
             const UnsuccessfulRequiredCallException&);

    /**
     * This method initializes the Output of the CompositeNode.
     */
    virtual Result doInitOutput()
      throw (const StateTransitionFailedException&, 
	     const IllegalStateException&,
             const UnsuccessfulRequiredCallException&);

    /**
     * This method activates the CompositeNode.
     */
    virtual Result doActivate()
      throw (const StateTransitionFailedException&, 
	     const IllegalStateException&,
             const UnsuccessfulRequiredCallException&);

    /*
     * This method starts the CompositeNode.
     */
    virtual Result doStart()
      throw (const StateTransitionFailedException&, 
	     const IllegalStateException&,
             const UnsuccessfulRequiredCallException&);

    /**
     * This method stops the CompositeNode.
     */
    virtual Result doStop()
      throw (const StateTransitionFailedException&, 
	     const IllegalStateException&,
             const UnsuccessfulRequiredCallException&);

    /**
     * This method deactivates the CompositeNode. 
     */
    virtual Result doDeactivate()
      throw (const StateTransitionFailedException&, 
	     const IllegalStateException&,
             const UnsuccessfulRequiredCallException&);

    /**
     * This method deinitializes the Output of the CompositeNode. 
     */
    virtual Result doDeinitOutput()
      throw (const StateTransitionFailedException&, 
	     const IllegalStateException&,
             const UnsuccessfulRequiredCallException&);

    /**
     * This method deinitializes the CompositeNode. 
     */
    virtual Result doDeinit()
      throw (const StateTransitionFailedException&, 
	     const IllegalStateException&,
             const UnsuccessfulRequiredCallException&);

    /**
     * This method causes the CompositeNode to perform a 
     * flush-operation on its nodes.
     */
    virtual Result doFlush()
      throw (const IllegalStateException&);

    // dummy implementations for Node interface methods not supported
    virtual InputJack * getOwnInputStream(const string & = "default") { return NULL; }
    virtual OutputJack * getOwnOutputStream(const string & = "default") { return NULL; }

    virtual const InputJack * getOwnInputStream(const string & = "default") const { return NULL; }
    virtual const OutputJack * getOwnOutputStream(const string & = "default") const { return NULL; }

    virtual const InputJack * getInputJack(const Format *, const nmm_char* = 0) const { return NULL; }
    virtual const OutputJack* getOutputJack(const Format* , const nmm_char*  = 0, bool = true) const { return NULL; }

    /** Method from interface INode */
    virtual Format* getInputStreamFormat(const string& /* jack_tag = "default" */) { return NULL; }
    /** Method from interface INode */
    virtual Format* getOutputStreamFormat(const string& /* jack_tag = "default" */) { return NULL; }

    /**
     * This method internally blocks the node (basically the same as stop, only
     * the state of the node is not changed and the method does not block until the node
     * is really stopped)
     */
    virtual void block();

    /**
     * This method internally unblocks the node (basically the same as start, only
     * the state of the node is not changed)
     */
    virtual void unblock();

  public:
    // to be able to serialize these enums to a ostream (in the cpp!), we must declare it public!
    enum JackType {
      INPUT = 0,
      OUTPUT = 1,
      STATIC_INPUT = 0,
      STATIC_OUTPUT = 1,
      WORKING_INPUT = 2,
      WORKING_OUTPUT = 3
    }; //! enum to specify the type of jacks & properties to use

    enum TransitionTargetState {
      INITIALIZED,
      OUTPUT_INITIALIZED,
      ACTIVATED,
      STARTED,
      STOPPED,
      DEACTIVATED,
      OUTPUT_DEINITIALIZED,
      DEINITIALIZED
    }; //! enum to specify the target of the doStateTransitionTo method

  private:

    ClientRegistry* registry;

    class CNNode {
    private:
      mutable INode& node;
      mutable list<Interface*> interfaces;
      mutable list<string> events;

    public:
      CNNode(INode& n, list<Interface*>& i, list<string>& e)
        : node(n), interfaces(i), events(e) {}

      INode & getNode() const { return node; }
      list<Interface*> & getInterfaces() const { return interfaces; }
      list<string> & getEvents() const { return events; }
    };

    map<unsigned int, CNNode> nodes; //! the list of nodes, contained in the CompositeNode

    /*** Jacks ***/
    /**
     * This struct contains all the information associated with a single
     * jack. These are the jack tag to be used and node, where the
     * jack is found.
     */
    struct CNJack_t {
      string jack_tag;      ///< The real tag of the jack. 
      INode* node;                 ///< The node, where the jack is found.

      CNJack_t(const string & tag = string(""), INode* n = NULL)
        : jack_tag(tag),
          node(n) { }
    };

    /**
     * This map type is used for maps that contain all the input or 
     * output jacks. Jacks are stored as CNJack_t structs and accessed 
     * via their associated label in the CompositeNode (the "jack tag"), 
     * hence the extra compare function.
     */
    typedef map<string, CNJack_t> CNJackMap_t;

    /*** InputJacks ***/
    /**
     * The input jack map of the CompositeNode.
     */
    mutable CNJackMap_t input_jacks; ///< The actual map

    /*** OutputJacks ***/
    /**
     * The output jack map of the CompositeNode.
     */
    mutable CNJackMap_t output_jacks; ///< The actual map

//     /*** Events from the Nodes ***/
//     CNEventHandler set_handler;
//     CNEventHandler get_handler;

    /**
     * this class handles events sent from the internal nodes to
     * IEventReceiver registered for _ALL_ events from this
     * CompositeNode. Its major purpose is to forward all events,
     * except some events tested for in isForwardingAllowed() method.
     */
    class ProxyEventReceiver
      : public IEventReceiver {

    public:
      /**
       * The Constructor must get a IEventReceiver to forward events to.
       */
      ProxyEventReceiver(IEventReceiver&);

      /**
       * The Destructor.
       * Must be virtual as this class inherits from another one.
       */
      virtual ~ProxyEventReceiver();

      // IEventReceiver interface
      Result receiveEvent(Event& e, const IEventSender* s = 0) { return receiveEventInternal(e,s); }
      
      Event* getEventPrototype(const string&) const { return 0; }

    private:
      //! The IEventReceiver to forward events to
      IEventReceiver& forwarding_receiver;

      /**
       * internal helper method to avoid duplicate code in
       * receiveEvent(const Event&, IEventSender*) and 
       * receiveEvent(const Event&, IEventSender*) const.
       * \param event The received Event. See IEventReceiver.
       * \param sender The sender of the event. See IEventReceiver.
       * \returns SUCCESS if forwarding is not allowed, otherwise the
       *          Result of the coresponding receiveEvent() call of
       *          the IEventReceiver the event is forwarded to.
       */
      Result receiveEventInternal(Event& event, const IEventSender* sender);

      /**
       * small helper to decide, if a event is allowed to be
       * forwarded.
       * \param event The Event on which the decision is to be made.
       * \returns true iff the event is allowed to be forwarded.
       * note: At the moment, the Events with Keys
       * "NMMObject::registerEventListener" and 
       * "NMMObject::removeEventListener" are forbidden as these
       * events affect the EventHandling itself and leed to strange
       * behaviours if forwarded too.
       */
      inline static bool isForwardingAllowed(const Event& event) {
        return (("NMMObject::registerEventListener" != event.getKey()) &&
                ("NMMObject::removeEventListener"   != event.getKey()));
      }

    };

    //! A Mutex to protect registerEventListener(IEventSender*) from
    //  being called multiple times at once!
    ThreadMutex register_receiver_mutex;

    //! A Map of ProxyEventReceivers which handle the Events of the
    //  internal Nodes and forward the by decision to the registered
    //  IEventReceivers
    multimap<IEventReceiver*, ProxyEventReceiver> proxy_receivers;

    set<string> forwarding_events;

    bool reach_state;

    /**
     * internal method to handle the state transitions init, activate, 
     * start, stop, deactivate and deinit.
     * \param target_state the state to transit to.
     * \returns SUCCESS iff the state transition was successful.
     */
    Result doStateTransitionTo(TransitionTargetState target_state)
      throw (const StateTransitionFailedException&, 
	     const IllegalStateException&,
             const UnsuccessfulRequiredCallException&);


    void removeNode(const unsigned int & node_name, bool do_release = false);

    void addAllFormats(const JackType, const unsigned int &);

    void removeAllFormats(const JackType, const unsigned int &);

    /**
     * internal method to find a CNJack in the specified jack map.
     * \param jack_tag the tag of the wanted CNJack.
     * \param jacks the map of CNJacks to search in.
     * \returns the CNJack if found, the first CNJack if not and NULL if ther are non else.
     */
    const CNJack_t getCNJackFromJackTag(const JackType, const string & jack_tag) const;

    Result renameJackTag(JackType dir, const string & old_cn_jack_tag, const string & new_cn_jack_tag);

    /**
     * this methods creates a new Interface of the given type and adds
     * it to this CompositeNode's Interfaces iff it is not added yet.
     * \param type The type of the Interface to add.
     * note: Interfaces like INode, which are implemented with a
     *       special semantics are automatically filtered as a empty
     *       CompositeNode already added them by its super-classes
     *       C'tors.
     */
    void addCNInterface(const TypeInfo & type);

    /**
     * this methods adds a event key to this CompositeNode's supported
     * Events iff it is not added yet.
     * \param key The key of the Event to add.
     * note: Events like INode::start, which are implemented with a
     *       special semantics are automatically filtered as a empty
     *       CompositeNode already registered them by its super-classes
     *       C'tors.
     */
    void addCNEvent(const string & key);

    /**
     * This method is used to unique the same code in
     * CompositeNode::receiveEvent(const Event&, const IEventSender*)
     * and
     * CompositeNode::receiveEvent(const Event&, const IEventSender*) const
     * \param event The Event received.
     * \param sender The Sender of this event.
     * \returns The return value of the super-class method if this is
     *          called or SUCCESS iff the Event is sent to all
     *          internal Nodes.
     */
    Result receiveEventInternal(Event & event, const IEventSender* sender);

    /**
     * This method is used to update the Interface-Lists of all
     * currently connected ProxyObjects. The reason, why we need this
     * is, that the connected PO's do NOT detect, if the
     * Interface-List of their connected Implentation have changed
     * (Normally, these lists are static and do not change at
     * run-time). The method CompositeNode::addNodeWithInterfaces() is
     * a exception: It adds Interfaces to the CompositeNode-Object at
     * runtime. To be able to use these newly added Interfaces from
     * the application, the existing ProxyObjects must be informed, to
     * update their Interface-Lists. That's what this method does.
     * \note As the CompositeNode is currently the only object, that
     *       needs it, the method is implemented her, not in
     *       NMMObject!
     */
    void updateRemoteInterfaceLists() const;


    GraphDescription*  __graph_description;

  };

}

#include "nmm/nmm_suffix.hpp"

#endif //NMM_COMPOSITENODE_HPP
