#include "AmSipDialog.h"
#include "AmConfig.h"
#include "AmSession.h"
#include "AmUtils.h"
#include "AmServer.h"

char* AmSipDialog::status2str[4]  = { 	
	"Disconnected",
	"Pending",
	"Connected",
	"Disconnecting" };


AmSipDialog::~AmSipDialog()
{
    DBG("callid = %s\n",callid.c_str());
    DBG("uac_trans.size() = %u\n",(unsigned int)uac_trans.size());
    if(uac_trans.size()){
	for(TransMap::iterator it = uac_trans.begin();
	    it != uac_trans.end(); it++){
	    
	    DBG("    cseq = %i; method = %s\n",it->first,it->second.method.c_str());
	}
    }
    DBG("uas_trans.size() = %u\n",(unsigned int)uas_trans.size());
    if(uas_trans.size()){
	for(TransMap::iterator it = uas_trans.begin();
	    it != uas_trans.end(); it++){
	    
	    DBG("    cseq = %i; method = %s\n",it->first,it->second.method.c_str());
	}
    }
}

void AmSipDialog::updateStatus(const AmSipRequest& req)
{
    if(uas_trans.find(req.cseq) == uas_trans.end())
	uas_trans[req.cseq] = AmSipTransaction(req.method,req.cseq);

    remote_uri = req.from_uri;
    sip_ip       = req.dstip;
    sip_port     = req.port;

    if(callid.empty()){
	callid       = req.callid;
	remote_tag   = req.from_tag;
	user         = req.user;
	domain       = req.domain;
	local_uri    = req.r_uri;
	remote_party = req.from;
	local_party  = req.to;

	setRoute(req.route);
	next_hop   = req.next_hop;
    }
}

/**
 *
 * update dialog status from UAC Request that we send (e.g. INVITE)
 */
void AmSipDialog::updateStatusFromLocalRequest(const AmSipRequest& req)
{
    remote_uri = req.r_uri;
    if(callid.empty()){
      DBG("dialog callid is empty, updating from UACRequest\n");
	callid       = req.callid;
	local_tag    = req.from_tag;
	user         = req.user;
	domain       = req.domain;
	local_uri    = req.from_uri;
	remote_party = req.to;
	local_party  = req.from;

// 	sip_ip       = AmConfig::req.dstip;
// 	sip_port     = req.port;

// 	setRoute(req.route);
	next_hop   = req.next_hop;
    }
}

int AmSipDialog::updateStatusReply(const AmSipRequest& req, unsigned int code)
{
    TransMap::iterator t_it = uas_trans.find(req.cseq);
    if(t_it == uas_trans.end()){
	ERROR("could not find any transaction matching request\n");
	ERROR("method=%s; callid=%s; local_tag=%s; remote_tag=%s; cseq=%i\n",
	      req.method.c_str(),callid.c_str(),local_tag.c_str(),
	      remote_tag.c_str(),req.cseq);
	return -1;
    }
    DBG("reply: transaction found!\n");
    
    AmSipTransaction& t = t_it->second;

    //t->reply_code = code;
    switch(status){

    case Disconnected:
    case Pending:
	if(t.method == "INVITE"){
	
	    if(req.method == "CANCEL"){
		
		// wait for somebody
		// to answer 487
		return 0;
	    }

	    if(code < 200)
		status = Pending;
	    else if(code < 300)
		status = Connected;
	    else
		status = Disconnected;
	}
	
	break;
    case Connected:
    case Disconnecting:
	if(t.method == "BYE"){
	    
	    if((code < 300) && (code >= 200))
		status = Disconnected;
	}
	break;
    }

    if(code >= 200){
	DBG("req.method = %s; t.method = %s\n",
	    req.method.c_str(),t.method.c_str());

	uas_trans.erase(t_it);
    }

    return 0;
}

void AmSipDialog::updateStatus(const AmSipReply& reply)
{
    TransMap::iterator t_it = uac_trans.find(reply.cseq);
    if(t_it == uac_trans.end()){
	ERROR("could not find any transaction matching reply\n");
	return;
    }
    DBG("updateStatus(reply): transaction found!\n");

    AmSipTransaction& t = t_it->second;

    //t->reply_code = reply.code;

    if(remote_tag.empty() && !reply.remote_tag.empty())
	remote_tag = reply.remote_tag;

    // allow route overwritting
    if(status < Connected) {

	if(!reply.route.empty())
	    setRoute(reply.route);

	next_hop = reply.next_hop;
    }

    remote_uri = reply.next_request_uri;

    switch(status){
    case Disconnecting:
	if( t.method == "INVITE" ){

	    if(reply.code == 487){
		// CANCEL accepted
		status = Disconnected;
	    }
	    else {
		// CANCEL rejected
		sendRequest("BYE");
	    }
	}
	break;

    case Pending:
    case Disconnected:
	if(t.method != "BYE"){

	    if(reply.code < 200)
		status = Pending;
	    else if(reply.code >= 300)
		status = Disconnected;
	    else
		status = Connected;
	}
	break;
    default:
	break;
    }

    if(reply.code >= 200)
	uac_trans.erase(t_it);
}

string AmSipDialog::getContactHdr()
{
    if(!contact_uri.empty())
	 return contact_uri;

    contact_uri = "Contact: <sip:";
    
    if(user.empty() || !AmConfig::PrefixSep.empty())
  	contact_uri += CONTACT_USER_PREFIX;
    
    if(!AmConfig::PrefixSep.empty())
  	contact_uri += AmConfig::PrefixSep;
    
    if(!user.empty())
  	contact_uri += user;
    
    contact_uri += "@";
    
    if(sip_ip.empty())
	contact_uri += "!!"; // Ser will replace that...
    else {
#ifdef SUPPORT_IPV6
	if(sip_ip.find('.') != string::npos)
	    contact_uri += sip_ip;
	else
	    contact_uri += "[" + sip_ip + "]";
#else
	contact_uri += sip_ip;
#endif
    }

    if(!sip_port.empty())
	contact_uri += ":" + sip_port;

    contact_uri += ">\n";
    
    return contact_uri;
}

int AmSipDialog::reply(const AmSipRequest& req,
		       unsigned int  code,
		       const string& reason,
		       const string& content_type,
		       const string& body,
		       const string& hdrs)
{
    string m_hdrs = hdrs;

    if(hdl)
	hdl->onSendReply(req,code,reason,
			 content_type,body,m_hdrs);

    string reply_sock = "/tmp/" + AmSession::getNewId();
    string code_str = int2str(code);

    string msg = 
	":t_reply:" + reply_sock + "\n" +
	code_str + "\n" + 
	reason + "\n" + 
	req.key + "\n" + 
 	local_tag + "\n";

    if(!m_hdrs.empty())
	msg += m_hdrs;

    msg += getContactHdr();

    if(!body.empty())
	msg += "Content-Type: " + content_type + "\n";

    msg += ".\n";

    if(!body.empty())
	msg += body;

    msg += ".\n\n";

    if(updateStatusReply(req,code))
	return -1;

    return AmServer::send_msg(msg,reply_sock, 500);
}

/* static */
int AmSipDialog::reply_error(const AmSipRequest& req, unsigned int code, 
			     const string& reason, const string& hdrs)
{
    string reply_sock = "/tmp/" + AmSession::getNewId();
    string code_str = int2str(code);

    string msg = 
      ":t_reply:" + reply_sock + "\n" +
      code_str + "\n" + 
      reason + "\n" + 
      req.key + "\n" + 
      AmSession::getNewId() + "\n";
      if(!hdrs.empty())
	msg += hdrs;
      else 
	msg +=  "\n";

      msg += ".\n.\n\n";
    
    return AmServer::send_msg(msg,reply_sock, 500);
}

int AmSipDialog::bye()
{
    switch(status){
    case Disconnecting:
    case Connected:
	status = Disconnected;
	return sendRequest("BYE");
    case Pending:
	status = Disconnecting;
	if(getUACTransPending())
	    return cancel();
	else {
	    // missing AmSipRequest to be able
	    // to send the reply on behalf of the app.
	    ERROR("bye(): Dialog should have"
		  " been terminated by the app !!!\n");
	}
    default:
	if(getUACTransPending())
	    return cancel();
	else {
	    DBG("bye(): we are not connected "
		"(status=%i). do nothing!\n",status);
	}
	return 0;
    }	
}

int AmSipDialog::reinvite(const string& hdrs,  
			  const string& content_type,
			  const string& body)
{
    switch(status){
    case Connected:
	return sendRequest("INVITE", content_type, body, hdrs);
    case Disconnecting:
    case Pending:
      DBG("reinvite(): we are not yet connected."
	    "(status=%i). do nothing!\n",status);

      return 0;
    default:
      DBG("reinvite(): we are not connected "
	  "(status=%i). do nothing!\n",status);
      return 0;
    }	
}

int AmSipDialog::invite(const string& hdrs,  
			const string& content_type,
			const string& body)
{
    switch(status){
    case Disconnected: {
      int res = sendRequest("INVITE", content_type, body, hdrs);
      status = Pending;
      return res;
    }; break;

    case Disconnecting:
    case Connected:
    case Pending:
    default:
      DBG("invite(): we are already connected."
	    "(status=%i). do nothing!\n",status);

      return 0;
    }	
}

int AmSipDialog::update(const string& hdrs)
{
    switch(status){
    case Connected:
	return sendRequest("UPDATE", "", "", hdrs);
    case Disconnecting:
    case Pending:
      DBG("update(): we are not yet connected."
	    "(status=%i). do nothing!\n",status);

      return 0;
    default:
      DBG("update(): we are not connected "
	  "(status=%i). do nothing!\n",status);
      return 0;
    }	
}

int AmSipDialog::cancel()
{
    int cancel_cseq = -1;
    TransMap::reverse_iterator t;

    for(t = uac_trans.rbegin();
	t != uac_trans.rend(); t++) {

	if(t->second.method == "INVITE"){
	    cancel_cseq = t->second.cseq;
	    break;
	}
    }
    
    if(t == uac_trans.rend()){
	ERROR("could not find INVITE transaction to cancel\n");
	return -1;
    }
    
    string reply_sock = "/tmp/" + AmSession::getNewId();
    string msg = ":t_uac_cancel:" + reply_sock + "\n"
	+ callid + "\n"
 	+ int2str(cancel_cseq) + "\n\n";

    return AmServer::send_msg(msg, reply_sock, 50000);
}

int AmSipDialog::sendRequest(const string& method, 
			     const string& content_type,
			     const string& body,
			     const string& hdrs)
{
    string msg,ser_cmd;
    string m_hdrs = hdrs;

    if(hdl)
	hdl->onSendRequest(method,content_type,body,m_hdrs);

    msg = ":t_uac_dlg:" + AmConfig::ReplySocketName + "\n"
	+ method + "\n"
	+ remote_uri + "\n";
    
    if(next_hop.empty())
	msg += ".";
    else
	msg += next_hop;
    
    msg += "\n";
    
    msg += "From: " + local_party;
    if(!local_tag.empty())
	msg += ";tag=" + local_tag;
    
    msg += "\n";
    
    msg += "To: " + remote_party;
    if(!remote_tag.empty()) 
	msg += ";tag=" + remote_tag;
    
    msg += "\n";
    
    msg += "CSeq: " + int2str(cseq) + " " + method + "\n"
	+ "Call-ID: " + callid + "\n";
    
    msg += getContactHdr();
    
    if(!hdrs.empty()){
	msg += hdrs;
	if(hdrs[hdrs.length()-1] != '\n')
	    msg += "\n";
    }

    if(!route.empty())
	msg += getRoute();
    
    if(!body.empty())
	msg += "Content-Type: " + content_type + "\n";
    
    msg += ".\n" // EoH
	+ body + ".\n\n";

    if (AmServer::send_msg_replyhandler(msg)) 
	    return -1;
    
    uac_trans[cseq] = AmSipTransaction(method,cseq);

    // increment for next request
    cseq++;
    
    return 0;
}

bool AmSipDialog::match_cancel(const AmSipRequest& cancel_req)
{
    TransMap::iterator t = uas_trans.find(cancel_req.cseq);

    if((t != uas_trans.end()) && (t->second.method == "INVITE"))
	return true;

    return false;
}

string AmSipDialog::get_uac_trans_method(unsigned int cseq)
{
    TransMap::iterator t = uac_trans.find(cseq);

    if (t != uac_trans.end())
      return t->second.method;

    return "";
}

string AmSipDialog::getRoute()
{
    string r_set("");
    for(vector<string>::iterator it = route.begin();
	it != route.end(); it++) {

	r_set += "Route: " + *it + "\n";
    }

    return r_set;
}

void AmSipDialog::setRoute(const string& n_route)
{
    string m_route = n_route;
    if(!m_route.empty() && (m_route.find("Route: ")!=string::npos))
	m_route = m_route.substr(7/*sizeof("Route: ")*/);
    
    route.clear();
    while(!m_route.empty()){
	
	string::size_type comma_pos;
	
	comma_pos = m_route.find(',');
	//route += "Route: " + m_route.substr(0,comma_pos) + "\n";
	route.push_back(m_route.substr(0,comma_pos));
	
	if(comma_pos != string::npos)
	    m_route = m_route.substr(comma_pos+1);
	else
	    m_route = "";
    }
}