/*
 * 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 AmSipDialog.h */
#ifndef AmSipDialog_h
#define AmSipDialog_h

#include "AmSipMsg.h"

#include <string>
#include <vector>
#include <map>
using std::string;

#define MAX_SER_KEY_LEN 30
#define CONTACT_USER_PREFIX "sems"

// flags which may be used when sending request/reply
#define SIP_FLAGS_VERBATIM     1 // send request verbatim, 
                                 // i.e. modify as little as possible

class AmSipTimeoutEvent;

/** \brief SIP transaction representation */
struct AmSipTransaction
{
  string       method;
  unsigned int cseq;
  trans_ticket tt;

  AmSipTransaction(const string& method, unsigned int cseq, const trans_ticket& tt)
  : method(method),
    cseq(cseq),
    tt(tt)
  {}

  AmSipTransaction()
  {}
};

typedef std::map<int,AmSipTransaction> TransMap;

/**
 * \brief base class for SIP request/reply event handler 
 */
class AmSipDialogEventHandler 
{
 public:
    
  /** Hook called when a request has been received */
  virtual void onSipRequest(const AmSipRequest& req)=0;

  /** Hook called when a reply has been received */
  virtual void onSipReply(const AmSipReply& reply, int old_dlg_status,
			  const string& trans_method)=0;

  /** Hook called before a request is sent */
  virtual void onSendRequest(const string& method,
			     const string& content_type,
			     const string& body,
			     string& hdrs,
			     int flags,
			     unsigned int cseq)=0;
    
  /** Hook called before a reply is sent */
  virtual void onSendReply(const AmSipRequest& req,
			   unsigned int  code,
			   const string& reason,
			   const string& content_type,
			   const string& body,
			   string& hdrs,
			   int flags)=0;
    
  /** Hook called when a local INVITE request has been replied with 2xx */
  virtual void onInvite2xx(const AmSipReply& reply)=0;

  /** Hook called when a UAS INVITE transaction did not receive the ACK */
  virtual void onNoAck(unsigned int)=0;

  /** Hook called when a UAS INVITE transaction did not receive the PRACK */
  virtual void onNoPrack(const AmSipRequest &, const AmSipReply &)=0;

  /** Hook called when a privisional reply is received with 100rel active */
  virtual void onInvite1xxRel(const AmSipReply &)=0;

  /** Hook called when an answer for a locally sent PRACK is received */
  virtual void onPrack2xx(const AmSipReply &)=0;

  enum FailureCause {
    FAIL_REL100_421,
#define FAIL_REL100_421  AmSipDialogEventHandler::FAIL_REL100_421
    FAIL_REL100_420,
#define FAIL_REL100_420  AmSipDialogEventHandler::FAIL_REL100_420
  };
  virtual void onFailure(FailureCause cause, const AmSipRequest*, 
      const AmSipReply*)=0;

  virtual ~AmSipDialogEventHandler() {};
};

/**
 * \brief implements the dialog state machine
 */
class AmSipDialog
{
  int status;

  TransMap uas_trans;
  TransMap uac_trans;
    
  unsigned int pending_invites;

  AmSipDialogEventHandler* hdl;

  int updateStatusReply(const AmSipRequest& req, 
			unsigned int code);

  string getRoute();

  int rel100OnRequestIn(const AmSipRequest &req);
  int rel100OnReplyIn(const AmSipReply &reply);
  void rel100OnTimeout(const AmSipRequest &req, const AmSipReply &rpl);

  void rel100OnRequestOut(const string &method, string &hdrs);
  void rel100OnReplyOut(const AmSipRequest &req, unsigned int code, 
      string &hdrs);

 public:
  enum Status {
	
    Disconnected=0,
    Pending,
    Connected,
    Disconnecting
  };

  static const char* status2str[4];

  string user;         // local user
  string domain;       // local domain

  string local_uri;    // local uri
  string remote_uri;   // remote uri

  string contact_uri;  // pre-calculated contact uri

  string callid;
  string remote_tag;
  string local_tag;

  string remote_party; // To/From
  string local_party;  // To/From

  string route;
  string outbound_proxy;
  bool   force_outbound_proxy;

  string next_hop_ip;
  unsigned short next_hop_port;
  bool next_hop_for_replies;

  int  outbound_interface;
  bool out_intf_for_replies;

  /** enable the reliability of provisional replies? */
  enum provisional_100rel { // could be a char
    REL100_DISABLED,
#define REL100_DISABLED         AmSipDialog::REL100_DISABLED
    REL100_SUPPORTED,
#define REL100_SUPPORTED        AmSipDialog::REL100_SUPPORTED
    REL100_REQUIRE,
    //REL100_PREFERED, //TODO
#define REL100_REQUIRE          AmSipDialog::REL100_REQUIRE
    REL100_IGNORED,
#define REL100_IGNORED          AmSipDialog::REL100_IGNORED
    REL100_MAX
#define REL100_MAX              AmSipDialog::REL100_MAX
  };
  enum provisional_100rel reliable_1xx;

#if 0
  int rseq;          // RSeq for next request (NOTE: keep it signed!)
#else
  unsigned rseq;          // RSeq for next request (NOTE: keep it signed!)
  bool rseq_confirmed;    // latest RSeq is confirmed
  unsigned rseq_1st;      // value of first RSeq (init value)
#endif
  unsigned int cseq; // Local CSeq for next request
  bool r_cseq_i;
  unsigned int r_cseq; // last remote CSeq  

  AmSipDialog(AmSipDialogEventHandler* h=0);
  ~AmSipDialog();

  /** @return whether UAC transaction is pending */
  bool   getUACTransPending();

  /** @return whether INVITE transaction is pending */
  bool   getUACInvTransPending();
  int    getStatus() { return status; }
  void   setStatus(int new_status);

  string getContactHdr();

  /** 
   * Computes, set and return the outbound interface
   * based on remote_uri, next_hop_ip, outbound_proxy, route.
   */
  int getOutboundIf();

  /**
   * Resets outbound_interface to it default value (-1).
   */
  void resetOutboundIf();


  /** update Status from locally originated request (e.g. INVITE) */
  void updateStatusFromLocalRequest(const AmSipRequest& req);

  void updateStatus(const AmSipRequest& req);
  void updateStatus(const AmSipReply& reply);

  void uasTimeout(AmSipTimeoutEvent* to_ev);
    
  /** @return 0 on success */
  int reply(const AmSipRequest& req,
	    unsigned int  code, 
	    const string& reason,
	    const string& content_type = "",
	    const string& body = "",
	    const string& hdrs = "",
	    int flags = 0);

  /** @return 0 on success */
  int sendRequest(const string& method, 
		  const string& content_type = "",
		  const string& body = "",
		  const string& hdrs = "",
		  int flags = 0);

  /** @return 0 on success */
  int send_200_ack(const AmSipTransaction& t,
		   const string& content_type = "",
		   const string& body = "",
		   const string& hdrs = "",
		   int flags = 0);
    
  /** @return 0 on success */
  int bye(const string& hdrs = "", int flags = 0);

  /** @return 0 on success */
  int cancel();

  /** @return 0 on success */
  int prack(const AmSipReply &reply1xx,
            const string &cont_type, 
            const string &body, 
            const string &hdrs);

  /** @return 0 on success */
  int update(const string &cont_type, 
            const string &body, 
            const string &hdrs);

  /** @return 0 on success */
  int reinvite(const string& hdrs,  
	       const string& content_type,
	       const string& body,
	       int flags = 0);

  /** @return 0 on success */
  int invite(const string& hdrs,  
	     const string& content_type,
	     const string& body);

  /** @return 0 on success */
  int refer(const string& refer_to,
	    int expires = -1);

  /** @return 0 on success */
  int transfer(const string& target);
  int drop();

  /**
   * @return the method of the corresponding uac request
   */
  string get_uac_trans_method(unsigned int cseq);

  AmSipTransaction* get_uac_trans(unsigned int cseq);

  /**
   * This method should only be used to send responses
   * to requests which are not referenced by any dialog.
   *
   * WARNING: If the request has already been referenced 
   * (see uas_trans, pending_invites), this method cannot 
   * mark the request as replied, thus leaving it
   * in the pending state forever.
   */
  static int reply_error(const AmSipRequest& req,
			 unsigned int  code,
			 const string& reason,
			 const string& hdrs = "",
			 const string& next_hop_ip = "",
			 unsigned short next_hop_port = 5060,
			 int outbound_interface = -1);
};


#endif

/** EMACS **
 * Local variables:
 * mode: c++
 * c-basic-offset: 2
 * End:
 */