/* 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_MPEGTIMESHIFTINGNODE_HPP_
#define _NMM_MPEGTIMESHIFTINGNODE_HPP_ _NMM_MPEGTIMESHIFTINGNODE_HPP_

#include "nmm/nmm_prefix.hpp"

#include "nmm/multimedia/GenericFilterNode.hpp"

#include "nmm/plugins/file/timeshifting/FileBuffer.hpp"
#include "nmm/plugins/file/timeshifting/ITimeshiftingImpl.hpp"

namespace NMM {
  
  /** This node implements timeshifting capabilities.
   * The timeshifting node implements (limited) seeking and pausing in streams coming from a live source.
   * This is implemented by means of a memory buffer (for instant replay) and a file buffer (for real timeshifting).
   * The node can operate in live mode with only the memory buffer (optionally) enabled, so timeshifting can be turned
   * of without reconnecting the graph. Seeking in both directions is implemented by changing the playback speed.
   * Instream events are also recorded in the buffers and sent again when replaying the buffer, except in rewind mode.
   * The file buffer can be kept after destroying the node and reused in a DVB simulator.
   *
   * @bug If the source is an IVTVReadNode, video frames are garbled in REWIND mode
   */
  class MPEGTimeShiftingNode :
    public GenericFilterNode,
    public ITimeshiftingImpl
  {      
  public:
    /** Constructor.
     */
    MPEGTimeShiftingNode( const char * = "MPEGTimeShiftingNode", StreamQueue::Mode = StreamQueue::MODE_SUSPEND, int = 30);
    
    /** Virtual destructor.
     */
    ~MPEGTimeShiftingNode();
    
    /** @name Memory Buffer
     */
    //@{
    /** Enable memory buffer.
     * Enable a memory buffer for instant replay that holds up to @a size buffers.
     * @param size Maximum number of buffers in the memory buffer
     */
    virtual void enableMemBuf(const nmm_uint32& size = 60);
    
    /** Disable memory buffer.
     * Disable and free the memory buffer. Set the @a play flag for instant replay.
     * @param play If true, start playback at the beginning of the buffer
     */
    virtual void disableMemBuf(const nmm_bool& play = false);
    
    /** Is memory buffer enabled.
     * @return true if the memorybuffer is enabled
     */
    virtual nmm_bool isMemBuf();
    
    /** Keep incoming data.
     * Control whether to keep incoming data (and forward no buffers) or not.
     * @param keep if true, keep data
     */
    virtual void keepData(const nmm_bool& keep);
    //@}
    
    /** @name Modes and Play Modes
     */
    //@{
    /** Get current main mode.
     * @return Current main mode
     */
    virtual ITimeshifting::Mode getMode() const;
    
    /** Get current play mode.
     * @return Current play mode, if the main mode is FILEBUF
     */
    virtual ITimeshifting::PlayMode getPlayMode() const;
    
    /** Switch to LIVE mode.
     * Switch the node into the LIVE main mode. This completely disables timeshifting and file buffering.
     * @param enable_memory_buffer Enable memory buffering
     * @param send_sync_reset Send a ISyncReset::syncReset event after the mode change
     */
    virtual void setModeLive(const nmm_bool& enable_memory_buffer = true, const nmm_bool& send_sync_event = true);
    
    /** Set play mode to FB_LIVE.
     * Switch the node into FILEBUF mode and set the play mode to FB_LIVE.
     * @param send_sync_reset Send a ISyncReset::syncReset event after the mode change
     */
    virtual void setModeFBLive(const nmm_bool& send_sync_event = true);
    
    /** Set play mode to PLAY.
     * Set the play mode to PLAY. The main mode must be FILEBUF!
     * @param send_sync_reset Send a ISyncReset::syncReset event after the mode change
     */
    virtual void setModePlay(const nmm_bool& send_sync_reset = true);
    
    /** Set play mode to PAUSE.
     * Switch the node into FILEBUF mode and set the play mode to PAUSE.
     */
    virtual void setModePause();
    
    /** Set play mode to FAST_FORWARD.
     * Set the play mode to FAST_FORWARD. The main mode must be FILEBUF!
     * Also set the playback speed, which is the number of I-Frames to skip, to @a speed.
     * If there are no more buffers to send, the play mode is imediately set to FB_LIVE.
     * @param speed Playback speed (number of I-Frames to skip for every I-frame sent)
     */
    virtual void setModeFastForward(const nmm_int32& speed = 5);
    
    /**
     * Set play mode to REWIND.
     * Set the play mode to REWIND. The main mode must be FILEBUF!
     * Also set the playback speed, which is the number of I-Frames to skip, to @a speed.
     * If there are no more buffers to send, the play mode is imediately set to PLAY.
     * @param speed Playback speed (number of I-Frames to skip for every I-frame sent)
     */
    virtual void setModeRewind(const nmm_int32& speed = -5);
    //@}
    
    /** @name File Buffer
     */
    //@{
    /** Set file buffer filename.
     * Use another file for buffering if current file is empty.
     * @param filename Buffer filename
     */
    virtual void setFilename(const string& filename);
    
    /** Set file buffer threshold.
     * Sets the file buffer threshold, which is the maximum number of GOPs to store in the file buffer.
     * If the buffer is full, the oldest recorded GOPs is overwritten.
     * @param threshold File buffer threshold
     */
    virtual void setFileBufferThreshhold(const nmm_uint32& threshold) /* limited(INode::CONSTRUCTED) */;
    
    /** Set maximum buffer size.
     * Sets the maximum size of arriving buffers to @a size. This should be the maximum size of a GOP.
     * @param size Maximum buffer size
     * @return SUCCESS if the method succeeded, otherwise FAILURE
     */
    virtual Result setBufferSize(const nmm_uint32& size);
    
    /** Enter recording mode.
     * Switch the file buffer into recording mode. In this mode, no blocks are overwritten and the file buffer
     * is not deleted after the node is destroyed.
     */
    virtual void startRecord();
    
    /** Leave recording mode.
     * Switch the file buffer back from recording mode into to mode.
     */
    virtual void stopRecord();
    
    /** Is file buffer in recording mode.
     * @return True if the file buffer is in recording mode
     */
    virtual nmm_bool isRecording();
    
    /** Save file buffer to file.
     * Saves the file buffer (without events) to a file.
     * @param filename Save filename
     */
    virtual Result saveToFile(const string& filename);
    //@}
    
    /** @name Timing
     */
    //@{
    /** Get read position.
     * This method returns the current read position in the timeshifting buffer.
     * In FILEBUF mode, this value is between the values returned by getStartTime and getLivePosition.
     * In LIVE mode, this returns the same value as getLivePosition.
     * @return Current read position
     */
    virtual Time getReadPosition() const /* limited(INode::STARTED) */;
    
    /** Get starting time.
     * This method returns the (absolute) time when the tiomeshifting buffer begins.
     * In LIVE mode, this is the same value as returned by getLivePosition.
     * @return The start time of the timeshifting buffer
     */
    virtual Time getStartTime() const /* limited(INode::STARTED) */;
    
    /** Get live position.
     * This method returns the (absolute) time when the timeshifting buffer ends.
     * In LIVE mode, this is the same value as returned by getStartTime.
     * @return The end time of the timeshifting buffer
     */
    virtual Time getLivePosition() const /* limited(INode::STARTED) */;
    
    /** Get live offset.
     * This method computes the "live" offset when timeshifting. This is the offset between the
     * live position and the current read position.
     * In LIVE mode, the live offset is always zero
     * @return The live offset
     */
    virtual Interval getLiveOffset() const /* limited(INode::STARTED) */;
    
    /** Get file size.
     * This method computes the length of the timeshifting buffer as a time interval.
     * In LIVE mode, the file length is always zero
     * @return The length of the timeshifting buffer
     */
    virtual Interval getFileLength() const /* limited(INode::STARTED) */;
    //@}
    
  protected:
    /** Generic node doInit mode.
     */
    Result doInit();

    /** Generic node doInit mode.
     */
    Result doInitOutput();

    /** Generic node doDeinit mode.
     */
    Result doDeinit();

    /** Generic node doFlush mode.
     */
    Result doFlush();
    
    /** Process incoming buffer.
     */
    Message* processBuffer(Buffer*);
    
    /** Process incoming CEvent.
     */
    Result processCEvent(CEvent* event);
    
  private:
    /** Flush memory buffer.
     */
    void clearMemoryBuffer();
    
    /** Get the timestamp of a buffer.
     */
    Time getTimestamp(Buffer* in_buffer);
    
    /** Read buffer from file buffer.
     */
    Buffer* popBuffer(size_t next_buffer_size, int skip);
    
    /* Read event from file buffer.
     */
    CEvent* popEvent(size_t next_buffer_size, int skip);
    
  private:
    /** Memory buffer block.
     * This structure is used to buffer incoming data and events in memory.
     */
    struct IntData {
      int size;
      char* data;
      bool is_event;
      Time timestamp;
    };
    
    /** Current main mode.
     */
    ITimeshifting::Mode mode;
    
    /** Current play mode.
     */
    ITimeshifting::PlayMode playmode;
    
    /** Current playback speed in FAST_FORWARD or REWIND mode.
     */
    int playback_speed;
    
    /** Maximum incoming buffer size.
     */
    unsigned int mpeg_buffer_size;
    
    /** File buffer.
     */
    FileBuffer* fbuf;
    
    /** File buffer threshold.
     * If file buffer contains more than this umber of blocks, the oldest blocks are overwritten by new blocks.
     */
    int filebuffer_threshold;
    
    /** Memory buffering is enabled.
     */
    bool use_membuf;
    
    /** Maximum number of blocks in the memory buffer.
     */
    unsigned int max_membuf_size;
    
    /** The memory buffer.
     */
    std::queue<IntData> membuf;
    
    /** Buffer file name.
     */
    string filename;
    
    /** Last buffer received.
     */
    Buffer* last_in_buffer;
    
    /** Indicates that events have to be sent before the next buffer.
     */
    bool start_play;
    
    /** Indicates that events have to be sent before the next buffer.
     */    
    bool start_play2;
    
    /** Indicates that events have to be sent before the next buffer.
     */
    bool start_seek;
    
    /** Indicates that the queues of the following nodes will be cleared.
     */
    bool send_flush;
    
    /**
     */
    bool send_play;
    
    /** Indicates that a progress event must be sent.
     */
    bool send_progress;
    
    /** Holds buffers that have to be sent.
     */
    std::list<Message*> ready_out_buffers;
    
    /** Mutex for processBuffer.
     */
    ThreadMutex process_buffer_mutex;
    
    /** Keep data (don't forward any data in LLIVE mode).
     */
    bool keep_data;
    
    /** Current read position.
     */
    Time current_read_position;
    
    /** Current live position.
     */
    Time live_position;
    
    /** Last time a progress event was sent.
     */
    Time last_progress_time;
  };
  
}
#include "nmm/nmm_suffix.hpp"

#endif

