/*
 * Copyright (C) 2008 iptego GmbH
 * Copyright (C) 2010 Stefan Sayer
 *
 * 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 "DSMCoreModule.h"
#include "DSMSession.h"
#include "AmSession.h"
#include "AmSessionContainer.h"
#include "AmUtils.h"
#include "AmEventDispatcher.h"
#include "DSM.h"
#include "AmB2BSession.h"

#include "jsonArg.h"

DSMCoreModule::DSMCoreModule() {
}

DSMAction* DSMCoreModule::getAction(const string& from_str) {
  string cmd;
  string params;
  splitCmd(from_str, cmd, params);

  DEF_CMD("repost", SCRepostAction);
  DEF_CMD("jumpFSM", SCJumpFSMAction);
  DEF_CMD("callFSM", SCCallFSMAction);
  DEF_CMD("returnFSM", SCReturnFSMAction);

  DEF_CMD("throw", SCThrowAction);
  DEF_CMD("throwOnError", SCThrowOnErrorAction);

  DEF_CMD("stop", SCStopAction);

  DEF_CMD("playPrompt", SCPlayPromptAction);
  DEF_CMD("playPromptFront", SCPlayPromptFrontAction);
  DEF_CMD("playPromptLooped", SCPlayPromptLoopedAction);
  DEF_CMD("playFile", SCPlayFileAction);
  DEF_CMD("playFileFront", SCPlayFileFrontAction);
  DEF_CMD("playSilence", SCPlaySilenceAction);
  DEF_CMD("playSilenceFront", SCPlaySilenceFrontAction);
  DEF_CMD("playRingtone", SCPlayRingtoneAction);
  DEF_CMD("recordFile", SCRecordFileAction);
  DEF_CMD("stopRecord", SCStopRecordAction);
  DEF_CMD("getRecordLength", SCGetRecordLengthAction);
  DEF_CMD("getRecordDataSize", SCGetRecordDataSizeAction);
  DEF_CMD("flushPlaylist", SCFlushPlaylistAction);
  DEF_CMD("closePlaylist", SCClosePlaylistAction); // deprecated
  DEF_CMD("setInOutPlaylist", SCSetInOutPlaylistAction);
  DEF_CMD("setInputPlaylist", SCSetInputPlaylistAction);
  DEF_CMD("setOutputPlaylist", SCSetOutputPlaylistAction);
  DEF_CMD("addSeparator", SCAddSeparatorAction);
  DEF_CMD("connectMedia", SCConnectMediaAction);
  DEF_CMD("disconnectMedia", SCDisconnectMediaAction);
  DEF_CMD("enableReceiving", SCEnableReceivingAction);
  DEF_CMD("disableReceiving", SCDisableReceivingAction);
  DEF_CMD("enableForceDTMFReceiving", SCEnableForceDTMFReceiving);
  DEF_CMD("disableForceDTMFReceiving", SCDisableForceDTMFReceiving);
  DEF_CMD("monitorRTPTimeout", SCMonitorRTPTimeoutAction);
  DEF_CMD("mute", SCMuteAction);
  DEF_CMD("unmute", SCUnmuteAction);
  DEF_CMD("enableDTMFDetection", SCEnableDTMFDetection);
  DEF_CMD("disableDTMFDetection", SCDisableDTMFDetection);
  DEF_CMD("sendDTMF", SCSendDTMFAction);
  DEF_CMD("sendDTMFSequence", SCSendDTMFSequenceAction);

  DEF_CMD("set", SCSetAction);
  DEF_CMD("sets", SCSetSAction);
  DEF_CMD("eval", SCEvalAction);
  DEF_CMD("setVar", SCSetVarAction);
  DEF_CMD("var", SCGetVarAction);
  DEF_CMD("param", SCGetParamAction);
  DEF_CMD("append", SCAppendAction);
  DEF_CMD("substr", SCSubStrAction);
  DEF_CMD("inc", SCIncAction);
  DEF_CMD("log", SCLogAction);
  DEF_CMD("logs", SCLogsAction);
  DEF_CMD("dbg", SCDbgAction);
  DEF_CMD("info", SCInfoAction);
  DEF_CMD("warn", SCWarnAction);
  DEF_CMD("error", SCErrorAction);
  DEF_CMD("clear", SCClearAction);
  DEF_CMD("clearStruct", SCClearStructAction);
  DEF_CMD("clearArray", SCClearArrayAction);
  DEF_CMD("size", SCSizeAction);
  DEF_CMD("arrayIndex", SCArrayIndexAction);
  DEF_CMD("logVars", SCLogVarsAction);
  DEF_CMD("logParams", SCLogParamsAction);
  DEF_CMD("logSelects", SCLogSelectsAction);
  DEF_CMD("logAll", SCLogAllAction);

  DEF_CMD("setTimer", SCSetTimerAction);
  DEF_CMD("removeTimer", SCRemoveTimerAction);
  DEF_CMD("removeTimers", SCRemoveTimersAction);

  DEF_CMD("setPrompts", SCSetPromptsAction);

  DEF_CMD("postEvent", SCPostEventAction);

  DEF_CMD("registerEventQueue", SCRegisterEventQueueAction);
  DEF_CMD("unregisterEventQueue", SCUnregisterEventQueueAction);
  DEF_CMD("createSystemDSM", SCCreateSystemDSMAction);

  DEF_CMD("removeFromString", SCRemovePatternAction);

  DEF_CMD("getErrorCodePlayback", SCGetErrorCodePlaybackAction);

  if (cmd == "DI") {
    SCDIAction * a = new SCDIAction(params, false);
    a->name = from_str;
    return a;
  }  

  if (cmd == "DIgetResult") {
    SCDIAction * a = new SCDIAction(params, true);
    a->name = from_str;
    return a;
  }  

  DEF_CMD("B2B.connectCallee", SCB2BConnectCalleeAction);
  DEF_CMD("B2B.terminateOtherLeg", SCB2BTerminateOtherLegAction);
  DEF_CMD("B2B.sendReinvite", SCB2BReinviteAction);
  DEF_CMD("B2B.sendEstablishedReinvite", SCB2BSendEstablishedReinviteAction);
  DEF_CMD("B2B.enableEarlyMediaRelay", SCB2BEnableEarlyMediaRelayAction);
  DEF_CMD("B2B.addHeader", SCB2BAddHeaderAction);
  DEF_CMD("B2B.getHeaderRequest", SCB2BGetHeaderRequestAction);
  DEF_CMD("B2B.getHeaderReply", SCB2BGetHeaderReplyAction);
  DEF_CMD("B2B.getHeaderParamRequest", SCB2BGetHeaderParamRequestAction);
  DEF_CMD("B2B.getHeaderParamReply", SCB2BGetHeaderParamReplyAction);
  DEF_CMD("B2B.removeHeader", SCB2BRemoveHeaderAction);
  DEF_CMD("B2B.clearHeaders", SCB2BClearHeadersAction);
  DEF_CMD("B2B.setHeaders", SCB2BSetHeadersAction);
  DEF_CMD("B2B.relayEvent", SCRelayB2BEventAction);

  DEF_CMD("trackObject", SCTrackObjectAction);
  DEF_CMD("releaseObject", SCReleaseObjectAction);
  DEF_CMD("freeObject", SCFreeObjectAction);

  return NULL;
}

DSMCondition* DSMCoreModule::getCondition(const string& from_str) {
  string cmd;
  string params;
  splitCmd(from_str, cmd, params);

  if (cmd == "keyPress") {
    DSMCondition* c = new DSMCondition();
    c->name = "key pressed: " + params;
    c->type = DSMCondition::Key;
    c->params["key"] = std::move(params);
    return c;
  }

  if (cmd == "test")
    return new TestDSMCondition(params, DSMCondition::Any);

  if ((cmd == "keyTest") || (cmd == "key"))
    return new TestDSMCondition(params, DSMCondition::Key);

  if ((cmd == "timerTest") || (cmd == "timer"))
    return new TestDSMCondition(params, DSMCondition::Timer);

  if ((cmd == "noAudioTest") || (cmd == "noAudio"))
    return new TestDSMCondition(params, DSMCondition::NoAudio);

  if ((cmd == "separatorTest") || (cmd == "separator"))
    return new TestDSMCondition(params, DSMCondition::PlaylistSeparator);

  if (cmd == "hangup") 
    return new TestDSMCondition(params, DSMCondition::Hangup);  

  if ((cmd == "eventTest") || (cmd == "event"))
    return new TestDSMCondition(params, DSMCondition::DSMEvent);  

  if (cmd == "B2Bevent")
    return new TestDSMCondition(params, DSMCondition::B2BEvent);

  if (cmd == "invite") 
    return new TestDSMCondition(params, DSMCondition::Invite);  

  if (cmd == "earlySession")
    return new TestDSMCondition(params, DSMCondition::EarlySession);

  if (cmd == "sessionStart") 
    return new TestDSMCondition(params, DSMCondition::SessionStart);  

  if (cmd == "ringing") 
    return new TestDSMCondition(params, DSMCondition::Ringing);

  if (cmd == "early") 
    return new TestDSMCondition(params, DSMCondition::EarlySession);

  if (cmd == "failed") 
    return new TestDSMCondition(params, DSMCondition::FailedCall);  

  if (cmd == "B2B.otherRequest")
    return new TestDSMCondition(params, DSMCondition::B2BOtherRequest);

  if (cmd == "B2B.otherReply") 
    return new TestDSMCondition(params, DSMCondition::B2BOtherReply);  

  if (cmd == "B2B.otherBye") 
    return new TestDSMCondition(params, DSMCondition::B2BOtherBye);  

  if (cmd == "sipRequest") 
    return new TestDSMCondition(params, DSMCondition::SipRequest);  

  if (cmd == "sipReply") 
    return new TestDSMCondition(params, DSMCondition::SipReply);  

  if (cmd == "remoteDisappeared") 
    return new TestDSMCondition(params, DSMCondition::RemoteDisappeared);  

  if (cmd == "sessionTimeout")
    return new TestDSMCondition(params, DSMCondition::SessionTimeout);

  if (cmd == "rtpTimeout")
    return new TestDSMCondition(params, DSMCondition::RtpTimeout);

  if (cmd == "jsonRpcRequest") 
    return new TestDSMCondition(params, DSMCondition::JsonRpcRequest);  

  if (cmd == "jsonRpcResponse") 
    return new TestDSMCondition(params, DSMCondition::JsonRpcResponse);  

  if (cmd == "subscription")
    return new TestDSMCondition(params, DSMCondition::SIPSubscription);

  if (cmd == "startup")
    return new TestDSMCondition(params, DSMCondition::Startup);

  if (cmd == "start")
    return new TestDSMCondition(params, DSMCondition::Start);

  if (cmd == "beforeDestroy")
    return new TestDSMCondition(params, DSMCondition::BeforeDestroy);

  if (cmd == "reload")
    return new TestDSMCondition(params, DSMCondition::Reload);

  if (cmd == "system")
    return new TestDSMCondition(params, DSMCondition::System);

  if (cmd == "rtpTimeout")
    return new TestDSMCondition(params, DSMCondition::RTPTimeout);

  return NULL;
}

EXEC_ACTION_START(SCPlayPromptAction) {
  sc_sess->playPrompt(resolveVars(arg, sess, sc_sess, event_params));
} EXEC_ACTION_END;

EXEC_ACTION_START(SCPlayPromptFrontAction) {
  sc_sess->playPrompt(resolveVars(arg, sess, sc_sess, event_params), false, true);
} EXEC_ACTION_END;

EXEC_ACTION_START(SCSetPromptsAction) {
  sc_sess->setPromptSet(resolveVars(arg, sess, sc_sess, event_params));
} EXEC_ACTION_END;

CONST_ACTION_2P(SCAddSeparatorAction, ',', true);
EXEC_ACTION_START(SCAddSeparatorAction){
  bool front = resolveVars(par2, sess, sc_sess, event_params) == "true";
  sc_sess->addSeparator(resolveVars(par1, sess, sc_sess, event_params), front);
} EXEC_ACTION_END;

EXEC_ACTION_START(SCPlayPromptLoopedAction){
  sc_sess->playPrompt(resolveVars(arg, sess, sc_sess, event_params), true);
} EXEC_ACTION_END;

void setEventParameters(const DSMSession* sc_sess, const string& var, VarMapT& params) {
  if (var.empty())
    return;

  if (var == "var") {
    params = sc_sess->var;
  } else {
    vector<string> vars = explode(var, ";");
    for (vector<string>::iterator it = vars.begin(); it != vars.end(); it++) {
      string varname = *it;

      if (varname.length() && varname[varname.length()-1]=='.') {
	DBG("adding postEvent param %s (struct)\n", varname.c_str());
	
	map<string, string>::const_iterator lb = sc_sess->var.lower_bound(varname);
	while (lb != sc_sess->var.end()) {
	  if ((lb->first.length() < varname.length()) ||
	      strncmp(lb->first.c_str(), varname.c_str(), varname.length()))
	    break;
	  params[lb->first] = lb->second;
	  lb++;
	}
      } else {
	VarMapT::const_iterator v_it = sc_sess->var.find(varname);
	if (v_it != sc_sess->var.end()) {
	  DBG("adding postEvent param %s=%s\n",
	      it->c_str(), v_it->second.c_str());
	  params[varname] = v_it->second;
	}
      }
    }
  }
}

CONST_ACTION_2P(SCPostEventAction, ',', true);
EXEC_ACTION_START(SCPostEventAction){
  string sess_id = resolveVars(par1, sess, sc_sess, event_params);
  string var = resolveVars(par2, sess, sc_sess, event_params);
  DSMEvent* ev = new DSMEvent();
  setEventParameters(sc_sess, var, ev->params);

  DBG("posting event to session '%s'\n", sess_id.c_str());
  if (!AmSessionContainer::instance()->postEvent(sess_id, ev)) {
    sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
    sc_sess->SET_STRERROR("event could not be posted\n");
  } else {
    sc_sess->CLR_ERRNO;
  }
} EXEC_ACTION_END;

EXEC_ACTION_START(SCRelayB2BEventAction) {
  AmB2BSession* b2b_sess = dynamic_cast<AmB2BSession*>(sess);
  if (NULL == b2b_sess) {
    throw DSMException("script", "cause", "relayEvent used without B2B call");
  }

  string var = resolveVars(arg, sess, sc_sess, event_params);
  B2BEvent* ev = new B2BEvent(E_B2B_APP, B2BEvent::B2BApplication);
  setEventParameters(sc_sess, var, ev->params);

  b2b_sess->relayEvent(ev);

} EXEC_ACTION_END;

CONST_ACTION_2P(SCPlayFileAction, ',', true);
EXEC_ACTION_START(SCPlayFileAction) {
  bool loop = 
    resolveVars(par2, sess, sc_sess, event_params) == "true";
  DBG("par1 = '%s', par2 = %s\n", par1.c_str(), par2.c_str());
  sc_sess->playFile(resolveVars(par1, sess, sc_sess, event_params), 
		    loop);
} EXEC_ACTION_END;

CONST_ACTION_2P(SCPlayFileFrontAction, ',', true);
EXEC_ACTION_START(SCPlayFileFrontAction) {
  bool loop = 
    resolveVars(par2, sess, sc_sess, event_params) == "true";
  DBG("par1 = '%s', par2 = %s\n", par1.c_str(), par2.c_str());
  sc_sess->playFile(resolveVars(par1, sess, sc_sess, event_params), 
		    loop, true);
} EXEC_ACTION_END;

EXEC_ACTION_START(SCPlaySilenceAction) {
  int length;
  string length_str = resolveVars(arg, sess, sc_sess, event_params);
  if (!str2int(length_str, length)) {
    throw DSMException("core", "cause", "cannot parse number");
  }
  sc_sess->playSilence(length);
} EXEC_ACTION_END;


CONST_ACTION_2P(SCPlayRingtoneAction, ',', false);
EXEC_ACTION_START(SCPlayRingtoneAction) {
  int length = 0, on=0, off=0, f=0, f2=0;

  string varname = par1;
  if (varname.length() && varname[0]=='$')
    varname = varname.substr(1);

  string front = resolveVars(par2, sess, sc_sess, event_params);

#define GET_VAR_INT(var_str, var_name)					\
  it = sc_sess->var.find(varname+"." var_str);				\
  if (it != sc_sess->var.end()) {					\
    if (!str2int(it->second, var_name)) {				\
      throw DSMException("core", "cause", "cannot parse number");	\
    }									\
  }

  VarMapT::iterator

  GET_VAR_INT("length", length);
  GET_VAR_INT("on", on);
  GET_VAR_INT("off", off);
  GET_VAR_INT("f", f);
  GET_VAR_INT("f2", f2);

#undef GET_VAR_INT
  DBG("Playing ringtone with length %d, on %d, off %d, f %d, f2 %d, front %s\n",
      length, on, off, f, f2, front.c_str());
  sc_sess->playRingtone(length, on, off, f, f2, front=="true");
} EXEC_ACTION_END;

EXEC_ACTION_START(SCPlaySilenceFrontAction) {
  int length;
  string length_str = resolveVars(arg, sess, sc_sess, event_params);
  if (!str2int(length_str, length)) {
    throw DSMException("core", "cause", "cannot parse number");
  }
  sc_sess->playSilence(length, true);
} EXEC_ACTION_END;

EXEC_ACTION_START(SCRecordFileAction) {
  sc_sess->recordFile(resolveVars(arg, sess, sc_sess, event_params));
} EXEC_ACTION_END;

EXEC_ACTION_START(SCStopRecordAction) {
  sc_sess->stopRecord();
} EXEC_ACTION_END;

EXEC_ACTION_START(SCGetRecordLengthAction) {
  string varname = resolveVars(arg, sess, sc_sess, event_params);
  if (varname.empty())
    varname = "record_length";
  sc_sess->var[varname]=int2str(sc_sess->getRecordLength());
} EXEC_ACTION_END;

EXEC_ACTION_START(SCGetRecordDataSizeAction) {
  string varname = resolveVars(arg, sess, sc_sess, event_params);
  if (varname.empty())
    varname = "record_data_size";
  sc_sess->var[varname]=int2str(sc_sess->getRecordDataSize());
} EXEC_ACTION_END;

EXEC_ACTION_START(SCFlushPlaylistAction) {
  sc_sess->flushPlaylist();
} EXEC_ACTION_END;

EXEC_ACTION_START(SCClosePlaylistAction) {
  WARN("closePlaylist() is deprecated - please use flushPlaylist() instead\n");
  sc_sess->flushPlaylist();
} EXEC_ACTION_END;


EXEC_ACTION_START(SCSetInOutPlaylistAction) {
  sc_sess->setInOutPlaylist();
} EXEC_ACTION_END;

EXEC_ACTION_START(SCSetInputPlaylistAction) {
  sc_sess->setInputPlaylist();
} EXEC_ACTION_END;

EXEC_ACTION_START(SCSetOutputPlaylistAction) {
  sc_sess->setOutputPlaylist();
} EXEC_ACTION_END;

EXEC_ACTION_START(SCConnectMediaAction) {
  sc_sess->connectMedia();
} EXEC_ACTION_END;

EXEC_ACTION_START(SCDisconnectMediaAction) {
  sc_sess->disconnectMedia();
} EXEC_ACTION_END;

EXEC_ACTION_START(SCEnableReceivingAction) {
  DBG("enabling RTP receving in session\nb");
  sess->setReceiving(true);
} EXEC_ACTION_END;

EXEC_ACTION_START(SCDisableReceivingAction) {
  DBG("disabling RTP receving in session\nb");
  sess->setReceiving(false);
} EXEC_ACTION_END;

EXEC_ACTION_START(SCEnableForceDTMFReceiving) {
  DBG("enabling forced DTMF RTP receving in session\nb");
  sess->setForceDtmfReceiving(true);
} EXEC_ACTION_END;

EXEC_ACTION_START(SCDisableForceDTMFReceiving) {
  DBG("disabling forced DTMF RTP receving in session\nb");
  sess->setForceDtmfReceiving(false);
} EXEC_ACTION_END;

EXEC_ACTION_START(SCMonitorRTPTimeoutAction) {
  string e = resolveVars(arg, sess, sc_sess, event_params);
  DBG("setting RTP stream to %smonitor RTP timeout\n", e=="true"?"":"not");
  sess->RTPStream()->setMonitorRTPTimeout(e=="true");
} EXEC_ACTION_END;

EXEC_ACTION_START(SCMuteAction) {
  sc_sess->mute();
} EXEC_ACTION_END;

EXEC_ACTION_START(SCUnmuteAction) {
  sc_sess->unmute();
} EXEC_ACTION_END;


EXEC_ACTION_START(SCEnableDTMFDetection) {
  sess->setDtmfDetectionEnabled(true);
} EXEC_ACTION_END;

EXEC_ACTION_START(SCDisableDTMFDetection) {
  sess->setDtmfDetectionEnabled(false);
} EXEC_ACTION_END;

CONST_ACTION_2P(SCThrowAction, ',', true);
EXEC_ACTION_START(SCThrowAction) {
  map<string, string> e_args;
  e_args["type"] = resolveVars(par1, sess, sc_sess, event_params); 
  DBG("throwing DSMException type '%s'\n", e_args["type"].c_str());

  string e_params = resolveVars(par2, sess, sc_sess, event_params);
  
  // inefficient param-split
  vector<string> params = explode(e_params, ";");
  for (vector<string>::iterator it=
	 params.begin(); it != params.end(); it++) {
    vector<string> n = explode(*it, "=");
    if (n.size()==2) {
      e_args[n[0]]=n[1];
    }
  }
  
  throw DSMException(e_args);

} EXEC_ACTION_END;

EXEC_ACTION_START(SCThrowOnErrorAction) {
  if (sc_sess->var["errno"].empty())
    EXEC_ACTION_STOP;

  map<string, string> e_args;
  e_args["type"] = sc_sess->var["errno"];

  DBG("throwing DSMException type '%s'\n", e_args["type"].c_str());
  e_args["text"] = sc_sess->var["strerror"];
  
  throw DSMException(e_args);

} EXEC_ACTION_END;


EXEC_ACTION_START(SCStopAction) {
  if (resolveVars(arg, sess, sc_sess, event_params) == "true") {
    DBG("sending bye\n");
    sess->dlg->bye();
  }
  sess->setStopped();
} EXEC_ACTION_END;

#define DEF_SCModActionExec(clsname)				\
								\
  bool clsname::execute(AmSession* sess, DSMSession* sc_sess,	\
			DSMCondition::EventType event,		\
			map<string,string>* event_params) {	\
    return true;						\
  }								\

DEF_SCModActionExec(SCRepostAction);
DSMAction::SEAction SCRepostAction::getSEAction(string& param,
						AmSession* sess, DSMSession* sc_sess,
						DSMCondition::EventType event,
						map<string,string>* event_params) {
  return Repost; 
}

DEF_SCModActionExec(SCJumpFSMAction);
DSMAction::SEAction SCJumpFSMAction::getSEAction(string& param,
						 AmSession* sess, DSMSession* sc_sess,
						 DSMCondition::EventType event,
						 map<string,string>* event_params) {
  param = resolveVars(arg, sess, sc_sess, event_params);
  return Jump; 
}

DEF_SCModActionExec(SCCallFSMAction);
DSMAction::SEAction SCCallFSMAction::getSEAction(string& param,
						 AmSession* sess, DSMSession* sc_sess,
						 DSMCondition::EventType event,
						 map<string,string>* event_params) {
  param = resolveVars(arg, sess, sc_sess, event_params);
  return Call; 
}

DEF_SCModActionExec(SCReturnFSMAction);
DSMAction::SEAction SCReturnFSMAction::getSEAction(string& param,
						   AmSession* sess, DSMSession* sc_sess,
						   DSMCondition::EventType event,
						   map<string,string>* event_params) {
  return Return; 
}

#undef DEF_SCModActionExec

CONST_ACTION_2P(SCLogAction, ',', false);
EXEC_ACTION_START(SCLogAction) {
  unsigned int lvl;
  if (str2int(resolveVars(par1, sess, sc_sess, event_params), lvl)) {
    ERROR("unknown log level '%s'\n", par1.c_str());
    EXEC_ACTION_STOP;
  }
  string l_line = resolveVars(par2, sess, sc_sess, event_params).c_str();
  _LOG((int)lvl, "FSM: %s '%s'\n", (par2 != l_line)?par2.c_str():"",
       l_line.c_str());
} EXEC_ACTION_END;

CONST_ACTION_2P(SCLogsAction, ',', false);
EXEC_ACTION_START(SCLogsAction) {
  unsigned int lvl;
  if (str2int(resolveVars(par1, sess, sc_sess, event_params), lvl)) {
    ERROR("unknown log level '%s'\n", par1.c_str());
    EXEC_ACTION_STOP;
  }
  string l_line = replaceParams(par2, sess, sc_sess, event_params);
  _LOG((int)lvl, "FSM: '%s'\n", l_line.c_str());
} EXEC_ACTION_END;

EXEC_ACTION_START(SCDbgAction) {
  string l_line = replaceParams(arg, sess, sc_sess, event_params);
  DBG("FSM: '%s'\n", l_line.c_str());
} EXEC_ACTION_END;

EXEC_ACTION_START(SCInfoAction) {
  string l_line = replaceParams(arg, sess, sc_sess, event_params);
  INFO("FSM: '%s'\n", l_line.c_str());
} EXEC_ACTION_END;

EXEC_ACTION_START(SCWarnAction) {
  string l_line = replaceParams(arg, sess, sc_sess, event_params);
  WARN("FSM: '%s'\n", l_line.c_str());
} EXEC_ACTION_END;

EXEC_ACTION_START(SCErrorAction) {
  string l_line = replaceParams(arg, sess, sc_sess, event_params);
  ERROR("FSM: '%s'\n", l_line.c_str());
} EXEC_ACTION_END;

void log_vars(const string& l_arg, AmSession* sess,
	      DSMSession* sc_sess, map<string,string>* event_params) {
  unsigned int lvl;
  if (str2int(resolveVars(l_arg, sess, sc_sess, event_params), lvl)) {
    ERROR("unknown log level '%s'\n", l_arg.c_str());
    return;
  }

  _LOG((int)lvl, "FSM: variables set ---\n");
  for (map<string, string>::iterator it = 
	 sc_sess->var.begin(); it != sc_sess->var.end(); it++) {
    _LOG((int)lvl, "FSM:  $%s='%s'\n", it->first.c_str(), it->second.c_str());
  }
  _LOG((int)lvl, "FSM: variables end ---\n");
}

EXEC_ACTION_START(SCLogVarsAction) {
  log_vars(arg, sess, sc_sess, event_params);
} EXEC_ACTION_END;

void log_params(const string& l_arg, AmSession* sess,
		DSMSession* sc_sess, map<string,string>* event_params) {
  unsigned int lvl;
  if (str2int(resolveVars(l_arg, sess, sc_sess, event_params), lvl)) {
    ERROR("unknown log level '%s'\n", l_arg.c_str());
    return;
  }

  if (NULL == event_params) {
    _LOG((int)lvl, "FSM: no event params ---\n");
    return;
  }

  _LOG((int)lvl, "FSM: params set ---\n");
  for (map<string, string>::iterator it = 
	 event_params->begin(); it != event_params->end(); it++) {
    _LOG((int)lvl, "FSM:  #%s='%s'\n", it->first.c_str(), it->second.c_str());
  }
  _LOG((int)lvl, "FSM: params end ---\n");
}

EXEC_ACTION_START(SCLogParamsAction) {
  log_params(arg, sess, sc_sess, event_params);
} EXEC_ACTION_END;


void log_selects(const string& l_arg, AmSession* sess,
		 DSMSession* sc_sess, map<string,string>* event_params) {
  unsigned int lvl;
  if (str2int(resolveVars(l_arg, sess, sc_sess, event_params), lvl)) {
    ERROR("unknown log level '%s'\n", l_arg.c_str());
    return;
  }

  _LOG((int)lvl, "FSM: selects set ---\n");

#define SELECT_LOG(select_name)					\
  _LOG((int)lvl, "FSM:  @%s='%s'\n", select_name,			\
       resolveVars("@" select_name, sess, sc_sess, event_params).c_str());	

  SELECT_LOG("local_tag");
  SELECT_LOG("user");
  SELECT_LOG("domain");
  SELECT_LOG("remote_tag");
  SELECT_LOG("callid");
  SELECT_LOG("local_uri");
  SELECT_LOG("local_party");
  SELECT_LOG("remote_uri");
  SELECT_LOG("remote_party");
#undef SELECT_LOG
  _LOG((int)lvl, "FSM: selects end ---\n");
}

EXEC_ACTION_START(SCLogSelectsAction) {
  log_selects(arg, sess, sc_sess, event_params);
} EXEC_ACTION_END;

EXEC_ACTION_START(SCLogAllAction) {
  log_vars(arg, sess, sc_sess, event_params);
  log_params(arg, sess, sc_sess, event_params);
  log_selects(arg, sess, sc_sess, event_params);
} EXEC_ACTION_END;

CONST_ACTION_2P(SCSetAction,'=', false);
EXEC_ACTION_START(SCSetAction) {
  if (par1.length() && par1[0] == '#') {
    // set param
    if (NULL != event_params) {
      string res = resolveVars(par2, sess, sc_sess, event_params);
      (*event_params)[par1.substr(1)] = res;
      DBG("set #%s='%s'\n", par1.substr(1).c_str(), res.c_str());
    } else {
      DBG("not setting %s (no param set)\n", par1.c_str());
    }
  } else {
    // set variable
    string var_name = (par1.length() && par1[0] == '$')?
      par1.substr(1) : par1;

    sc_sess->var[var_name] = resolveVars(par2, sess, sc_sess, event_params);
    
    DBG("set $%s='%s'\n", 
	var_name.c_str(), sc_sess->var[var_name].c_str());
  }
} EXEC_ACTION_END;

string replaceParams(const string& q, AmSession* sess, DSMSession* sc_sess, 
		     map<string,string>* event_params) {
  string res = q;
  size_t repl_pos = 0;
  while (repl_pos<res.length()) {
    size_t rstart = res.find_first_of("$#@", repl_pos);
    repl_pos = rstart+1;
    if (rstart == string::npos) 
      break;
    if (rstart && (res.length() > rstart) && (res[rstart]==res[repl_pos])) {
      res.erase(rstart, 1);
      continue;
    }
    if (rstart && res[rstart-1] == '\\') // escaped
      continue;
    size_t rend;
    if (res.length() > rstart+1 && 
	(res[rstart+1] == '(' ||
	 res[rstart+1] == '"' ||
	 res[rstart+1] == '\''
	 ))
      rend = res.find_first_of(" ,()[]$#@\t;:'\"", rstart+2);
    else 
      rend = res.find_first_of(" ,()[]$#@\t;:'\"", rstart+1);
    if (rend==string::npos)
      rend = res.length();
    string keyname = res.substr(rstart+1, rend-rstart-1);

    if (keyname.length()>2) {
      if ((keyname[0] == '(' && res[rend] == ')') ||
	  (keyname[0] == res[rend] &&
	   (keyname[0] == '"' ||keyname[0] == '\''))) {
	keyname = keyname.substr(1);
	if (rend != res.length())
	  rend++;
      }
    }
    // todo: simply use resolveVars (?)
    switch(res[rstart]) {
    case '$': {
      if (sc_sess->var.find(keyname) == sc_sess->var.end()) {
	res.erase(rstart, rend-rstart);
	if (repl_pos) repl_pos--; // repl_pos was after $
      } else { 
	res.replace(rstart, rend-rstart, sc_sess->var[keyname]);
	if (sc_sess->var[keyname].size())
	  repl_pos+=sc_sess->var[keyname].size()-1; // skip after new string
      }
    } break;
    case '#':
      if (NULL!=event_params) {
	if (event_params->find(keyname) != event_params->end()) {
	  res.replace(rstart, rend-rstart, (*event_params)[keyname]);
	  repl_pos+=(*event_params)[keyname].size()-1; // skip after new string
	} else {
	  res.erase(rstart, rend-rstart);
	  if (repl_pos) repl_pos--; // repl_pos was after #
	}
      } break;
    case '@': {
      // todo: optimize
      string n = resolveVars("@"+keyname, sess, sc_sess, event_params);
      res.replace(rstart, rend-rstart, n);
      if (n.size())
	repl_pos+=n.size()-1;  // skip after new string
        //    rstart  rend
	// bla@(varname)uuuu
        //     r
        //         r
        // bla12345huuuu
    } break;
    default: break;
    }
  }
  return res;
}
CONST_ACTION_2P(SCSetSAction,'=', false);
EXEC_ACTION_START(SCSetSAction) {
  if (par1.length() && par1[0] == '#') {
    // set param
    if (NULL != event_params) {
      string res = replaceParams(par2, sess, sc_sess, event_params);
      (*event_params)[par1.substr(1)] = res;
      DBG("set #%s='%s'\n", par1.substr(1).c_str(), res.c_str());
    } else {
      DBG("not set %s (no param set)\n", par1.c_str());
    }
  } else {
    // set variable
    string var_name = (par1.length() && par1[0] == '$')?
      par1.substr(1) : par1;

    sc_sess->var[var_name] = replaceParams(par2, sess, sc_sess, event_params);
    
    DBG("set $%s='%s'\n", 
	var_name.c_str(), sc_sess->var[var_name].c_str());
  }
} EXEC_ACTION_END;

CONST_ACTION_2P(SCEvalAction,'=', false);
EXEC_ACTION_START(SCEvalAction) {
  string var_name = (par1.length() && par1[0] == '$')?
    par1.substr(1) : par1;

  sc_sess->var[var_name] = resolveVars(par2, sess, sc_sess, event_params, true);
  DBG("eval $%s='%s'\n", 
      var_name.c_str(), sc_sess->var[var_name].c_str());
} EXEC_ACTION_END;

CONST_ACTION_2P(SCSetVarAction,'=', false);
EXEC_ACTION_START(SCSetVarAction) {
  string var_name = resolveVars(par1, sess, sc_sess, event_params);
  sc_sess->var[var_name] = resolveVars(par2, sess, sc_sess, event_params);
  DBG("set $%s='%s'\n", 
      var_name.c_str(), sc_sess->var[var_name].c_str());
} EXEC_ACTION_END;

CONST_ACTION_2P(SCGetParamAction,'=', false);
EXEC_ACTION_START(SCGetParamAction){

  string dst_var_name = (par1.length() && par1[0] == '$')?
    par1.substr(1) : par1;
  string param_name = resolveVars(par2, sess, sc_sess, event_params);
  
  DBG("param_name = %s, dst = %s\n", param_name.c_str(), dst_var_name.c_str());

  if (NULL==event_params) {
    sc_sess->var[dst_var_name] = "";
    EXEC_ACTION_STOP;
  }

  map<string, string>::iterator it = event_params->find(param_name);
  if (it != event_params->end()) {
    sc_sess->var[dst_var_name] = it->second;
  } else {
    sc_sess->var[dst_var_name] = "";
  }
  
  DBG("set $%s='%s'\n", 
      dst_var_name.c_str(), sc_sess->var[dst_var_name].c_str());
} EXEC_ACTION_END;

CONST_ACTION_2P(SCGetVarAction,'=', false);
EXEC_ACTION_START(SCGetVarAction){
  string dst_var_name = (par1.length() && par1[0] == '$')?
    par1.substr(1) : par1;
  string var_name = resolveVars(par2, sess, sc_sess, event_params);
  
  DBG("var_name = %s, dst = %s\n", var_name.c_str(), dst_var_name.c_str());
  sc_sess->var[dst_var_name] = sc_sess->var[var_name];
  DBG("set $%s='%s'\n", 
      dst_var_name.c_str(), sc_sess->var[dst_var_name].c_str());
} EXEC_ACTION_END;

EXEC_ACTION_START(SCClearAction) {
  string var_name = (arg.length() && arg[0] == '$')?
    arg.substr(1) : arg;
  DBG("clear variable '%s'\n", var_name.c_str());
  sc_sess->var.erase(var_name);
} EXEC_ACTION_END;

EXEC_ACTION_START(SCClearStructAction) {
  string varprefix = (arg.length() && arg[0] == '$')?
    arg.substr(1) : arg;
  DBG("clear variable struct '%s.*'\n", varprefix.c_str());

  varprefix+=".";

  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;
    map<string, string>::iterator lb_d = lb;
    lb++;
    sc_sess->var.erase(lb_d);    
  }

} EXEC_ACTION_END;


EXEC_ACTION_START(SCClearArrayAction) {
  string varprefix = (arg.length() && arg[0] == '$')?
    arg.substr(1) : arg;
  DBG("clear variable array '%s[*'\n", varprefix.c_str());

  varprefix+="[";

  VarMapT::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;
    // fixme: check whether it's really an array index
    VarMapT::iterator lb_d = lb;
    lb++;
    sc_sess->var.erase(lb_d);
  }

} EXEC_ACTION_END;

CONST_ACTION_2P(SCSizeAction, ',', false);
EXEC_ACTION_START(SCSizeAction) {
  string array_name = par1;
  if (array_name.length() && array_name[0]=='$')
    array_name.erase(0,1);

  string dst_name = par2;
  if (dst_name.length()&&dst_name[0]=='$')
    dst_name.erase(0,1);


  unsigned int a_size = 0;
  while (true) {
    string ai_name = array_name+"["+int2str(a_size)+"]";
    VarMapT::iterator lb = sc_sess->var.lower_bound(ai_name);
    if (lb == sc_sess->var.end() ||
	lb->first.substr(0,ai_name.length()) != ai_name)
      break;
    a_size++;
  }
  string res = int2str(a_size);
  sc_sess->var[dst_name] = res;
  DBG("set $%s=%s\n", dst_name.c_str(), res.c_str());
} EXEC_ACTION_END;

CONST_ACTION_2P(SCArrayIndexAction, ',', false);
EXEC_ACTION_START(SCArrayIndexAction) {
  string array_name = par1;
  if (array_name.length() && array_name[0]=='$')
    array_name.erase(0,1);

  string val = resolveVars(par2, sess, sc_sess, event_params);
  unsigned int i = 0;
  bool found = false;
  while (true) {
    VarMapT::iterator lb = sc_sess->var.find(array_name+"["+int2str(i)+"]");
    if (lb == sc_sess->var.end())
      break;
    if (val == lb->second) {
      found = true;
      break;
    }
    i++;
  }

  string res = found ? int2str(i) : "nil";
  if (par2[0]=='$') {
    sc_sess->var[par2.substr(1)+".index"] = res;
    DBG("set %s=%s\n", (par2+".index").c_str(), res.c_str());
  } else {
    sc_sess->var["index"] = res;
    DBG("set $index=%s\n", res.c_str());
  }
} EXEC_ACTION_END;

CONST_ACTION_2P(SCRemovePatternAction, ',', false);
EXEC_ACTION_START(SCRemovePatternAction) {
  string source_name = (par1.length() && par1[0] == '$') ? par1.substr(1) : par1;
  string source_value = resolveVars(par1, sess, sc_sess, event_params);
  string pattern = par2;
  size_t pos = std::string::npos;
  while ((pos = source_value.find(pattern)) != std::string::npos)
  {
    source_value.erase(pos, pattern.length());
  }
  sc_sess->var[source_name] = source_value;
} EXEC_ACTION_END;


CONST_ACTION_3P(SCGetErrorCodePlaybackAction, ',', false);
EXEC_ACTION_START(SCGetErrorCodePlaybackAction) {
  string source_value = resolveVars(par1, sess, sc_sess, event_params);
  string pattern = resolveVars(par2, sess, sc_sess, event_params);
  string destination_variable = (par3.length() && par3[0] == '$') ? par3.substr(1) : par3;
  string result; /* empty by default */
	size_t pos = 0;
	size_t end_pos;
  /* parses strings like "var1:val1,var2:val2,var3:val3" */
  while ((end_pos = source_value.find(',', pos)) != std::string::npos || pos < source_value.size())
  {
    string pair = source_value.substr(pos, end_pos - pos);
    size_t separator_pos = pair.find(':');
    if (separator_pos != std::string::npos) {
      string current_var = pair.substr(0, separator_pos);
      string current_val = pair.substr(separator_pos + 1);
      if (current_var == pattern) {
        result = std::move(current_val);
        break;
      }
    }
    pos = (end_pos == std::string::npos) ? source_value.size() : end_pos + 1;
  }
  /* rewrite only if we got something */
  if (!result.empty())
    sc_sess->var[std::move(destination_variable)] = std::move(result);
} EXEC_ACTION_END;

CONST_ACTION_2P(SCAppendAction,',', false);
EXEC_ACTION_START(SCAppendAction) {
  string var_name = (par1.length() && par1[0] == '$')?
    par1.substr(1) : par1;

  sc_sess->var[var_name] += resolveVars(par2, sess, sc_sess, event_params);

  DBG("$%s now '%s'\n", 
      var_name.c_str(), sc_sess->var[var_name].c_str());
} EXEC_ACTION_END;

CONST_ACTION_2P(SCSubStrAction,',', false);
EXEC_ACTION_START(SCSubStrAction) {
  string var_name = (par1.length() && par1[0] == '$')?
    par1.substr(1) : par1;
  unsigned int pos = 0;
  unsigned int pos2 = 0;
  size_t c_pos = par2.find(",");
  if (c_pos == string::npos) {
    if (str2int(resolveVars(par2, sess, sc_sess, event_params), pos)) {
      ERROR("substr length '%s' unparseable\n",
	    resolveVars(par2, sess, sc_sess, event_params).c_str());
      return false;
    }
  } else {
    if (str2int(resolveVars(par2.substr(0, c_pos), sess, sc_sess, event_params), pos)) {
      ERROR("substr length '%s' unparseable\n",
	    resolveVars(par2.substr(0, c_pos), sess, sc_sess, event_params).c_str());
      return false;
    }

    if (str2int(resolveVars(par2.substr(c_pos+1), sess, sc_sess, event_params), pos2)) {
      ERROR("substr length '%s' unparseable\n",
	    resolveVars(par2.substr(0, c_pos-1), sess, sc_sess, event_params).c_str());
      return false;
    }
  }

  try {
    if (pos2 == 0)
      sc_sess->var[var_name] = sc_sess->var[var_name].substr(pos);
    else 
      sc_sess->var[var_name] = sc_sess->var[var_name].substr(pos, pos2);
  } catch(...) {
    ERROR("in substr\n");
    return false;
  }  

  DBG("$%s now '%s'\n", 
      var_name.c_str(), sc_sess->var[var_name].c_str());
} EXEC_ACTION_END;

EXEC_ACTION_START(SCIncAction) {
  string var_name = (arg.length() && arg[0] == '$')?
    arg.substr(1) : arg;
  unsigned int val = 0;
  str2int(sc_sess->var[var_name], val);
  sc_sess->var[var_name] = int2str(val+1);

  DBG("inc: $%s now '%s'\n", 
      var_name.c_str(), sc_sess->var[var_name].c_str());

} EXEC_ACTION_END;

CONST_ACTION_2P(SCSetTimerAction,',', false);
EXEC_ACTION_START(SCSetTimerAction) {

  unsigned int timerid;
  if (str2int(resolveVars(par1, sess, sc_sess, event_params), timerid)) {
    ERROR("timer id '%s' not decipherable\n", 
	  resolveVars(par1, sess, sc_sess, event_params).c_str());
    sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
    sc_sess->SET_STRERROR("timer id '"+
			  resolveVars(par1, sess, sc_sess, event_params)+
			  "' not decipherable\n");
    EXEC_ACTION_STOP;
  }

  double timeout;

  if (!str2int(resolveVars(par2, sess, sc_sess, event_params), timeout)) {
    ERROR("timeout value '%s' not decipherable\n", 
	  resolveVars(par2, sess, sc_sess, event_params).c_str());
    sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
    sc_sess->SET_STRERROR("timeout value '"+
			  resolveVars(par2, sess, sc_sess, event_params)+
			  "' not decipherable\n");
    EXEC_ACTION_STOP;
  }

  if (!sess->setTimer(timerid, timeout)) {
    ERROR("load session_timer module for timers.\n");
    sc_sess->SET_ERRNO(DSM_ERRNO_CONFIG);
    sc_sess->SET_STRERROR("load sess_timer module for timers.\n");
    EXEC_ACTION_STOP;
  }

  sc_sess->CLR_ERRNO;
} EXEC_ACTION_END;


EXEC_ACTION_START(SCRemoveTimerAction) {

  unsigned int timerid;
  string timerid_s = resolveVars(arg, sess, sc_sess, event_params);
  if (str2int(timerid_s, timerid)) {
    ERROR("timer id '%s' not decipherable\n", timerid_s.c_str());
    sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
    sc_sess->SET_STRERROR("timer id '"+timerid_s+"' not decipherable\n");
    return false;
  }

  if (!sess->removeTimer(timerid)) {
    ERROR("load session_timer module for timers.\n");
    sc_sess->SET_ERRNO(DSM_ERRNO_CONFIG);
    sc_sess->SET_STRERROR("load session_timer module for timers.\n");
    EXEC_ACTION_STOP;
  }

  sc_sess->CLR_ERRNO;
} EXEC_ACTION_END;

EXEC_ACTION_START(SCRemoveTimersAction) {

  DBG("removing timers for session %s\n", sess->getLocalTag().c_str());
  if (!sess->removeTimers()) {
    ERROR("load session_timer module for timers.\n");
    sc_sess->SET_ERRNO(DSM_ERRNO_CONFIG);
    sc_sess->SET_STRERROR("load sess_timer module for timers.\n");
    EXEC_ACTION_STOP;
  }

  sc_sess->CLR_ERRNO;
} EXEC_ACTION_END;



// TODO: replace with real expression matching 
TestDSMCondition::TestDSMCondition(const string& expr, DSMCondition::EventType evt) {

  type = evt;

  if (expr.empty()) {
    ttype = Always;
    return;
  }
 
  ttype = None;
  
  size_t p = expr.find("==");
  size_t p2;
  if (p != string::npos) {
    ttype = Eq; p2 = p+2;
  } else {
    p = expr.find("!=");
    if (p != string::npos)  {
      ttype = Neq; p2 = p+2;
    } else {
      p = expr.find("<");
      if (p != string::npos) {
	ttype = Less; p2 = p+1;
      } else {
	p = expr.find(">");
	if (p != string::npos)  {
	  ttype = Gt; p2 = p+1;
	} else {
	  ERROR("expression '%s' not understood\n", 
		expr.c_str());
	  return;
	}
      }
    }
  }

  lhs = trim(expr.substr(0, p), " ");
  rhs = trim(expr.substr(p2,expr.length()-p2+1), " ");

  name = expr;
}

bool TestDSMCondition::match(AmSession* sess, DSMSession* sc_sess, DSMCondition::EventType event,
			  map<string,string>* event_params) {
  if (ttype == None || (type != DSMCondition::Any && type != event))
    return false;

  if (ttype == Always)
    return true;

  if (!sc_sess) {
    ERROR("wrong session type\n");
    return false;
  }
  
  string l;
  string r;
  if (lhs.length() > 5 && 
      (lhs.substr(0, 4) == "len(") && lhs[lhs.length()-1] == ')') {
    l = int2str((unsigned int)resolveVars(lhs.substr(4, lhs.length()-5), sess, sc_sess, event_params).length());
  } else {    
    l   = resolveVars(lhs, sess, sc_sess, event_params);
  }
  if (rhs.length() > 5 && 
      rhs.substr(0, 4) == "len(" && rhs[rhs.length()-1] == ')') {
    r = resolveVars(rhs.substr(4, rhs.length()-5), sess, sc_sess, event_params).length();
  } else {    
    r   = resolveVars(rhs, sess, sc_sess, event_params);
  }

//   string r = resolveVars(rhs, sess, sc_sess, event_params);

  DBG("test '%s' vs '%s'\n", l.c_str(), r.c_str());

  switch (ttype) {
  case Eq: {
    size_t starpos = r.find("*");
    if (starpos==string::npos)
      return l == r;
    else {
      if (l.size()<starpos)
	return false;
      return r.substr(0, starpos) == l.substr(0, starpos);
    }
  }
  case Neq: return l != r;
  case Less: {
    char* endptr = NULL;
    long l_i = strtol(l.c_str(), &endptr, 10);
    if (endptr && *endptr  == '\0') {
      long r_i = strtol(r.c_str(), &endptr, 10);
      if (endptr && *endptr  == '\0') 
	return l_i < r_i;
      }
    return l < r;
  }
  case Gt: {
    char* endptr = NULL;
    long l_i = strtol(l.c_str(), &endptr, 10);
    if (endptr && *endptr  == '\0') {
      long r_i = strtol(r.c_str(), &endptr, 10);
      if (endptr && *endptr  == '\0') 
	return l_i > r_i;
      }
    return l > r;
  } 
  default: return false;
  }  
}


SCDIAction::SCDIAction(const string& arg, bool get_res) 
  : get_res(get_res) {
  // TODO: don't ignore "" (don't use explode here)
  params = explode(arg,",");
  if (params.size()<2) {
    ERROR("DI needs at least: mod_name, "
	  "function_name\n");
    return;
  }
}

void string2argarray(const string& key, const string& val, AmArg& res) {
  if (key.empty())
    return;

  if (!(isArgStruct(res) || isArgUndef(res))) {
    WARN("array element [%s] is shadowed by value '%s'\n", 
	 key.c_str(), AmArg::print(res).c_str());
    return;
  }

  size_t delim = key.find(".");
  if (delim == string::npos) {
    res[key]=val;
    return;
  }
  string2argarray(key.substr(delim+1), val, res[key.substr(0,delim)]);
}

EXEC_ACTION_START(SCDIAction) {

  if (params.size() < 2) {
    ERROR("DI needs at least: mod_name, "
	  "function_name (in '%s')\n", name.c_str());
    sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
    sc_sess->SET_STRERROR("DI needs at least: mod_name, "
			  "function_name (in '"+name+"%s')\n");
    EXEC_ACTION_STOP;
  }

  vector<string>::iterator p_it=params.begin();
  string fact_name = trim(*p_it, " \"");
  AmDynInvokeFactory* fact = 
    AmPlugIn::instance()->getFactory4Di(fact_name);

  if(!fact) {
    ERROR("load module for factory '%s'.\n", fact_name.c_str());
    sc_sess->SET_ERRNO(DSM_ERRNO_CONFIG);
    sc_sess->SET_STRERROR("load module for factory '"+fact_name+"'.\n");
    EXEC_ACTION_STOP;
  }
  AmDynInvoke* di_inst = fact->getInstance();
  if(!di_inst) {
    ERROR("load module for factory '%s'\n", fact_name.c_str());
    sc_sess->SET_ERRNO(DSM_ERRNO_CONFIG);
    sc_sess->SET_STRERROR("load module for factory '"+fact_name+"'.\n");
    EXEC_ACTION_STOP;
  }
  p_it++; 

  string func_name = trim(*p_it, " \""); 
  p_it++;

  AmArg di_args;
  
  while (p_it != params.end()) {
    string p = trim(*p_it, " \t");
    if (p.length() && p[0] == '"') {
      di_args.push(trim(p,"\"").c_str());
    } else if (p.length() > 5 && 
	       p.substr(0, 5) =="(int)") {
      p = resolveVars(p.substr(5), sess, sc_sess, event_params);
      char* endptr = NULL;
      long p_i = strtol(p.c_str(), &endptr, 10);
      if (endptr && *endptr  == '\0') {
	di_args.push((int)p_i);
      } else {
	ERROR("converting value '%s' to int\n", 
	      p.c_str());
	sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
	sc_sess->SET_STRERROR("converting value '"+p+"' to int\n");
	EXEC_ACTION_STOP;
      }
    } else if (p.length() > 8 &&  
	       p.substr(0, 8) =="(struct)") {
      p.erase(0, 8);
      AmArg var_struct;
      string varprefix = p+".";
      //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 (varname.find(".") == string::npos)
	  var_struct[std::move(varname)] = lb->second;
	else
	  string2argarray(varname, lb->second, var_struct);
	
	lb++;
	//has_vars = true;
      }

      if (var_struct.getType() != AmArg::Undef)
        di_args.push(var_struct);

    } else if (p.length() > 7 &&  
	       p.substr(0, 7) =="(array)") {
      p.erase(0, 7);
      di_args.push(AmArg());
      AmArg& var_array = di_args.get(di_args.size()-1);
      
      unsigned int i=0;
      while (true) {
	map<string, string>::iterator it = 
	  sc_sess->var.find(p+"["+int2str(i)+"]");
	if (it == sc_sess->var.end())
	  break;
	var_array.push(it->second);
	i++;
      }
    } else if (p.length() > 6 &&  
	       p.substr(0, 6) == "(json)") {
      p.erase(0, 6);
      if (p.length() && p[0] == '"') {
	// remove quotes if parameter in form (json)"{"json":"object"}"
	p = trim(p,"\"");
      } else {
	p = resolveVars(p, sess, sc_sess, event_params);
      }

      di_args.push(AmArg());
      AmArg& var_json = di_args.get(di_args.size()-1);
      if (!json2arg(p, var_json)) {
	WARN("Error parsing JSON object '%s'\n", p.c_str());
	// todo: throw exception? 
      }      
    } else {
      di_args.push(resolveVars(p, sess, sc_sess, event_params).c_str());
    }
    p_it++;
  }

  sc_sess->di_res.clear();
  DBG("executing DI function '%s'\n", func_name.c_str());
  try {
    di_inst->invoke(func_name, di_args, sc_sess->di_res);
  } catch (const AmDynInvoke::NotImplemented& ni) {
    ERROR("not implemented DI function '%s'\n", 
	  ni.what.c_str());
    sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
    sc_sess->SET_STRERROR("not implemented DI function '"+ni.what+"'\n");
    EXEC_ACTION_STOP;
  } catch (const AmArg::OutOfBoundsException& oob) {
    ERROR("out of bounds in  DI call '%s'\n", 
	  name.c_str());
    sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
    sc_sess->SET_STRERROR("out of bounds in  DI call '"+name+"'\n");
    EXEC_ACTION_STOP;
  } catch (const AmArg::TypeMismatchException& oob) {
    ERROR("type mismatch  in  DI call '%s'\n", 
	  name.c_str());
    sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
    sc_sess->SET_STRERROR("type mismatch in  DI call '"+name+"'\n");
    EXEC_ACTION_STOP;
  } catch (...) {
    ERROR("unexpected Exception  in  DI call '%s'\n", 
	  name.c_str());
    sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
    sc_sess->SET_STRERROR("unexpected Exception in  DI call '"+name+"'\n");
    EXEC_ACTION_STOP;
  }

  bool flag_error = false;
  if (get_res) {
    // rudimentary variables conversion...
    if (isArgCStr(sc_sess->di_res)) 
      sc_sess->var["DI_res"] = sc_sess->di_res.asCStr();
    else if (isArgInt(sc_sess->di_res)) 
      sc_sess->var["DI_res"] = int2str(sc_sess->di_res.asInt());
    else if (isArgArray(sc_sess->di_res)) {
      // copy results to $DI_res0..$DI_resn
      for (size_t i=0;i<sc_sess->di_res.size();i++) {
	switch (sc_sess->di_res.get(i).getType()) {
	case AmArg::CStr: {
	  sc_sess->var["DI_res"+int2str((unsigned int)i)] =
	    sc_sess->di_res.get(i).asCStr();
	} break;
	case AmArg::Int: {
	  sc_sess->var["DI_res"+int2str((unsigned int)i)] =
	    int2str(sc_sess->di_res.get(i).asInt());
	} break;
	default: {
	  ERROR("unsupported AmArg return type!");
	  flag_error = true;
	  sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
	  sc_sess->SET_STRERROR("unsupported AmArg return type");
	}
	}
      }
    } else {
      ERROR("unsupported AmArg return type!");
      flag_error = true;
      sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
      sc_sess->SET_STRERROR("unsupported AmArg return type");
    }
  }
  if (!flag_error) {
    sc_sess->CLR_ERRNO;
  }

} EXEC_ACTION_END;
  

CONST_ACTION_2P(SCB2BConnectCalleeAction,',', false);
EXEC_ACTION_START(SCB2BConnectCalleeAction) {  
  string remote_party = resolveVars(par1, sess, sc_sess, event_params);
  string remote_uri = resolveVars(par2, sess, sc_sess, event_params);
  bool relayed_invite = false;
  VarMapT::iterator it = sc_sess->var.find(DSM_B2B_RELAYED_INVITE);
  if (it != sc_sess->var.end() && it->second == "true")
    relayed_invite = true;
  DBG("B2B connecting callee '%s', URI '%s', relayed: %s\n",
      remote_party.c_str(), remote_uri.c_str(), relayed_invite?"yes":"no");
  sc_sess->B2BconnectCallee(remote_party, remote_uri, relayed_invite);
} EXEC_ACTION_END;
 
EXEC_ACTION_START(SCB2BTerminateOtherLegAction) {
  sc_sess->B2BterminateOtherLeg();
} EXEC_ACTION_END;

CONST_ACTION_2P(SCB2BReinviteAction,',', true);
EXEC_ACTION_START(SCB2BReinviteAction) {
  bool updateSDP = par1=="true";
  sess->sendReinvite(updateSDP, par2);
} EXEC_ACTION_END;

EXEC_ACTION_START(SCB2BSendEstablishedReinviteAction) {
  AmB2BSession* b2b_sess = dynamic_cast<AmB2BSession*>(sess);
  string hdrs = resolveVars(arg, sess, sc_sess, event_params);

  sc_sess->replaceHdrsCRLF(hdrs);

  if (NULL != b2b_sess) {
    b2b_sess->sendEstablishedReInvite(hdrs);
  } else {
    ERROR("internal: Session object is not B2B session (huh?), not reinviting\n");
  }
} EXEC_ACTION_END;

EXEC_ACTION_START(SCB2BEnableEarlyMediaRelayAction) {
  string val = resolveVars(arg, sess, sc_sess, event_params);
  DBG("B2B: %sabling early media SDP relay as re-Invite\n", (val=="true")?"En":"Dis");
  sc_sess->B2BsetRelayEarlyMediaSDP(val=="true");
} EXEC_ACTION_END;

EXEC_ACTION_START(SCB2BAddHeaderAction) {
  string val = resolveVars(arg, sess, sc_sess, event_params);
  DBG("adding B2B header '%s'\n", val.c_str());
  sc_sess->B2BaddHeader(val);
} EXEC_ACTION_END;

CONST_ACTION_2P(SCB2BGetHeaderRequestAction,',', false);
EXEC_ACTION_START(SCB2BGetHeaderRequestAction) {
  string hdr_name = resolveVars(par1, sess, sc_sess, event_params);
  string destination_variable = (par2.length() && par2[0] == '$') ? par2.substr(1) : par2;
  string result;

  if (hdr_name.empty() || destination_variable.empty())
    throw DSMException("core", "cause", "parameters missing");

  sc_sess->B2BgetHeaderRequest(hdr_name, result);

  /* write only if we got something */
  if (!result.empty())
    sc_sess->var[destination_variable] = std::move(result);
  else
    DBG("No header with name '%s' found.\n", hdr_name.c_str());
} EXEC_ACTION_END;

CONST_ACTION_3P(SCB2BGetHeaderParamRequestAction,',', false);
EXEC_ACTION_START(SCB2BGetHeaderParamRequestAction) {
  string hdr_name = resolveVars(par1, sess, sc_sess, event_params);
  string param_name = resolveVars(par2, sess, sc_sess, event_params);
  string destination_variable = (par3.length() && par3[0] == '$') ? par3.substr(1) : par3;
  string result;
  if (hdr_name.empty() || param_name.empty() || destination_variable.empty())
    throw DSMException("core", "cause", "parameters missing");
  sc_sess->B2BgetHeaderParamRequest(hdr_name, param_name, result);
  /* write only if we got something */
  if (!result.empty())
    sc_sess->var[destination_variable] = result;
  else
    DBG("No header param with name '%s' found.\n", hdr_name.c_str());
} EXEC_ACTION_END;

CONST_ACTION_2P(SCB2BGetHeaderReplyAction,',', false);
EXEC_ACTION_START(SCB2BGetHeaderReplyAction) {
  string hdr_name = resolveVars(par1, sess, sc_sess, event_params);
  string destination_variable = (par2.length() && par2[0] == '$') ? par2.substr(1) : par2;
  string result;

  if (hdr_name.empty() || destination_variable.empty())
    throw DSMException("core", "cause", "parameters missing");

  sc_sess->B2BgetHeaderReply(hdr_name, result);

  /* write only if we got something */
  if (!result.empty())
    sc_sess->var[std::move(destination_variable)] = std::move(result);
  else
    DBG("No header with name '%s' found.\n", hdr_name.c_str());
} EXEC_ACTION_END;

CONST_ACTION_3P(SCB2BGetHeaderParamReplyAction,',', false);
EXEC_ACTION_START(SCB2BGetHeaderParamReplyAction) {
  string hdr_name = resolveVars(par1, sess, sc_sess, event_params);
  string param_name = resolveVars(par2, sess, sc_sess, event_params);
  string destination_variable = (par3.length() && par3[0] == '$') ? par3.substr(1) : par3;
  string result;
  if (hdr_name.empty() || param_name.empty() || destination_variable.empty())
    throw DSMException("core", "cause", "parameters missing");
  sc_sess->B2BgetHeaderParamReply(hdr_name, param_name, result);
  /* write only if we got something */
  if (!result.empty())
    sc_sess->var[std::move(destination_variable)] = std::move(result);
  else
    DBG("No header param with name '%s' found.\n", hdr_name.c_str());
} EXEC_ACTION_END;

EXEC_ACTION_START(SCB2BRemoveHeaderAction) {
  string val = resolveVars(arg, sess, sc_sess, event_params);
  DBG("removing B2B header '%s'\n", val.c_str());
  sc_sess->B2BremoveHeader(val);
} EXEC_ACTION_END;

CONST_ACTION_2P(SCB2BSetHeadersAction,',', true);
EXEC_ACTION_START(SCB2BSetHeadersAction) {
  string val = resolveVars(par1, sess, sc_sess, event_params);
  string repl = resolveVars(par2, sess, sc_sess, event_params);
  bool replace_crlf = false;
  if (repl == "true")
    replace_crlf = true;
  DBG("setting B2B headers to '%s' (%sreplacing CRLF)\n", 
      val.c_str(), replace_crlf?"":"not ");
  sc_sess->B2BsetHeaders(val, replace_crlf);
} EXEC_ACTION_END;

EXEC_ACTION_START(SCB2BClearHeadersAction) {
  DBG("clearing B2B headers\n");
  sc_sess->B2BclearHeaders();
} EXEC_ACTION_END;

CONST_ACTION_2P(SCSendDTMFAction,',', true);
EXEC_ACTION_START(SCSendDTMFAction) {
  string event = resolveVars(par1, sess, sc_sess, event_params);
  string duration = resolveVars(par2, sess, sc_sess, event_params);  
  
  unsigned int event_i;
  if (str2int(event, event_i)) {
    ERROR("event '%s' not a valid DTMF event\n", event.c_str());
    throw DSMException("core", "cause", "invalid DTMF:"+ event);
  }

  unsigned int duration_i;
  if (duration.empty()) {
    duration_i = 500; // default
  } else {
    if (str2int(duration, duration_i)) {
      ERROR("event duration '%s' not a valid DTMF duration\n", duration.c_str());
      throw DSMException("core", "cause", "invalid DTMF duration:"+ duration);
    }
  }

  sess->sendDtmf(event_i, duration_i);
} EXEC_ACTION_END;

CONST_ACTION_2P(SCSendDTMFSequenceAction,',', true);
EXEC_ACTION_START(SCSendDTMFSequenceAction) {
  string events = resolveVars(par1, sess, sc_sess, event_params);
  string duration = resolveVars(par2, sess, sc_sess, event_params);

  unsigned int duration_i;
  if (duration.empty()) {
    duration_i = 500; // default
  } else {
    if (str2int(duration, duration_i)) {
      ERROR("event duration '%s' not a valid DTMF duration\n", duration.c_str());
      throw DSMException("core", "cause", "invalid DTMF duration:"+ duration);
    }
  }

  for (size_t i=0;i<events.length();i++) {
    if ((events[i]<'0' || events[i]>'9')
	&& (events[i] != '#') && (events[i] != '*')
	&& (events[i] <'A' || events[i] >'F')) {
	DBG("skipping non-DTMF event char '%c'\n", events[i]);
	continue;
    }
    int event = events[i] - '0';
    if (events[i] == '*')
      event = 10;
    else if (events[i] == '#')
      event = 11;
    else if (events[i] >= 'A' && events[i] <= 'F' )
      event = 12 + (events[i] - 'A');
    DBG("sending event %d duration %u\n", event, duration_i);
    sess->sendDtmf(event, duration_i);
  }
} EXEC_ACTION_END;

EXEC_ACTION_START(SCRegisterEventQueueAction) {
  string q_name = resolveVars(arg, sess, sc_sess, event_params);
  DBG("Registering event queue '%s'\n", q_name.c_str());
  if (q_name.empty()) {
    WARN("Registering empty event queue name!\n");
  }
  AmEventDispatcher::instance()->addEventQueue(q_name, sess);
} EXEC_ACTION_END;

EXEC_ACTION_START(SCUnregisterEventQueueAction) {
  string q_name = resolveVars(arg, sess, sc_sess, event_params);
  DBG("Unregistering event queue '%s'\n", q_name.c_str());
  if (q_name.empty()) {
    WARN("Unregistering empty event queue name!\n");
  }
  AmEventDispatcher::instance()->delEventQueue(q_name);
} EXEC_ACTION_END;

CONST_ACTION_2P(SCCreateSystemDSMAction,',', false);
EXEC_ACTION_START(SCCreateSystemDSMAction) {
  string conf_name = resolveVars(par1, sess, sc_sess, event_params);
  string script_name = resolveVars(par2, sess, sc_sess, event_params);

  if (conf_name.empty() || script_name.empty()) {
    throw DSMException("core", "cause", "parameters missing - "
		       "need both conf_name and script_name for createSystemDSM");
  }

  DBG("creating system DSM conf_name %s, script_name %s\n", 
      conf_name.c_str(), script_name.c_str());
  string status;
  if (!DSMFactory::instance()->createSystemDSM(conf_name, script_name, false, status)) {
    ERROR("creating system DSM: %s\n", status.c_str());
    throw DSMException("core", "cause", status);
  }
  
} EXEC_ACTION_END;

DSMDisposable* getObjectFromVariable(DSMSession* sc_sess, const string& var_name) {
  AVarMapT::iterator it = sc_sess->avar.find(var_name);
  if (it == sc_sess->avar.end()) {
    DBG("object '%s' not found\n", var_name.c_str());
    sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
    sc_sess->SET_STRERROR("object '"+var_name+"' not found\n");
    return NULL;
  }

  DSMDisposable* disp = dynamic_cast<DSMDisposable*>(it->second.asObject());
  if (NULL == disp) {
    DBG("object '%s' is not a DSMDisposable\n", var_name.c_str());
    sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
    sc_sess->SET_STRERROR("object '"+var_name+"' is not a DSMDisposable\n");
    return NULL;
  }
  return disp;
}

EXEC_ACTION_START(SCTrackObjectAction) {
  string var_name = resolveVars(arg, sess, sc_sess, event_params);
  DSMDisposable* disp = getObjectFromVariable(sc_sess, var_name);
  if (NULL == disp) {
    EXEC_ACTION_STOP;
  }
  sc_sess->transferOwnership(disp);
} EXEC_ACTION_END;

EXEC_ACTION_START(SCReleaseObjectAction) {
  string var_name = resolveVars(arg, sess, sc_sess, event_params);
  DSMDisposable* disp = getObjectFromVariable(sc_sess, var_name);
  if (NULL == disp) {
    EXEC_ACTION_STOP;
  }
  sc_sess->releaseOwnership(disp);
} EXEC_ACTION_END;

EXEC_ACTION_START(SCFreeObjectAction) {
  string var_name = resolveVars(arg, sess, sc_sess, event_params);
  DSMDisposable* disp = getObjectFromVariable(sc_sess, var_name);
  if (NULL == disp) {
    EXEC_ACTION_STOP;
  }
  delete disp;
  sc_sess->avar.erase(var_name);
} EXEC_ACTION_END;
