/*
 * Copyright (C) 2002-2003 Fhg Fokus
 *
 * This file is part of SEMS, a free SIP media server.
 *
 * SEMS 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; either version 2 of the License, or
 * (at your option) any later version. This program is released under
 * the GPL with the additional exemption that compiling, linking,
 * and/or using OpenSSL is allowed.
 *
 * For a license to use the SEMS software under conditions
 * other than those described here, or to purchase support for this
 * software, please contact iptel.org by e-mail at the following addresses:
 *    info@iptel.org
 *
 * SEMS 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 this program; if not, write to the Free Software 
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
/** @file AmRtpStream.h */
#ifndef _RtpStream_h_
#define _RtpStream_h_


#include "AmSdp.h"
#include "AmThread.h"
#include "SampleArray.h"
#include "AmRtpPacket.h"
#include "AmEvent.h"
#include "AmDtmfSender.h"
#include "AmAppTimer.h"

#include <netinet/in.h>

#include <string>
#include <map>
#include <queue>
#include <memory>
using std::string;
using std::unique_ptr;
using std::pair;

class AmRtpTransport;

// return values of AmRtpStream::receive
#define RTP_EMPTY        0 // no rtp packet available
#define RTP_ERROR       -1 // generic error
#define RTP_PARSE_ERROR -2 // error while parsing rtp packet
#define RTP_TIMEOUT     -3 // last received packet is too old
#define RTP_DTMF        -4 // dtmf packet has been received
#define RTP_BUFFER_SIZE -5 // buffer overrun
#define RTP_UNKNOWN_PL  -6 // unknown payload


/**
 * Forward declarations
 */
class  AmAudio;
class  AmSession;
struct SdpPayload;
struct amci_payload_t;
class msg_logger;

/**
 * This provides the memory for the receive buffer.
 */
struct PacketMem {
#define MAX_PACKETS_BITS 5
#define MAX_PACKETS (1<<MAX_PACKETS_BITS)
#define MAX_PACKETS_MASK (MAX_PACKETS-1)

  AmRtpPacket packets[MAX_PACKETS];
  bool        used[MAX_PACKETS];

  PacketMem();

  inline AmRtpPacket* newPacket();
  inline void freePacket(AmRtpPacket* p);
  inline void clear();

private:
  unsigned int cur_idx;
  unsigned int n_used;
};

/** \brief event fired on RTP timeout */
class AmRtpTimeoutEvent
  : public AmEvent
{
	
public:
  AmRtpTimeoutEvent() 
    : AmEvent(0) { }
  ~AmRtpTimeoutEvent() { }
};

/** helper class for assigning boolean floag to a payload ID
 * it is used to check if the payload should be relayed or not */
class PayloadMask
{
  private:
    unsigned char bits[16];

  public:
    // clear flag for all payloads
    void clear();

    // set given flag (TODO: once it shows to be working, change / and % to >> and &)
    void set(unsigned char payload_id) { if (payload_id < 128) bits[payload_id / 8] |= 1 << (payload_id % 8); }

    // set all flags to 'true'
    void set_all();

    // invert all flags
    void invert();

    // get given flag
    bool get(unsigned char payload_id) { if (payload_id > 127) { ERROR("BUG: payload_id out of range\n"); return false; } return (bits[payload_id / 8] & (1 << (payload_id % 8))); }
    
    PayloadMask() { clear(); }
    PayloadMask(bool _set_all) { if (_set_all) set_all(); else clear(); }
    PayloadMask(const PayloadMask &src);
};

/**
 * \brief represents one admissible payload type
 *
 *
 */
struct Payload {
  unsigned char pt;
  string        name;
  unsigned int  clock_rate;
  unsigned int  advertised_clock_rate; // differs for G722
  int           codec_id;
  string        sdp_format_parameters;
};

/**
 * \brief RTP implementation
 *
 * Rtp stream high level interface.
 */
class AmRtpStream
  : public AmObject
{

private:

  class KeepAliveTimer
    : public DirectAppTimer
  {
    AmRtpStream* stream;

  public:
    KeepAliveTimer(AmRtpStream* stream)
      : stream(stream)
    {}

    void fire(){
      stream->onKeepAliveTimeout();
    }
  };

  class RtpTimer
    : public DirectAppTimer
  {
    AmRtpStream* stream;

  public:
    RtpTimer(AmRtpStream* stream)
      : stream(stream)
    {}

    void fire(){
      stream->onRtpTimeout();
    }
  };

friend class AmSession;
friend class AmRtpTransport;
friend class KeepAliveTimer;
friend class RtpTimer;

public:

  // hooks into RTP stream functionality
  class Hook
  {
    public:
      virtual void receivedPacket(AmRtpPacket *p) = 0;
      virtual void relayedPacket(AmRtpPacket *p) = 0;
      virtual void initStream(const AmSdp& local, const AmSdp& remote, int media_idx) = 0;
      virtual ~Hook() { }
  };

protected:

  /* Keep Alive Timer */
  KeepAliveTimer rtp_keepalive_timer;

  unsigned int rtp_keepalive_freq;

  void onKeepAliveTimeout();

  /* RTP Timer */
  RtpTimer rtp_timer;

  unsigned int rtp_timeout;

  void onRtpTimeout();

  // payload collection
  typedef std::vector<Payload> PayloadCollection;
  
  // list of locally supported payloads
  PayloadCollection payloads;

  // list of payloads present in the stream description
  std::map<int,int> offered_payloads;

  // current payload (index into @payloads)
  int payload;

  struct PayloadMapping {
    // remote payload type
    int8_t remote_pt;

    // index in payloads vector
    uint8_t index;
  };

  typedef std::map<unsigned int, AmRtpPacket*, ts_less> ReceiveBuffer;
  typedef std::queue<AmRtpPacket*>                      RtpEventQueue;
  typedef std::map<unsigned char, PayloadMapping>       PayloadMappingTable;
  
  // mapping from local payload type to PayloadMapping
  PayloadMappingTable pl_map;

  /** SDP media slot number (n-th media line) */
  int sdp_media_index;

  /** RTP sequence number */
  unsigned int sequence;

  /**
     Payload of last received packet.
     Usefull to detect talk spurt, looking 
     for comfort noise packets.
  */
  int         last_payload;

  /** RTP transport */
  AmRtpTransport* rtp_transport;

  /** Timestamp of the last received RTP packet */
  uint64_t last_recv_time;

  /** Local and remote SSRC information */
  uint32_t  l_ssrc;
  uint32_t  r_ssrc;
  bool           r_ssrc_i;

  /** symmetric RTP & RTCP */
  bool           passive;
  bool           passive_rtcp;

  /** mute && port == 0 */
  bool           hold;

  /** marker flag */
  bool           begin_talk;

  /** do check rtp timeout */
  bool           monitor_rtp_timeout;

  /** Payload type for telephone event */
  unique_ptr<const SdpPayload> remote_telephone_event_pt;
  unique_ptr<const SdpPayload> local_telephone_event_pt;

  /** DTMF sender */
  AmDtmfSender   dtmf_sender;

  /**
   * Receive buffer, queue and mutex
   */
  PacketMem       mem;
  ReceiveBuffer   receive_buf;
  RtpEventQueue   rtp_ev_qu;
  AmMutex         receive_mut;

  /** should we receive packets? if not -> drop */
  bool receiving;

  /** if relay_stream is initialized, received RTP is relayed there */
  bool            relay_enabled;
  /** if true, packets are note parsed or checked */
  bool            relay_raw;
  /** pointer to relay stream.
      NOTE: This may only be accessed in initialization
      or by the AmRtpReceiver thread while relaying!  */
  AmRtpStream*    relay_stream;
  /** control transparency for RTP seqno in RTP relay mode */
  bool            relay_transparent_seqno;
  /** control transparency for RTP ssrc in RTP relay mode */
  bool            relay_transparent_ssrc;
  /** filter RTP DTMF (2833 / 4733) in relaying */
  bool            relay_filter_dtmf;

  /** Session owning this stream */
  AmSession*         session;

  /** Payload provider */
  AmPayloadProvider* payload_provider;

  Hook* hook;

  /** insert packet in DTMF queue if correct payload */
  void recvDtmfPacket(AmRtpPacket* p);

  /** Insert an RTP packet to the buffer queue */
  void bufferPacket(AmRtpPacket* p, sockaddr_storage& recv_addr);
  /* Get next packet from the buffer queue */
  int nextPacket(AmRtpPacket*& p);
  
  /** Try to reuse oldest buffered packet for newly coming packet */
  AmRtpPacket *reuseBufferedPacket();

  /** handle symmetric RTP/RTCP - if in passive mode, update raddr from rp */
  void handleSymmetricRtp(struct sockaddr_storage* recv_addr, bool rtcp);

  void relay(AmRtpPacket* p);

  /** Sets generic parameters on SDP media */
  void getSdp(SdpMedia& m);

  /** Clear RTP timeout at time recv_time */
  void clearRTPTimeout(struct timeval* recv_time);

  PayloadMask relay_payloads;
  bool offer_answer_used;

  /** set to true if any data received */
  bool active;

  /** 
   * Select a compatible default payload 
   * @return -1 if none available.
   */
  int getDefaultPT();

  // generate DTMF into the RTP stream
  void generateDtmf(unsigned int ts);

public:

  // set hook and return the old one
  Hook *setHook(Hook *h);

  /**
   * Set whether RTP stream will receive RTP packets internally (received packets will be dropped or not).
   */
  void setReceiving(bool r);

  /**
   * Stops RTP stream receiving RTP packets internally (received packets will be dropped).
   */
  void pause();

  /**
   * Resume a paused RTP stream internally (received packets will be processed).
   */
  void resume();

  /** Mute */
  bool mute;

  /** should we receive RFC-2833-style DTMF even when receiving is disabled? */
  bool force_receive_dtmf;

  /** Allocates resources for future use of RTP. */
  AmRtpStream(AmSession* _s, int _if);

  /** Stops the stream and frees all resources. */
  virtual ~AmRtpStream();

  int send( unsigned int ts,
	    unsigned char* buffer,
	    unsigned int   size );
  
  int send_raw( char* packet, unsigned int length );

  int compile_and_send( const int payload, bool marker, 
		        unsigned int ts, unsigned char* buffer, 
		        unsigned int size );

  int receive( unsigned char* buffer, unsigned int size,
	       unsigned int& ts, int& payload );

  void recvPacket(int fd);

  void recvRtpPacket(unsigned char* buffer, int size, sockaddr_storage& recv_addr);
  void recvRtcpPacket(unsigned char*, int, sockaddr_storage&);

  void recvRtcpPacket();

  /** ping the remote side, to open NATs and enable symmetric RTP */
  void ping();

  /** 
   * @return local RTCP port. 
   */
  int getLocalRtpPort();

  /** 
   * @return local RTCP port. 
   */
  int getLocalRtcpPort();

  /** 
   * Gets remote RTP port.
   * @return remote RTP port.
   */
  int getRemoteRtpPort();
    
  /**
   * Gets remote host IP.
   * @return remote host IP.
   */
  string getRemoteAddress();

  /** Symmetric RTP & RTCP: passive mode ? */
  void setPassiveMode(bool p);
  bool getPassiveMode() { return passive || passive_rtcp; }

  unsigned int get_ssrc() { return l_ssrc; }

  void setRemoteSSRC(unsigned int ssrc);
  unsigned int getRemoteSSRC();

  int getLocalTelephoneEventPT();
  int getLocalTelephoneEventRate();

  void setPayloadProvider(AmPayloadProvider* pl_prov);

  int getSdpMediaIndex() { return sdp_media_index; }
  void forceSdpMediaIndex(int idx) { sdp_media_index = idx; offer_answer_used = false; }
  int getPayloadType() { return payload; }
  int getLastPayload() { return last_payload; }
  string getPayloadName(int payload_type);

  /**
   * send a DTMF as RTP payload (RFC4733)
   * @param event event ID (e.g. key press), see rfc
   * @param duration_ms duration in milliseconds
   */
  void sendDtmf(int event, unsigned int duration_ms);

  /**
   * Generate an SDP offer based on the stream capabilities.
   * @param index index of the SDP media within the SDP.
   * @param offer the local offer to be filled/completed.
   */
  virtual void getSdpOffer(unsigned int index, SdpMedia& offer);

  /**
   * Generate an answer for the given SDP media based on the stream capabilities.
   * @param index index of the SDP media within the SDP.
   * @param offer the remote offer.
   * @param answer the local answer to be filled/completed.
   */
  virtual void getSdpAnswer(unsigned int index, const SdpMedia& offer, SdpMedia& answer);

  /**
   * Enables RTP stream.
   * @param local the SDP message generated by the local UA.
   * @param remote the SDP message generated by the remote UA.
   * @warning It is necessary to call getSdpOffer/getSdpAnswer prior to init(...)
   * @warning so that the internal SDP media line index is set properly.
   */
  virtual int init(const AmSdp& local, const AmSdp& remote, bool force_passive_mode = false);

  /** set the RTP stream on hold */
  void setOnHold(bool on_hold);
  
  /** get whether RTP stream is on hold  */
  bool getOnHold();

  /** setter for monitor_rtp_timeout */
  void setMonitorRTPTimeout(bool m) { monitor_rtp_timeout = m; }
  /** getter for monitor_rtp_timeout */
  bool getMonitorRTPTimeout() { return monitor_rtp_timeout; }

  /*
   * clear RTP timeout to current time
   */
  void clearRTPTimeout();

  /** set relay stream for  RTP relaying */
  void setRelayStream(AmRtpStream* stream);

  /** set relay payloads for  RTP relaying */
  void setRelayPayloads(const PayloadMask &_relay_payloads);

  /** ensable RTP relaying through relay stream */
  void enableRtpRelay();

  /** disable RTP relaying through relay stream */
  void disableRtpRelay();

  /** enable raw UDP relaying through relay stream */
  void enableRawRelay();

  /** disable raw UDP relaying through relay stream */
  void disableRawRelay();

  /** Check raw UDP relaying through relay stream */
  bool isRawRelayed();

  /** enable or disable transparent RTP seqno for relay */
  void setRtpRelayTransparentSeqno(bool transparent);

  /** enable or disable transparent SSRC seqno for relay */
  void setRtpRelayTransparentSSRC(bool transparent);

  /** enable or disable filtering of RTP DTMF for relay */
  void setRtpRelayFilterRtpDtmf(bool filter);

  /** remove from RTP receiver */
  void stopReceiving();

  /** (re-)insert into RTP receiver */
  void resumeReceiving();

  /** Quick hack to assign existing stream to another session. The stream should
   * not be reinitialised implicitly (it might be used for media traffic
   * already). */
  void changeSession(AmSession *_s);

  void debug(std::ostream &out, const char *line_prefix = "");

  /** set RTP and RTCP transports **/
  void setRtpTransport(AmRtpTransport* rtp_transport);

  AmRtpTransport* getRtpTransport();
};

#endif
