/*
 * Copyright (C) 2008 iptego 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. 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
 */
#include "ModDlg.h"
#include "log.h"
#include "AmUtils.h"

#include "DSMSession.h"
#include "AmSession.h"
#include "AmB2BSession.h"
#include <string.h>
#include "AmSipHeaders.h"

#include "AmUAC.h"
#include "ampi/UACAuthAPI.h"

SC_EXPORT(MOD_CLS_NAME);


MOD_ACTIONEXPORT_BEGIN(MOD_CLS_NAME) {

  DEF_CMD("dlg.reply", DLGReplyAction);
  DEF_CMD("dlg.replyRequest", DLGReplyRequestAction);
  DEF_CMD("dlg.acceptInvite", DLGAcceptInviteAction);
  DEF_CMD("dlg.bye", DLGByeAction);
  DEF_CMD("dlg.connectCalleeRelayed", DLGConnectCalleeRelayedAction);
  DEF_CMD("dlg.dialout", DLGDialoutAction);

  DEF_CMD("dlg.getRequestBody", DLGGetRequestBodyAction)
  DEF_CMD("dlg.getReplyBody", DLGGetReplyBodyAction)

  DEF_CMD("dlg.getOtherId", DLGGetOtherIdAction)
  DEF_CMD("dlg.getRtpRelayMode", DLGGetRtpRelayModeAction)

  DEF_CMD("dlg.refer", DLGReferAction);
  DEF_CMD("dlg.info", DLGInfoAction);
  DEF_CMD("dlg.relayError", DLGB2BRelayErrorAction);

  DEF_CMD("dlg.addReplyBodyPart", DLGAddReplyBodyPartAction);
  DEF_CMD("dlg.deleteReplyBodyPart", DLGDeleteReplyBodyPartAction);

} MOD_ACTIONEXPORT_END;

//MOD_CONDITIONEXPORT_NONE(MOD_CLS_NAME);


MOD_CONDITIONEXPORT_BEGIN(MOD_CLS_NAME) {
  if (cmd == "dlg.replyHasContentType")
    return new DLGReplyHasContentTypeCondition(params, false);

  if (cmd == "dlg.requestHasContentType")
    return new DLGRequestHasContentTypeCondition(params, false);

} MOD_CONDITIONEXPORT_END;

bool DLGModule::onInvite(const AmSipRequest& req, DSMSession* sess) {
  // save inivital invite to last_req 
  // todo: save this in avar
 sess->last_req.reset(new AmSipRequest(req));
 return true;
}

string replaceLineEnds(string input)
{
  string result;
  size_t last = 0;
  size_t pos;
  while ((pos = input.find("\\r\\n", last)) != string::npos) {
    result += input.substr(last, pos-last);
    result += "\r\n";
    last = pos + 4;
  }
  if (!input.substr(last).empty()) {
    result += input.substr(last);
    result += "\r\n";
  }
  return result;
}

// todo: convert errors to exceptions
void replyRequest(DSMSession* sc_sess, AmSession* sess, 
		  EventParamT* event_params,
		  const string& par1, const string& par2,
		  const AmSipRequest& req) {
  string code = resolveVars(par1, sess, sc_sess, event_params);
  string reason = resolveVars(par2, sess, sc_sess, event_params);
  string hdrs = replaceLineEnds(resolveVars("$dlg.reply.hdrs", sess, sc_sess, event_params));
  unsigned int code_i;
  if (str2i(code, code_i)) {
    ERROR("decoding reply code '%s'\n", code.c_str());
    sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
    return;
  }

  DBG("replying with %i %s, hdrs='%s'\n", code_i, reason.c_str(), hdrs.c_str());

  if (sess->dlg->reply(req, code_i, reason, NULL, hdrs)) {
    sc_sess->SET_ERRNO(DSM_ERRNO_GENERAL);
    sc_sess->SET_STRERROR("error sending reply");
  } else
    sc_sess->CLR_ERRNO;
}

CONST_ACTION_2P(DLGReplyAction, ',', true);
EXEC_ACTION_START(DLGReplyAction) {

  if (!sc_sess->last_req.get()) {
    ERROR("no last request to reply\n");
    sc_sess->SET_ERRNO(DSM_ERRNO_GENERAL);
    sc_sess->SET_STRERROR("no last request to reply");
    return false;
  }

  replyRequest(sc_sess, sess, event_params, par1, par2, *sc_sess->last_req.get());
} EXEC_ACTION_END;

// todo (?) move replyRequest to core module (?)
CONST_ACTION_2P(DLGReplyRequestAction, ',', true);
EXEC_ACTION_START(DLGReplyRequestAction) {
  DSMSipRequest* sip_req;

  AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REQUEST);
  if (it == sc_sess->avar.end() ||
      !isArgAObject(it->second) || 
      !(sip_req = dynamic_cast<DSMSipRequest*>(it->second.asObject()))) {
    throw DSMException("dlg", "cause", "no request");
  }
    
  replyRequest(sc_sess, sess, event_params, par1, par2, *sip_req->req);
} EXEC_ACTION_END;

CONST_ACTION_2P(DLGAcceptInviteAction, ',', true);
EXEC_ACTION_START(DLGAcceptInviteAction) {
  // defaults to 200 OK
  unsigned int code_i=200;
  string reason = "OK";
  string code = resolveVars(par1, sess, sc_sess, event_params);
  string hdrs = replaceLineEnds(resolveVars("$dlg.reply.hdrs", sess, sc_sess, event_params));

  if (code.length()) {
    reason = resolveVars(par2, sess, sc_sess, event_params);
    if (str2i(code, code_i)) {
      ERROR("decoding reply code '%s'\n", code.c_str());
      sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
      sc_sess->SET_STRERROR("decoding reply code '"+
			    code+"%s'\n");
      return false;
    }
  }

  DBG("replying with %i %s, hdrs='%s'\n", code_i, reason.c_str(), hdrs.c_str());

  if (!sc_sess->last_req.get()) {
    ERROR("no last request to reply\n");
    sc_sess->SET_ERRNO(DSM_ERRNO_GENERAL);
    sc_sess->SET_STRERROR("no last request to reply");
    return false;
  }

  try {
    AmMimeBody sdp_body;
    if(sess->dlg->reply(*sc_sess->last_req.get(),code_i, reason,
		       sdp_body.addPart(SIP_APPLICATION_SDP), hdrs) != 0)
      throw AmSession::Exception(500,"could not send response");

  }catch(const AmSession::Exception& e){

    ERROR("%i %s\n",e.code,e.reason.c_str());
    sess->setStopped();
    sess->dlg->reply(*sc_sess->last_req.get(),e.code,e.reason);

    sc_sess->SET_ERRNO(DSM_ERRNO_DLG);
    sc_sess->SET_STRERROR("Error accepting call: "+ int2str(e.code) + " "+ e.reason);
  }
} EXEC_ACTION_END;

EXEC_ACTION_START(DLGByeAction) {
  string hdrs = resolveVars(arg, sess, sc_sess, event_params);

  if (sess->dlg->bye(hdrs)) {
    sc_sess->SET_ERRNO(DSM_ERRNO_GENERAL);
    sc_sess->SET_STRERROR("Error sending bye");
  } else {
    sc_sess->SET_ERRNO(DSM_ERRNO_OK);
  }
} EXEC_ACTION_END;


CONST_ACTION_2P(DLGConnectCalleeRelayedAction,',', false);
EXEC_ACTION_START(DLGConnectCalleeRelayedAction) {  
  string remote_party = resolveVars(par1, sess, sc_sess, event_params);
  string remote_uri = resolveVars(par2, sess, sc_sess, event_params);

  // if (sc_sess->last_req.get()) {
  //   sc_sess->B2BaddReceivedRequest(*sc_sess->last_req.get());
  // } else {
  //   WARN("internal error: initial INVITE request missing.\n");
  // }
  // AmB2BSession* b2b_sess = dynamic_cast<AmB2BSession*>(sess);
  // if (b2b_sess) 
  //   b2b_sess->set_sip_relay_only(true);
  // else 
  //   ERROR("getting B2B session.\n");

  sc_sess->B2BconnectCallee(remote_party, remote_uri, true);
} EXEC_ACTION_END;

EXEC_ACTION_START(DLGDialoutAction) {  
  string arrayname = resolveVars(arg, sess, sc_sess, event_params);

#define GET_VARIABLE_MANDATORY(varname_suffix, outvar)			\
  it = sc_sess->var.find(arrayname+varname_suffix); \
  if (it == sc_sess->var.end()) {					\
    WARN("%s", std::string("need " + arrayname + varname_suffix " set for dlg.dialoutSimple("+arrayname+")").c_str()); \
    sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);				\
    return false;							\
  }									\
  outvar = it->second;

#define GET_VARIABLE_OPTIONAL(varname_suffix, outvar) \
  it = sc_sess->var.find(arrayname+varname_suffix);  \
  if (it != sc_sess->var.end())		      \
    outvar = it->second;

  map<string, string>::iterator it; 

  string v_from;
  GET_VARIABLE_MANDATORY("_caller", v_from);
  string v_to;
  GET_VARIABLE_MANDATORY("_callee", v_to);
  string v_domain;
  GET_VARIABLE_MANDATORY("_domain", v_domain);
  string app_name;
  GET_VARIABLE_MANDATORY("_app", app_name);

  string user = v_from;
  string r_uri = "sip:"+v_to+"@"+v_domain;

  GET_VARIABLE_OPTIONAL("_r_uri", r_uri);

  string from = "<sip:"+v_from+"@"+v_domain+">"; 
  GET_VARIABLE_OPTIONAL("_from", from);

  string from_uri = "sip:"+v_from+"@"+v_domain; 
  GET_VARIABLE_OPTIONAL("_from_uri", from_uri);

  string to = "<sip:"+v_to+"@"+v_domain+">";
  GET_VARIABLE_OPTIONAL("_to", to);

  string auth_user; 
  GET_VARIABLE_OPTIONAL("_auth_user", auth_user);

  string auth_pwd; 
  GET_VARIABLE_OPTIONAL("_auth_pwd", auth_pwd);
   
  string ltag; 
  GET_VARIABLE_OPTIONAL("_ltag", ltag);

  string hdrs; 
  GET_VARIABLE_OPTIONAL("_hdrs", hdrs);
  
  if (hdrs.length()) {
    size_t crpos;
    while ((crpos=hdrs.find("\\r\\n")) != string::npos) {
      hdrs.replace(crpos, 4, "\r\n");
    }
  }

#undef GET_VARIABLE_MANDATORY
#undef GET_VARIABLE_OPTIONAL

  DBG("placing UAC call: user <%s>, app <%s>, ruri <%s>, from <%s> "
      "from_uri <%s>, to <%s>, ltag <%s>, hdrs <%s>, auth_user <%s>, auth_pwd <not shown>\n",
      user.c_str(), app_name.c_str(), r_uri.c_str(), from.c_str(),
      from_uri.c_str(), to.c_str(), ltag.c_str(), hdrs.c_str(), auth_user.c_str());

  AmArg* sess_params = new AmArg();
  bool has_auth = false;
  if (!auth_user.empty() && !auth_pwd.empty()) {
    AmArg auth_param;
    auth_param.setBorrowedPointer(new UACAuthCred("", auth_user,auth_pwd));
    sess_params->push(auth_param);
    has_auth = true;
  }
  AmArg var_struct;
  string varprefix = arrayname+"_var.";
  bool has_vars = false;
  map<string, string>::iterator lb = sc_sess->var.lower_bound(varprefix);
  while (lb != sc_sess->var.end()) {
    if ((lb->first.length() < varprefix.length()) ||
	strncmp(lb->first.c_str(), varprefix.c_str(),varprefix.length()))
      break;
    string varname = lb->first.substr(varprefix.length());
    if (!has_auth) // sess_params is variable struct
      (*sess_params)[varname] = lb->second;
    else // variable struct is in sess_params array
      var_struct[varname] = lb->second;

    lb++;
    has_vars = true;
  }

  if (has_vars && has_auth)
      sess_params->push(var_struct);
 
  DBG("sess_params: '%s'\n", AmArg::print(*sess_params).c_str());

  string new_sess_tag = AmUAC::dialout(user, app_name, r_uri, from, from_uri, to, ltag, hdrs, sess_params);

  if (!new_sess_tag.empty()) {
    sc_sess->var[arrayname + "_ltag"] = new_sess_tag;
  } else {
    sc_sess->var[arrayname + "_ltag"] = "";
    sc_sess->SET_ERRNO(DSM_ERRNO_GENERAL);
  }

} EXEC_ACTION_END;

MATCH_CONDITION_START(DLGReplyHasContentTypeCondition) {
  AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REPLY);
  if (it == sc_sess->avar.end()) {
    ERROR("DSM script error: dlg.replyHasContentType condition used for "
	  "other event than sipReply event\n");
    return false;
  }

  DSMSipReply* dsm_reply = NULL;
  if (!isArgAObject(sc_sess->avar[DSM_AVAR_REPLY]) ||
      (NULL ==
       (dsm_reply = dynamic_cast<DSMSipReply*>(sc_sess->avar[DSM_AVAR_REPLY].asObject())))) {
    ERROR("internal: DSM could not get DSMSipReply\n");
    return false;
  }

  bool res = dsm_reply->reply->body.hasContentType(arg);

  DBG("checking for content_type '%s': %s\n", arg.c_str(), res?"has it":"doesn't have it");
  return res;
} MATCH_CONDITION_END;

MATCH_CONDITION_START(DLGRequestHasContentTypeCondition) {
  AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REQUEST);
  if (it == sc_sess->avar.end()) {
    ERROR("DSM script error: dlg.requestHasContentType condition used for "
	  "other event than sipRequest event\n");
    return false;
  }

  DSMSipRequest* dsm_req = NULL;
  if (!isArgAObject(sc_sess->avar[DSM_AVAR_REQUEST]) ||
      (NULL ==
       (dsm_req = dynamic_cast<DSMSipRequest*>(sc_sess->avar[DSM_AVAR_REQUEST].asObject())))) {
    ERROR("internal: DSM could not get DSMSipRequest\n");
    return false;
  }

  bool res = dsm_req->req->body.hasContentType(arg);

  DBG("checking for content_type '%s': %s\n", arg.c_str(), res?"has it":"doesn't have it");
  return res;
} MATCH_CONDITION_END;

CONST_ACTION_2P(DLGGetRequestBodyAction, ',', false);
EXEC_ACTION_START(DLGGetRequestBodyAction) {
  DSMSipRequest* sip_req;

  AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REQUEST);
  if (it == sc_sess->avar.end() ||
      !isArgAObject(it->second) ||
      !(sip_req = dynamic_cast<DSMSipRequest*>(it->second.asObject()))) {
    throw DSMException("dlg", "cause", "no request");
  }

  string content_type = resolveVars(par1, sess, sc_sess, event_params);
  string dstvar = resolveVars(par2, sess, sc_sess, event_params);

  const AmMimeBody* msg_body = sip_req->req->body.hasContentType(content_type);
  if (NULL == msg_body) {
    DBG("body with content_type %s not found\n", content_type.c_str());
    sc_sess->var.erase(dstvar);
  } else {
    sc_sess->var[dstvar] = string((const char*)msg_body->getPayload());
    DBG("set $%s='%s'\n", dstvar.c_str(), sc_sess->var[dstvar].c_str());
  }
} EXEC_ACTION_END;

CONST_ACTION_2P(DLGGetReplyBodyAction, ',', false);
EXEC_ACTION_START(DLGGetReplyBodyAction) {
  DSMSipReply* sip_req;

  AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REPLY);
  if (it == sc_sess->avar.end() ||
      !isArgAObject(it->second) ||
      !(sip_req = dynamic_cast<DSMSipReply*>(it->second.asObject()))) {
    throw DSMException("dlg", "cause", "no reply");
  }

  string content_type = resolveVars(par1, sess, sc_sess, event_params);
  string dstvar = resolveVars(par2, sess, sc_sess, event_params);

  const AmMimeBody* msg_body = sip_req->reply->body.hasContentType(content_type);
  if (NULL == msg_body) {
    DBG("body with content_type %s not found\n", content_type.c_str());
    sc_sess->var.erase(dstvar);
  } else {
    sc_sess->var[dstvar] = string((const char*)msg_body->getPayload());
    DBG("set $%s='%s'\n", dstvar.c_str(), sc_sess->var[dstvar].c_str());
  }
} EXEC_ACTION_END;

EXEC_ACTION_START(DLGGetOtherIdAction) {
  string varname = arg;
  AmB2BSession* b2b_sess = dynamic_cast<AmB2BSession*>(sess);
  if (NULL == b2b_sess) {
    DBG("script writer error: dlg.getOtherId used without B2B session object.\n");
    EXEC_ACTION_STOP;
  }

  if (varname.size() && varname[0] == '$')
    varname.erase(0, 1);
  sc_sess->var[varname] = b2b_sess->getOtherId();
} EXEC_ACTION_END;

EXEC_ACTION_START(DLGGetRtpRelayModeAction) {
  string varname = arg;
  AmB2BSession* b2b_sess = dynamic_cast<AmB2BSession*>(sess);
  if (NULL == b2b_sess) {
    DBG("script writer error: dlg.getOtherId used without B2B session object.\n");
    EXEC_ACTION_STOP;
  }

  if (varname.size() && varname[0] == '$')
    varname.erase(0, 1);
  switch (b2b_sess->getRtpRelayMode()) {
  case AmB2BSession::RTP_Direct: sc_sess->var[varname] = "RTP_Direct"; break;
  case AmB2BSession::RTP_Relay: sc_sess->var[varname] = "RTP_Relay"; break;
  case AmB2BSession::RTP_Transcoding: sc_sess->var[varname] = "RTP_Transcoding"; break;
  default: sc_sess->var[varname] = "Unknown"; break;
  }

  DBG("get RTP relay mode: %s='%s'\n", varname.c_str(), sc_sess->var[varname].c_str());
} EXEC_ACTION_END;

CONST_ACTION_2P(DLGReferAction, ',', true);
EXEC_ACTION_START(DLGReferAction) {

  AmSession* b2b_sess = dynamic_cast<AmSession*>(sess);
  if (NULL == b2b_sess) {
    throw DSMException("sbc", "type", "param", "cause",
		       "dlg.refer used on non-session");
  }

  string refer_to = resolveVars(par1, sess, sc_sess, event_params);
  string expires_s = resolveVars(par2, sess, sc_sess, event_params);

  int expires = -1;
  if (!expires_s.empty()) {
    if (!str2int(expires_s, expires)) {
      throw DSMException("sbc", "type", "param", "cause",
			 "expires "+expires_s+" not valid");
    }
  }

  if (NULL == b2b_sess->dlg) {
      throw DSMException("sbc", "type", "param", "cause",
			 "call doesn't have SIP dialog (OOPS!)");
  }

  if (b2b_sess->dlg->refer(refer_to, expires)) {
    sc_sess->SET_ERRNO(DSM_ERRNO_DLG);
    sc_sess->SET_STRERROR("sending REFER failed");
  } else {
    sc_sess->CLR_ERRNO;
  }
} EXEC_ACTION_END;

CONST_ACTION_2P(DLGInfoAction, ',', true);
EXEC_ACTION_START(DLGInfoAction) {

  AmSession* b2b_sess = dynamic_cast<AmSession*>(sess);
  if (NULL == b2b_sess) {
    throw DSMException("sbc", "type", "param", "cause",
		       "dlg.info used on non-session");
  }

  string content_type = resolveVars(par1, sess, sc_sess, event_params);
  string body_str = resolveVars(par2, sess, sc_sess, event_params);

  if (NULL == b2b_sess->dlg) {
    throw DSMException("sbc", "type", "param", "cause",
		       "call doesn't have SIP dialog (OOPS!)");
  }

  string body_crlf = body_str;
  AmMimeBody *body = new AmMimeBody();
  if (!content_type.empty()) {
    DBG("body_crlf is '%s'\n", body_crlf.c_str());
    while (true) {
      size_t p = body_crlf.find("\\r\\n");
      if (p==string::npos)
	break;
      body_crlf.replace(p, 4, "\r\n");
    }
    DBG("-> body_crlf is '%s'\n", body_crlf.c_str());
    if (body->parse(content_type,
		    reinterpret_cast<const unsigned char*>(body_crlf.c_str()),
		    body_crlf.length())) {
      throw DSMException("sbc", "type", "param", "cause",
			 "parsing of INFO body failed");
    }
  }

  if (b2b_sess->dlg->info("", body)) {
    sc_sess->SET_ERRNO(DSM_ERRNO_DLG);
    sc_sess->SET_STRERROR("sending INFO failed");
  } else {
    sc_sess->CLR_ERRNO;
  }

} EXEC_ACTION_END;

#define GET_B2B_SESSION(action)						\
  AmB2BSession* b2b_sess = dynamic_cast<AmB2BSession*>(sess);		\
  if (NULL == b2b_sess) {						\
    throw DSMException("sbc", "type", "param", "cause",			\
		       #action " used on non-b2b-session");		\
  }

CONST_ACTION_2P(DLGB2BRelayErrorAction, ',', false);
EXEC_ACTION_START(DLGB2BRelayErrorAction) {
  DSMSipRequest* sip_req;

  AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REQUEST);
  if (it == sc_sess->avar.end() ||
      !isArgAObject(it->second) || 
      !(sip_req = dynamic_cast<DSMSipRequest*>(it->second.asObject()))) {
    throw DSMException("dlg", "cause", "no request");
  }
  GET_B2B_SESSION(dlg.refer);

  string code = resolveVars(par1, sess, sc_sess, event_params);
  string reason = resolveVars(par2, sess, sc_sess, event_params);
  unsigned int code_i;
  if (str2i(code, code_i)) {
    ERROR("decoding reply code '%s'\n", code.c_str());
    sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
    EXEC_ACTION_STOP;
  }

  b2b_sess->relayError(sip_req->req->method, sip_req->req->cseq, true, code_i, reason.c_str());
} EXEC_ACTION_END;

CONST_ACTION_2P(DLGAddReplyBodyPartAction, ',', false);
EXEC_ACTION_START(DLGAddReplyBodyPartAction) {
  DSMMutableSipReply* sip_reply;

  AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REPLY);
  if (it == sc_sess->avar.end() ||
      !isArgAObject(it->second) ||
      !(sip_reply = dynamic_cast<DSMMutableSipReply*>(it->second.asObject()))) {
    throw DSMException("dlg", "cause", "no reply");
  }

  string content_type = resolveVars(par1, sess, sc_sess, event_params);
  string body_part = resolveVars(par2, sess, sc_sess, event_params);

  AmMimeBody* new_part;

  new_part = sip_reply->mutable_reply->body.addPart(content_type);
  new_part->setPayload((const unsigned char*)body_part.c_str(),
		       body_part.length());
  DBG("added to reply body part %s='%s'\n",
      content_type.c_str(), body_part.c_str());
} EXEC_ACTION_END;

EXEC_ACTION_START(DLGDeleteReplyBodyPartAction) {
  DSMMutableSipReply* sip_reply;

  AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REPLY);
  if (it == sc_sess->avar.end() ||
      !isArgAObject(it->second) ||
      !(sip_reply = dynamic_cast<DSMMutableSipReply*>(it->second.asObject()))) {
    throw DSMException("dlg", "cause", "no reply");
  }

  if (sip_reply->mutable_reply->body.deletePart(arg)) {
    DBG("failed to delete reply body part '%s'\n", arg.c_str());
  } else {
    DBG("deleted reply body part '%s'\n", arg.c_str());
  }
} EXEC_ACTION_END;