/* 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_MULTI_AUDIO_VIDEO_SYNCHRONIZER_HPP_
#define _NMM_MULTI_AUDIO_VIDEO_SYNCHRONIZER_HPP_

#include "nmm/nmm_prefix.hpp"

#include "Synchronizer.hpp"
#include "nmm/multimedia/sync/interfaces/IMultiAudioVideoSynchronizerImpl.hpp"
#include "nmm/utils/time/Interval.hpp"
#include "nmm/utils/thread/ThreadCondition.hpp"

#include "visibility.hpp" 

namespace NMM {
  class GenericSyncSinkNode;

  /** A synchronizer for multiple audio and multiple video sinks.
   * This class synchronizes an arbitrary number of audio sinks with an arbitrary number of video sinks
   * to a single (master) audio sink
   */
  class DLLEXPORT MultiAudioVideoSynchronizer :
    public SinkSynchronizer,
    virtual public IMultiAudioVideoSynchronizerImpl
  {
  private:
    /** Information about a sink.
     * This data structure stores all per-sink data.
     */
    struct InternalSink {
    private:
      ISinkController* controller;    ///< Sink controller interface
      Interval average_latency;       ///< Average latency reported by sink controller
      Interval skew;                  ///< Skew relative to synchronization master
      bool wait_for_reset;            ///< Sink is waiting for syncReset

    public:
      /** Default constructor.
       */
      InternalSink();

      /** Destructor.
       */
      ~InternalSink();

      /** Set the sink controller interface.
       *
       * @param controller Sink controller interface
       */
      void setController(ISinkController* controller);

      /** Get the sink controller interface
       *
       * @return Sink controller interface
       */
      ISinkController* getController() const;

      /** Get the average latency reported by the sink.
       *
       * @return Average latency
       */
      Interval getAverageLatency() const;

      /** Set average latency reported by the sink.
       *
       * @param average_latency Average latency
       */
      void setAverageLatency(const Interval& average_latency);

      /** Get skew relative to synchronization master.
       *
       * @return Skew relative to synchronization master
       */
      Interval getSkew() const;

      /** Set skew relative to synchronization master.
       *
       * @param skew Skew relative to synchronization master
       */
      void setSkew(const Interval& skew);

      /** Set whether sink is waiting for syncReset.
       *
       * @param value @c true if sink is waiting for sync reset, @c false otherwise
       */
      void setWaitForReset(bool value);

      /** Get whether sink is waiting for syncReset.
       *
       * @return @c true if sink is waiting for sync reset, @c false otherwise
       */
      bool getWaitForReset() const;
    };

    /** A sink map.
     * This type maps numerical sink IDs to sink data structures.
     * It is used for bookkeeping of audio and video sinks which have been added to this synchronizer.
     */
    typedef map<int, InternalSink> sinks_t;
    
    sinks_t audio_sinks; ///< Audio sink map
    sinks_t video_sinks; ///< Video sink map
    int master;          ///< Sink ID of master audio sink
    bool master_average_latency_available;  ///< Average latency of master is already known
    Interval theo_latency;                  ///< Theoretical latency
    Interval security_interval;             ///< Security interval for the output nodes
    Time pause_start;                       ///< The time of the last pause() call
    int sink_count;                         ///< Total number of sinks
    int reset_sink_count;                   ///< Number of sinks waiting for reset
    
  protected:
    /** States of the synchronizer.
     */
    enum State {
      WORKING,        ///< The synchronizer is working normally
      RESET,          ///< The synchronizer is performing a reset on all sinks
      PAUSE           ///< The synchronizer is paused
    };
    
    State state;              ///< Current state of the synchronizer
    ThreadMutex state_mutex;  ///< Mutex for synchronizing state changes

    ThreadMutex sinks_mutex;      ///< Mutex for synchronized access to sink maps

    ThreadCondition pause_cond;   ///< Condition for waiting for pause mode to end
    
    /** Compute new theoretical latency.
     * This method computes the new theoretical latency based on the average latencies of all sinks.
     *
     * @return @c true if latency has changed, @c false if not
     */
    bool computeTheoLatency();

    /** Send theoretical latency to sinks.
     * This method sends the theoretical latency to the sink controllers.
     */
    void sendTheoLatency();

    /** Resolve internal sink ID.
     * This method takes the sink ID of a controller and determines whether the sink is an audio or
     * video sink as well as the ID of the sink in the audio or video sink map.
     *
     * General usage:
     * @code
     * void methodCalledByController(int sink_id) {
     *   int id;
     *   bool is_audio;
     *   resolveSinkID(sink_id, &id, &is_audio);
     *   if(is_audio) { // audio
     *     sinks_t::const_iterator found = audio_sinks.find(id);
     *     if(found == audio_sinks.end()) {
     *       // INVALID SINK ID
     *     }
     *     else {
     *       // found audio sink
     *       InternalSink* the_sink = found->second;
     *     }
     *   }
     *   else { // video
     *     sinks_t::const_iterator found = video_sinks.find(id);
     *     if(found == video_sinks.end()) {
     *       // INVALID SINK ID
     *     }
     *     else {
     *       // found video sink
     *       InternalSink* the_sink = found->second;
     *     }
     *   }
     * }
     * @endcode
     * Note that this is the most general case, in which the method does something for both audio and video sinks.
     *
     * The internal ID of a sink is the audio/video sink ID shifted left by 1 and the lowest bit
     * set if the sink is an audio sink. It is needed for the methods from the base interfaces that
     * only accept a sink ID to identify both audio and video sinks.
     *
     * @param internal_id Sink ID
     * @param id Returns audio or video sink ID
     * @param is_audio Returns @c true if the sink is an audio sink, @c false if the sink is a video sink
     */
    void resolveSinkID(int internal_id, int* id, bool* is_audio);
    
  public:
    /** Create a new synchronizer.
     */
    MultiAudioVideoSynchronizer(const char* name = "Synchronizer");

    /** Virtual destructor.
     */
    virtual ~MultiAudioVideoSynchronizer();
    
  protected:
    /*---- internal methods -----*/
    /** Set average audio latency.
     * This method stores the average latency of an audio sink and updates the theoretical latency.
     *
     * @param id sink ID
     * @param average_latency Average latency reported by the controller
     */
    virtual Result setAverageAudioLatency(const int& id, const Interval& average_latency);

    /** Set average video latency.
     * This method stores the average latency of a video sink and updates the theoretical latency.
     *
     * @param id sink ID
     * @param average_latency Average latency reported by the controller
     */
    virtual Result setAverageVideoLatency(const int& id, const Interval& average_latency);

    /** Set an audio sink controller.
     * This method sets the controller for an audio sink.
     * If the sink id doesn't exist, a new sink is added.
     *
     * @param id audio sink id
     * @param controller controller for this sink
     * @return SUCCESS
     */
    virtual Result setAudioSinkController(const int& id, const ISinkController* controller);

    /** Set an video sink controller.
     * This method sets the controller for an video sink.
     * If the sink id doesn't exist, a new sink is added.
     *
     * @param id audio sink id
     * @param controller controller for this sink
     * @return SUCCESS
     */
    virtual Result setVideoSinkController(const int& id, const ISinkController* controller);

    /** Remove an audio sink.
     * This method removes the audio sink with the specified id.
     *
     * @param id sink id
     */
    virtual Result removeAudioSink(const int& id);

    /** Remove a video sink.
     * This method removes the video sink with the specified id.
     *
     * @param id sink id
     */
    virtual Result removeVideoSink(const int& id);

   /*---- methods from IMultiAudioSynchronizer -----*/
    /** Add an audio sink controller.
     * This method adds a the controller for an audio sink to the synchronizer.
     *
     * @param controller An audio sink controller
     * @return Sink ID of the audio controller
     */
    virtual nmm_int32 addAudioSinkController(const ISinkController* controller);

     /*---- methods from IMultiVideoSynchronizer -----*/
    /** Add a video sink controller.
     * This method adds a the controller for a video sink to the synchronizer.
     *
     * @param controller A video sink controller
     * @return Sink ID of the video controller
     */
    virtual nmm_int32 addVideoSinkController(const ISinkController* controller);

   /** Set the master audio sink.
     * This method sets an audio sink as master audio sink.
     * If this method is not called, the first audio sink is the master by default.
     *
     * @param id audio sink id
     * @return SUCCESS
     */
    virtual Result setMasterAudioSink(const nmm_int32& id);

    /** Set skew of an audio sink.
     * This method sets the skew of an audio sink relative to the master audio sink.
     *
     * @param id Sink id
     * @param skew Skew of this sink relative to master
     * @return SUCCESS
     */
    virtual Result setAudioSkew(const nmm_int32& id, const Interval& skew);

    /** Set skew of a video sink.
     * This method sets the skew of a video sink relative to the master audio sink.
     *
     * @param id Sink id
     * @param skew Skew of this sink relative to master
     * @return SUCCESS
     */
    virtual Result setVideoSkew(const nmm_int32& id, const Interval& skew);

    /*---- methods from ISinkSynchronizer -----*/
    /** Set average latency.
     * With this method, the sink controllers tell the
     * synchronizer the average latency of their incoming buffers.
     *
     * @param average_latency Average latency computed by the controller
     * @param sink Sink ID
     */
    virtual void setAverageLatency(const Interval& average_latency, const nmm_int32& sink);
 
    /** Set the average latency of the first buffer in tmie.
     * With this method, the sink controllers tell the
     * synchronizer the average latency of their first buffer in time after syncReset.
     *
     * @param average_latency The average latency of incoming buffers computed by the Controller
     * @param sink Sink ID
     * @param first_ts Timestamp of the first buffer in time
     * @return SUCCESS if value is good enough compared to the theoretical latency, FAILURE otherwise
     */   
    virtual Result setFirstBufferInTimeAverageLatency(const Interval& average_latency, const nmm_int32& sink, const Timestamp& first_ts);

    /** Remove a sink controller.
     * This method removes the sink controller with the specified interface from the synchronizer.
     *
     * @param controller Interface of sink controller to remove
     */
    virtual Result removeSinkController(const ISinkController* controller);

    /*---- methods from ISynchronizer -----*/
    /** Pause the synchronizer.
     * This method pauses all sinks synchronized by the synchronizer.
     *
     * @return @c SUCCESS if the operation succeeded, @c FAILURE otherwise
     */
    virtual Result pause();

    /** Wake up the synchronizer after pause.
     * This method wakes up all sinks synchronized by the synchronizer after pause.
     *
     * @return @c SUCCESS if the operation succeeded, @c FAILURE otherwise
     */
    virtual Result wakeup();
    
    /** Reset the synchronizer.
     * This method resets all sinks synchronized by the synchronizer.
     * It is called by a sink which received a syncReset event. Only all other sinks are reset
     * explicitly by the synchronizer.
     *
     * @param sink_id Sink ID of sink which received the initial syncReset
     */
    virtual Result reset(const nmm_int32& sink);
  };
}

#include "nmm/nmm_suffix.hpp"

#endif // _NMM_MULTI_AUDIO_VIDEO_SYNCHRONIZER_HPP_
