/*
 * Copyright (C) 2010-2011 Stefan Sayer
 * Copyright (C) 2012-2013 FRAFOS GmbH
 *
 * 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.
 *
 * 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
 */

#ifndef __SBCCALL_LEG_H
#define __SBCCALL_LEG_H

#include "SBC.h"
#include "ExtendedCCInterface.h"
#include "sbc_events.h"
#include "RateLimit.h"

class PayloadIdMapping
{
  private:
    std::map<int, int> mapping;
      
  public:
    void map(int stream_index, int payload_index, int payload_id);
    int get(int stream_index, int payload_index);
    void reset();
};

class SBCCallLeg : public CallLeg, public CredentialHolder
{
  enum {
    BB_Init = 0,
    BB_Dialing,
    BB_Connected,
    BB_Teardown
  } CallerState;

  int m_state;

  map<int, double> call_timers;

  // call control
  vector<AmDynInvoke*> cc_modules;
  vector<ExtendedCCInterface*> cc_ext;

  // modules to initialize
  CCInterfaceListT cc_module_queue;

  // current timer ID - cc module setting timer will use this
  int cc_timer_id;
  int ext_cc_timer_id; // for assigning IDs to timers through "extended CC interface"

  // auth
  AmSessionEventHandler* auth;
  AmDynInvoke* auth_di;

  /** Storage for remembered payload IDs from SDP offer to be put correctly into
   * SDP answer (we avoid with this parsing SDP offer again when processing the
   * answer). We can not use call_profile.transcoder.audio_codecs for storing
   * the payload IDs because they need to be remembered per media stream. */
  PayloadIdMapping transcoder_payload_mapping;

  SBCCallProfile call_profile;

  // Rate limiting
  unique_ptr<RateLimit> rtp_relay_rate_limit;
  AmArg rtp_bw_limit_event; // event to be logged for reached bandwidth limit
  bool log_rtp_bw_limit_event; // flag saying whether to log the event
  
  // Measurements
  list<atomic_int*> rtp_pegs;

  // RTP bandwidth limit (based on negotiated codecs)
  unsigned int bandwidth_limit_a;
  unsigned int bandwidth_limit_b;
  unsigned int bandwidth_usage;
  bool last_sdp_offer;

  void fixupCCInterface(const string& val, CCInterface& cc_if);

  /** handler called when call is stopped (see AmSession) */
  virtual void onStop();

  /** call is started */
  bool CCStart(const AmSipRequest& req);
  /** connection of second leg */
  void CCConnect(const AmSipReply& reply);
  /** end call */
  void CCEnd();
  void CCEnd(const CCInterfaceListIteratorT& end_interface);

  void connectCallee(const string& remote_party, const string& remote_uri, 
		     const string &from, const AmSipRequest &original_invite, 
		     const AmSipRequest &invite_req);

  /** process (filter, ...) body (SDP) before relaying it to other leg */
  int onBeforeB2BRelayBody(AmMimeBody &body, const string &method);

  int filterSdp(AmMimeBody &body, const string &method);
  void appendTranscoderCodecs(AmSdp &sdp);
  void savePayloadIDs(AmSdp &sdp);

  /** apply A leg configuration from call profile */
  void applyAProfile(const AmSipRequest &req);

  /** apply B leg configuration from call profile */
  void applyBProfile();

  virtual void onCallStatusChange(const StatusChangeCause &cause);
  virtual void onBLegRefused(const AmSipReply& reply);

  /** handler called when the call is refused with a non-ok reply or canceled */
  virtual void onCallFailed(CallFailureReason reason, const AmSipReply *reply);

  /** handler called when the second leg is connected */
  virtual void onCallConnected(const AmSipReply& reply);

  /** Call-backs used by RTP stream(s)
   *  Note: these methods will be called from the RTP receiver thread.
   */
  virtual bool onBeforeRTPRelay(AmRtpPacket* p, sockaddr_storage* remote_addr);
  virtual void onAfterRTPRelay(AmRtpPacket* p, sockaddr_storage* remote_addr);

  void logCallStart(const AmSipReply& reply);
  void logCanceledCall();

  void alterHoldRequestImpl(AmSdp &sdp); // do the SDP update (called by alterHoldRequest)

 public:

  SBCCallLeg(const SBCCallProfile& call_profile, AmSipDialog* dlg=NULL,
	     AmSipSubscription* p_subs=NULL);
  SBCCallLeg(SBCCallLeg* caller, AmSipDialog* dlg=NULL,
	     AmSipSubscription* p_subs=NULL);
  SBCCallLeg(AmSipDialog* dlg=NULL,AmSipSubscription* p_subs=NULL);
  ~SBCCallLeg();

  void process(AmEvent* ev);
  void onInvite(const AmSipRequest& req);

  void onDtmf(int event, int duration);

  virtual void onStart();
  virtual void onBeforeDestroy();

  UACAuthCred* getCredentials();

  void setAuthHandler(AmSessionEventHandler* h) { auth = h; }
  void setAuthDI(AmDynInvoke* di_inst) { auth_di = di_inst; }

  bool initCCExtModules(const CCInterfaceListT& cc_module_list, const vector<AmDynInvoke*>& cc_module_di);
  bool initPendingCCExtModules();
  void addPendingCCExtModule(const string& cc_name, const string& cc_module, const map<string, string>& cc_values);

  /** save call timer; only effective before call is connected */
  void saveCallTimer(int timer, double timeout);
  /** clear saved call timer, only effective before call is connected */
  void clearCallTimer(int timer);
  /** clear all saved call timer, only effective before call is connected */
  void clearCallTimers();

  // SBC interface usable from CC modules

  void setLocalParty(const string &party, const string &uri) { 
    dlg->setLocalParty(party); dlg->setLocalUri(uri);
  }

  void setRemoteParty(const string &party, const string &uri) { 
    dlg->setRemoteParty(party); dlg->setRemoteUri(uri);
  }

  SBCCallProfile &getCallProfile() { return call_profile; }
  CallStatus getCallStatus() { return CallLeg::getCallStatus(); }

  /** 
   * Copy settings which need to be identical on both legs 
   */
  void copyGlobalSettings(const SBCCallProfile& callee_profile);

  void setRTPMeasurements(const list<atomic_int*>& rtp_meas) {
    rtp_pegs = rtp_meas;
  }
  const RateLimit* getRTPRateLimit() { return rtp_relay_rate_limit.get(); }
  void setRTPRateLimit(RateLimit* rl) { rtp_relay_rate_limit.reset(rl); }

  const unsigned int getBandwidthUsage() { return bandwidth_usage; }
  void getBandwidthLimits(unsigned int* limits) {
    limits[0] = bandwidth_limit_a;
    limits[1] = bandwidth_limit_b;
  }

  void setBandwidthLimits(unsigned int limit_a, unsigned int limit_b) {
    bandwidth_limit_a = limit_a;
    bandwidth_limit_b = limit_b;
  }

  bool wasLastSdpOffer() { return last_sdp_offer; }
  void setLastSdpOffer(bool is_offer) { last_sdp_offer = is_offer; }

  virtual void updateBandwidthUsage(unsigned int bw) {
    bandwidth_usage = bw;
  };

  // media interface must be accessible from CC modules
  AmB2BMedia *getMediaSession() { return AmB2BSession::getMediaSession(); }
  virtual void updateLocalSdp(AmSdp &sdp);
  void changeRtpMode(RTPRelayMode new_mode) { CallLeg::changeRtpMode(new_mode); }

  bool reinvite(const AmSdp &sdp, unsigned &request_cseq);

  int relayEvent(AmEvent* ev);
  void onSipRequest(const AmSipRequest& req);
  bool isALeg() { return a_leg; }

  // timers accessible from CC modules
  int startTimer(double timeout) { setTimer(ext_cc_timer_id, timeout); return ext_cc_timer_id++; }

  virtual void setMediaSession(AmB2BMedia *new_session);
  virtual void computeRelayMask(const SdpMedia &m, bool &enable, PayloadMask &mask);

  virtual void onAudioStreamCreated(AudioStreamData *stream);

  void onInitialRelayBody(AmMimeBody& body, const string& method);

 protected:
  /** set to true once CCStart passed to call CCEnd implicitly (from onStop)
   * only when CCStart was called */
  bool cc_started;

  // Call duration measurements
  struct timeval call_start_ts;
  struct timeval call_connect_ts;
  struct timeval call_end_ts;

  void setOtherId(const AmSipReply& reply);
  void setOtherId(const string& n_other_id) { CallLeg::setOtherId(n_other_id); }

  /** OA callback **/
  virtual int onSdpCompleted(const AmSdp& local, const AmSdp& remote);
  virtual void onSdpReceived(const AmSdp& sdp, bool is_offer);

  void onSipReply(const AmSipRequest& req, const AmSipReply& reply, AmSipDialog::Status old_dlg_status);
  void onSendRequest(AmSipRequest& req, int &flags);

  virtual void onInitialReply(B2BSipReplyEvent *e);

  void onRemoteDisappeared(const AmSipReply& reply);
  void onRtpTimeout();
  void onBye(const AmSipRequest& req);
  void onOtherBye(const AmSipRequest& req);

  void onControlCmd(string& cmd, AmArg& params);

  /* set call timer (if enabled) */
  virtual bool startCallTimers();
  /* clear call timer */
  virtual void stopCallTimers();

  const map<int, double> getCallTimers() { return call_timers; }

  void createCalleeSession();

  /** initialize call control module interfaces @return sucess or not*/
  bool getCCInterfaces();
  vector<AmDynInvoke*>& getCCModules() { return cc_modules; }

  virtual void createHoldRequest(AmSdp &sdp);
  virtual void alterHoldRequest(AmSdp &sdp);
  virtual void holdRequested();
  virtual void holdAccepted();
  virtual void holdRejected();
  virtual void resumeRequested();
  virtual void resumeAccepted();
  virtual void resumeRejected();

  int applySSTCfg(AmConfigReader& sst_cfg, const AmSipRequest* p_req);

  virtual double get491RetryTime() { return (get_random() % call_profile.max_491_retry_time) / 1000.0; }
};

#endif
