/*******************************************************************************
  copyright   : Copyright (C) 2012-2019, www.peergine.com, All rights reserved.
  filename    : pgCallFlow.js
  description : 
  modify      : create, chenbichao, 2019/01/08
*******************************************************************************/

// Error codes.
var pgCallFlowError = {
	OK: 0,
	NoInit: 1,
	RunTime: 2,
	BadStatus: 3,
	SendFailed: 4,
	BadParam: 5,
	Offline: 6,
	Timeout: 7,
	Network: 8
};

// Terminate codes.
var pgCallFlowTerm = {
	Normal: 0,
	RunTime: 1,
	Busy: 2,
	Reject: 3,
	Cancel: 4,
	NoReply: 5,
	NoAnswer: 6,
	Offline: 7,
	NetFailed: 8
};


function pgCallFlow(oCallback)
{
	// Check callback object.
	if (!oCallback) {
		alert("pgCallFlow: oCallback is null.");
		return null;
	}
	
	if (typeof(oCallback.onMessageSend) != "function") {
		alert("pgCallFlow: oCallback has no 'onMessageSend' method.");
		return null;
	}

	if (typeof(oCallback.onRequest) != "function") {
		alert("pgCallFlow: oCallback has no 'onRequest' method.");
		return null;
	}

	if (typeof(oCallback.onRinging) != "function") {
		alert("pgCallFlow: oCallback has no 'onRinging' method.");
		return null;
	}

	if (typeof(oCallback.onEstablish) != "function") {
		alert("pgCallFlow: oCallback has no 'onEstablish' method.");
		return null;
	}

	if (typeof(oCallback.onTerminate) != "function") {
		alert("pgCallFlow: oCallback has no 'onTerminate' method.");
		return null;
	}

	if (typeof(oCallback.onLogOutput) != "function") {
		alert("pgCallFlow: oCallback has no 'onLogOutput' method.");
	}

	// Set callback interface object.
	this._oCallback = oCallback;


	// initialize the paramters.
	this.Initialize = function(sLocalID, sInfo, iTimeoutNoAnswer, iTimeoutReply) {
		try {
			var iErr = pgCallFlowError.OK;

			if (this._sLocalID == "") {
				// Find empty instance unit. 
				var iCallInd = -1;
				for (var i = 0; i < _pgCallFlowCallback.aInstList.length; i++) {
					if (!_pgCallFlowCallback.aInstList[i]) {
						iCallInd = i;
						break;
					}
				}
				if (iCallInd >= 0) {
					_pgCallFlowCallback.aInstList[iCallInd] = this;
					this._iInd = iCallInd;

					this._sLocalID = sLocalID;
					this._sInfo = sInfo;
					if (iTimeoutNoAnswer > 0) {
						this._iTimeoutNoAnswer = iTimeoutNoAnswer * 1000;
					}
					if (iTimeoutReply > 0) {
						this._iTimeoutReply = iTimeoutReply * 1000;
					}
				}
				else {
					this._OutString("pgCallFlow.Initialize: No empty instance unit.");
					iErr = pgCallFlowError.RunTime;
				}
			}

			return iErr;
		}
		catch (ex) {
			this._OutString("pgCallFlow.Initialize: ex=" + ex.toString());
			return pgCallFlowError.RunTime;
		}
	}

	this.Cleanup = function() {
		try {
			if (this._iInd >= 0) {
				_pgCallFlowCallback.aInstList[this._iInd] = null;
				this._iInd = -1;
			}

			this._TimerCancelNoAnswer();
			this._TimerCancelReply();

			this._StatusReset();
			this._sLocalID = "";
			this._sInfo = "";
		}
		catch (ex) {
			this._OutString("pgCallFlow.Cleanup: ex=" + ex.toString());
		}
	}

	// Calling handlers.
	this.Request = function(sCallID, sInfo) {
		try {
			var iErr = pgCallFlowError.OK;

			if (this._sLocalID == "") {
				iErr = pgCallFlowError.NoInit;
			}
			else if (this._iCallingSide != -1
				|| (this._iCallStatus != this.CALLING_Idle && this._iCallStatus != this.CALLED_Idle))
			{
				iErr = pgCallFlowError.BadStatus;
			}
			else {
				if (this._TimerStartNoAnswer()) {
					var iSessionID = this._SessionGen(this._iSessionID);
					var sData = this._MessageBuild(iSessionID, this._sLocalID,
						sCallID, this.ACTION_Request, sInfo);
					if (this._MessageSendCallback(sCallID, sData)) {
						this._iCallingSide = 1;
						this._iCallStatus = this.CALLING_Requesting;
						this._iSessionID = iSessionID;
						this._sCallID = sCallID;
					}
					else {
						this._TimerCancelNoAnswer();
						iErr = pgCallFlowError.SendFailed;
					}
				}
				else {
					iErr = pgCallFlowError.RunTime;
				}
			}

			return iErr;
		}
		catch (ex) {
			this._OutString("pgCallFlow.Request: ex=" + ex.toString());
			return pgCallFlowError.RunTime;
		}
	}

	this.Cancel = function(sInfo) {
		try {
			var iErr = pgCallFlowError.OK;
			var sCallID = "";

			if (this._sLocalID == "") {
				iErr = pgCallFlowError.NoInit;
			}
			else if (this._iCallingSide != 1
				|| (this._iCallStatus != this.CALLING_Requesting && this._iCallStatus != this.CALLING_Ringing))
			{
				iErr = pgCallFlowError.BadStatus;
			}
			else {
				this._TimerCancelNoAnswer();
				var sData = this._MessageBuild(this._iSessionID, this._sLocalID,
					this._sCallID, this.ACTION_Cancel, sInfo);
				this._MessageSendCallback(this._sCallID, sData);
				sCallID = this._StatusReset();
			}

			if (sCallID != "") {
				this._EventCallback(this.EVENT_Terminate, sCallID, "", pgCallFlowTerm.Cancel);
			}

			return iErr;
		}
		catch (ex) {
			this._OutString("pgCallFlow.Cancel: ex=" + ex.toString());
			return pgCallFlowError.RunTime;
		}
	}

	this.Accept = function(sInfo) {
		try {
			var iErr = pgCallFlowError.OK;
			var sCallID = "";

			if (this._sLocalID == "") {
				iErr = pgCallFlowError.NoInit;
			}
			else if (this._iCallingSide != 0 || this._iCallStatus != this.CALLED_Requesting) {
				iErr = pgCallFlowError.BadStatus;
			}
			else {
				this._TimerCancelNoAnswer();
				if (this._TimerStartReply()) {
					var sData = this._MessageBuild(this._iSessionID, this._sCallID,
						this._sLocalID, this.ACTION_Accept, sInfo);
					if (this._MessageSendCallback(this._sCallID, sData)) {
						this._iCallStatus = this.CALLED_Establishing;
					}
					else {
						this._TimerCancelReply();
						sCallID = this._StatusReset();
						iErr = pgCallFlowError.SendFailed;
					}
				}
				else {
					sCallID = this._StatusReset();
					iErr = pgCallFlowError.RunTime;
				}
			}

			if (sCallID != "") {
				var iTermCode = (iErr == pgCallFlowError.SendFailed) ? pgCallFlowTerm.NetFailed : pgCallFlowTerm.RunTime;
				this._EventCallback(this.EVENT_Terminate, sCallID, "", iTermCode);
			}

			return iErr;
		}
		catch (ex) {
			this._OutString("pgCallFlow.Accept: ex=" + ex.toString());
			return pgCallFlowError.RunTime;
		}
	}

	this.Reject = function(sInfo) {
		try {
			var iErr = pgCallFlowError.OK;
			var sCallID = "";

			if (this._sLocalID == "") {
				iErr = pgCallFlowError.NoInit;
			}
			else if (this._iCallingSide != 0 || this._iCallStatus != this.CALLED_Requesting) {
				iErr = pgCallFlowError.BadStatus;
			}
			else {
				this._TimerCancelNoAnswer();
				var sData = this._MessageBuild(this._iSessionID, this._sCallID,
					this._sLocalID, this.ACTION_Reject, sInfo);
				this._MessageSendCallback(this._sCallID, sData);
				sCallID = this._StatusReset();
			}

			if (sCallID != "") {
				this._EventCallback(this.EVENT_Terminate, sCallID, "", pgCallFlowTerm.Reject);
			}

			return iErr;
		}
		catch (ex) {
			this._OutString("pgCallFlow.Reject: ex=" + ex.toString());
			return pgCallFlowError.RunTime;
		}
	}

	this.Terminate = function(sInfo) {
		try {
			var iErr = pgCallFlowError.OK;
			var sCallID = "";

			if (this._sLocalID == "") {
				iErr = pgCallFlowError.NoInit;
			}
			else if (this._iCallingSide == 0) {
				if (this._iCallStatus != this.CALLED_Establishing && this._iCallStatus != this.CALLED_Established) {
					iErr = pgCallFlowError.BadStatus;
				}
				else {
					this._TimerCancelReply();
					if (this._TimerStartReply()) {
						var sData = this._MessageBuild(this._iSessionID, this._sCallID,
							this._sLocalID, this.ACTION_TermRequest, sInfo);
						if (this._MessageSendCallback(this._sCallID, sData)) {
							this._iCallStatus = this.CALLED_Terminating;
						}
						else {
							this._TimerCancelReply();
							sCallID = this._StatusReset();
							iErr = pgCallFlowError.SendFailed;
						}
					}
					else {
						sCallID = this._StatusReset();
						iErr = pgCallFlowError.RunTime;
					}
				}
			}
			else if (this._iCallingSide == 1) {
				if (this._iCallStatus != this.CALLED_Established) {
					iErr = pgCallFlowError.BadStatus;
				}
				else {
					if (this._TimerStartReply()) {
						var sData = this._MessageBuild(this._iSessionID, this._sLocalID,
							this._sCallID, this.ACTION_TermRequest, sInfo);
						if (this._MessageSendCallback(this._sCallID, sData)) {
							this._iCallStatus = this.CALLING_Terminating;
						}
						else {
							this._TimerCancelReply();
							sCallID = this._StatusReset();
							iErr = pgCallFlowError.SendFailed;
						}
					}
					else {
						sCallID = this._StatusReset();
						iErr = pgCallFlowError.RunTime;
					}
				}
			}
			else {
				iErr = pgCallFlowError.BadStatus;
			}

			if (sCallID != "") {
				var iTermCode = (iErr == pgCallFlowError.SendFailed) ? pgCallFlowTerm.NetFailed : pgCallFlowTerm.RunTime;
				this._EventCallback(this.EVENT_Terminate, sCallID, "", iTermCode);
			}

			return iErr;
		}
		catch (ex) {
			this._OutString("pgCallFlow.Terminate: ex=" + ex.toString());
			return pgCallFlowError.RunTime;
		}
	}


	// Process the receiving message.
	this.MessageProc = function(sCallID, sData) {
		try {
			var msgData = this._MessageParse(sData);
			if (msgData == null || !msgData.bResult) {
				return;
			}

			var iEventCode = -1;
			var iEventTermCode = -1;
			var sEventCallID = "";
			var sEventInfo = "";

			switch (msgData.iAction) {
			case this.ACTION_Request:
				if (this._iCallingSide < 0) {
					if (this._TimerStartNoAnswer()) {
						var sDataReply = this._MessageBuild(msgData.iSessionID,
							msgData.sCallerID, msgData.sCalledID, this.ACTION_Ringing, this._sInfo);
						if (this._MessageSendCallback(msgData.sCallerID, sDataReply)) {
							this._iCallingSide = 0;
							this._iCallStatus = this.CALLED_Requesting;
							this._iSessionID = msgData.iSessionID;
							this._sCallID = msgData.sCallerID;

							iEventCode = this.EVENT_Request;
							sEventCallID = msgData.sCallerID;
							sEventInfo = msgData.sInfo;
						}
						else {
							this._TimerCancelNoAnswer();
							this._OutString("pgCallFlow.MessageProc: send 'ringing' message failed!");
						}
					}
					else {
						var sDataReply = this._MessageBuild(msgData.iSessionID,
							msgData.sCallerID, msgData.sCalledID, this.ACTION_Reject, this._sInfo);
						this._MessageSendCallback(msgData.sCallerID, sDataReply);
						this._OutString("pgCallFlow.MessageProc: start timer failed!");
					}
				}
				else {
					if (msgData.iSessionID != this._iSessionID) {
						var sDataReply = this._MessageBuild(msgData.iSessionID,
							msgData.sCallerID, msgData.sCalledID, this.ACTION_Busy, msgData.sInfo);
						this._MessageSendCallback(msgData.sCallerID, sDataReply);
					}
					else {
						this._OutString("pgCallFlow.MessageProc: duplicate 'request' message!");
					}
				}
				break;

			case this.ACTION_Busy:
				if (this._iCallingSide == 1
					&& msgData.iSessionID == this._iSessionID
					&& this._iCallStatus == this.CALLING_Requesting)
				{
					this._TimerCancelNoAnswer();
					this._StatusReset();

					iEventCode = this.EVENT_Terminate;
					iEventTermCode = pgCallFlowTerm.Busy;
					sEventCallID = msgData.sCalledID;
					sEventInfo = msgData.sInfo;
				}
				break;

			case this.ACTION_Ringing:
				if (this._iCallingSide == 1
					&& msgData.iSessionID == this._iSessionID
					&& this._iCallStatus == this.CALLING_Requesting)
				{
					this._iCallStatus = this.CALLING_Ringing;
					iEventCode = this.EVENT_Ringing;
					sEventCallID = msgData.sCalledID;
					sEventInfo = msgData.sInfo;
				}
				break;

			case this.ACTION_Accept:
				if (this._iCallingSide == 1
					&& msgData.iSessionID == this._iSessionID
					&& this._iCallStatus == this.CALLING_Ringing)
				{
					this._TimerCancelNoAnswer();

					var sDataReply = this._MessageBuild(msgData.iSessionID,
						msgData.sCallerID, msgData.sCalledID, this.ACTION_Establish, this._sInfo);
					if (this._MessageSendCallback(this._sCallID, sDataReply)) {
						this._iCallStatus = this.CALLING_Established;
						iEventCode = this.EVENT_Establish;
						sEventCallID = msgData.sCalledID;
						sEventInfo = msgData.sInfo;
					}
					else {
						this._StatusReset();
						iEventCode = this.EVENT_Terminate;
						iEventTermCode = pgCallFlowTerm.NetFailed;
						sEventCallID = msgData.sCalledID;
						sEventInfo = msgData.sInfo;
					}
				}
				break;

			case this.ACTION_Reject:
				if (this._iCallingSide == 1
					&& msgData.iSessionID == this._iSessionID
					&& this._iCallStatus == this.CALLING_Ringing)
				{
					this._TimerCancelNoAnswer();
					this._StatusReset();

					iEventCode = this.EVENT_Terminate;
					iEventTermCode = pgCallFlowTerm.Reject;
					sEventCallID = msgData.sCalledID;
					sEventInfo = msgData.sInfo;
				}
				break;

			case this.ACTION_Cancel:
				if (this._iCallingSide == 0
					&& msgData.iSessionID == this._iSessionID
					&& (this._iCallStatus == this.CALLED_Requesting || this._iCallStatus == this.CALLED_Establishing))
				{
					this._TimerCancelNoAnswer();
					this._TimerCancelReply();
					this._StatusReset();

					iEventCode = this.EVENT_Terminate;
					iEventTermCode = pgCallFlowTerm.Cancel;
					sEventCallID = msgData.sCallerID;
					sEventInfo = msgData.sInfo;
				}
				break;

			case this.ACTION_NoAnswer:
				if (this._iCallingSide == 1
					&& msgData.iSessionID == this._iSessionID
					&& this._iCallStatus == this.CALLING_Ringing)
				{
					this._TimerCancelNoAnswer();
					this._StatusReset();

					iEventCode = this.EVENT_Terminate;
					iEventTermCode = pgCallFlowTerm.NoAnswer;
					sEventCallID = msgData.sCalledID;
					sEventInfo = msgData.sInfo;
				}
				break;

			case this.ACTION_Establish:
				if (this._iCallingSide == 0
					&& msgData.iSessionID == this._iSessionID
					&& this._iCallStatus == this.CALLED_Establishing)
				{
					this._TimerCancelReply();

					this._iCallStatus = this.CALLED_Established;
					iEventCode = this.EVENT_Establish;
					sEventCallID = msgData.sCallerID;
					sEventInfo = msgData.sInfo;
				}
				break;

			case this.ACTION_TermRequest:
				var sDataReply = this._MessageBuild(msgData.iSessionID,
					msgData.sCallerID, msgData.sCalledID, this.ACTION_TermConfirm, "");
				this._MessageSendCallback(sCallID, sDataReply);

				if (this._iCallingSide == 0
					&& msgData.iSessionID == this._iSessionID
					&& this._iCallStatus == this.CALLED_Established)
				{
					this._StatusReset();
					iEventCode = this.EVENT_Terminate;
					iEventTermCode = pgCallFlowTerm.Normal;
					sEventCallID = msgData.sCallerID;
					sEventInfo = msgData.sInfo;
				}
				else if (this._iCallingSide == 1
					&& msgData.iSessionID == this._iSessionID
					&& (this._iCallStatus == this.CALLING_Ringing || this._iCallStatus == this.CALLING_Established))
				{
					this._StatusReset();
					iEventCode = this.EVENT_Terminate;
					iEventTermCode = pgCallFlowTerm.Normal;
					sEventCallID = msgData.sCalledID;
					sEventInfo = msgData.sInfo;
				}
				break;

			case this.ACTION_TermConfirm:
				if (this._iCallingSide == 0
					&& msgData.iSessionID == this._iSessionID
					&& this._iCallStatus == this.CALLED_Terminating)
				{
					this._TimerCancelReply();
					this._StatusReset();

					iEventCode = this.EVENT_Terminate;
					iEventTermCode = pgCallFlowTerm.Normal;
					sEventCallID = msgData.sCallerID;
					sEventInfo = msgData.sInfo;
				}
				else if (this._iCallingSide == 1
					&& msgData.iSessionID == this._iSessionID
					&& this._iCallStatus == this.CALLING_Terminating)
				{
					this._TimerCancelReply();
					this._StatusReset();

					iEventCode = this.EVENT_Terminate;
					iEventTermCode = pgCallFlowTerm.Normal;
					sEventCallID = msgData.sCalledID;
					sEventInfo = msgData.sInfo;
				}
				break;

			default:
				this._OutString("pgCallFlow.MessageProc: receive invalid message!");
				break;
			}

			this._EventCallback(iEventCode, sEventCallID, sEventInfo, iEventTermCode);
		}
		catch (ex) {
			this._OutString("pgCallFlow.MessageProc: ex=" + ex.toString());
		}
	}

	this.MessageError = function(iError) {
		try {
			var iEventCode = -1;
			var iEventTermCode = -1;
			var sEventCallID = "";
			var sEventInfo = "";

			switch (iError) {
			case pgCallFlowError.BadParam:
			case pgCallFlowError.Timeout:
			case pgCallFlowError.Network:
				if ((this._iCallingSide == 0 && this._iCallStatus != this.CALLED_Idle)
					|| (this._iCallingSide == 1 && this._iCallStatus != this.CALLING_Idle))
				{
					this._TimerCancelNoAnswer();
					this._TimerCancelReply();

					sEventCallID = this._StatusReset();
					iEventCode = this.EVENT_Terminate;
					iEventTermCode = pgCallFlowTerm.NetFailed;
				}
				break;

			case pgCallFlowError.Offline:
				if ((this._iCallingSide == 0 && this._iCallStatus != this.CALLED_Idle)
					|| (this._iCallingSide == 1 && this._iCallStatus != this.CALLING_Idle))
				{
					this._TimerCancelNoAnswer();
					this._TimerCancelReply();

					sEventCallID = this._StatusReset();
					iEventCode = this.EVENT_Terminate;
					iEventTermCode = pgCallFlowTerm.Offline;
				}
				break;
			}

			this._EventCallback(iEventCode, sEventCallID, sEventInfo, iEventTermCode);
		}
		catch (ex) {
			this._OutString("pgCallFlow.MessageError: ex=" + ex.toString());
		}
	}

	this.GetErrorText = function(iError) {
		switch (iError) {
		case pgCallFlowError.OK:
			return "Success";

		case pgCallFlowError.NoInit:
			return "Not initialize";

		case pgCallFlowError.RunTime:
			return "Run time error";

		case pgCallFlowError.BadStatus:
			return "Invalid status";

		case pgCallFlowError.SendFailed:
			return "Send message failed";

		case pgCallFlowError.BadParam:
			return "Invalid parameters";

		case pgCallFlowError.Offline:
			return "Other side is offline";

		case pgCallFlowError.Timeout:
			return "Request timeout";

		case pgCallFlowError.Network:
			return "Network error";

		default:
			return "Unknown error";
		}
	}
	
	this.GetTerminateText = function(iTermCode) {
		switch (iTermCode) {
		case pgCallFlowTerm.Normal:
			return "Handup success";

		case pgCallFlowTerm.RunTime:
			return "Run time error";

		case pgCallFlowTerm.Busy:
			return "Other side is busy";

		case pgCallFlowTerm.Reject:
			return "Calling is reject";

		case pgCallFlowTerm.Cancel:
			return "Calling is cancel";

		case pgCallFlowTerm.NoReply:
			return "Calling is no reply";

		case pgCallFlowTerm.NoAnswer:
			return "Calling is no answer";

		case pgCallFlowTerm.Offline:
			return "Other side is offline";

		case pgCallFlowTerm.NetFailed:
			return "Network failed";

		default:
			return "Unknown code";
		}
	}


	//--------------------------------------------------------------------------
	// Private member and method.

	// Call action codes.
	this.ACTION_Null = 0;
	this.ACTION_Request = 1;
	this.ACTION_Busy = 2;
	this.ACTION_Ringing = 3;
	this.ACTION_Accept = 4;
	this.ACTION_Reject = 5;
	this.ACTION_Cancel = 6;
	this.ACTION_NoAnswer = 7;
	this.ACTION_Establish = 8;
	this.ACTION_TermRequest = 9;
	this.ACTION_TermConfirm = 10;

	// Calling status codes.
	this.CALLING_Idle = 0;
	this.CALLING_Requesting = 1;
	this.CALLING_Ringing = 2;
	this.CALLING_Established = 3;
	this.CALLING_Terminating = 4;

	// Called status codes.
	this.CALLED_Idle = 0;
	this.CALLED_Requesting = 1;
	this.CALLED_Establishing = 2;
	this.CALLED_Established = 3;
	this.CALLED_Terminating = 4;

	// Event codes.
	this.EVENT_Request = 0;
	this.EVENT_Ringing = 1;
	this.EVENT_Establish = 2;
	this.EVENT_Terminate = 3;

	// Config parameters.
	this._iInd = -1;
	this._sLocalID = "";
	this._sInfo = "";
	this._iTimeoutNoAnswer = (60 * 1000);
	this._iTimeoutReply = (5 * 1000);

	// Call status members.
	this._iCallingSide = -1;
	this._iCallStatus = 0;
	this._iSessionID = 0;
	this._sCallID = "";
	
	// Call status reset
	this._StatusReset = function() {
		var sCallID = this._sCallID;
		this._iCallingSide = -1;
		this._iCallStatus = 0;
		this._iSessionID = 0;
		this._sCallID = "";
		return sCallID;
	}

	this._MessageBuild = function(iSessionID, sCallerID, sCalledID, iAction, sInfo) {
		var sData = "session=" + iSessionID + "|callerid=" + sCallerID
			+ "|calledid=" + sCalledID + "|action=" + iAction + "|info=" + sInfo;
		return sData;
	}

	this._MessageParse = function(sData) {
		try {
			var msgData = new _MsgData();

			var sList = sData.split("|");
			for (var i = 0; i < sList.length; i++) {
				var sParam = sList[i].split("=", 2);
				if (sParam.length >= 2) {
					if (sParam[0] == "session") {
						msgData.iSessionID = this._ParseInt(sParam[1], 0);
					}
					else if (sParam[0] == "action") {
						msgData.iAction = this._ParseInt(sParam[1], 0);
					}
					else if (sParam[0] == "callerid") {
						msgData.sCallerID = sParam[1];
					}
					else if (sParam[0] == "calledid") {
						msgData.sCalledID = sParam[1];
					}
					else if (sParam[0] == "info") {
						msgData.sInfo = sParam[1];
					}
				}
			}

			if (msgData.iSessionID != 0
				&& msgData.iAction != 0
				&& msgData.sCallerID != ""
				&& msgData.sCalledID != "")
			{
				msgData.bResult = true;
			}
			else {
				this._OutString("pgCallFlow._MessageParse: invalid message format. sData=" + sData);
			}

			return msgData;
		}
		catch (ex) {
			this._OutString("pgCallFlow._MessageParse: ex=" + ex.toString());
			return null;
		}
	}

	// Session id genernal.
	this._SessionGen = function(iSessionIdOld) {
		var iTemp = 0;
		while (true) {
			iTemp = parseInt(Math.random() * 1000000000);
			if (iTemp < 0) {
				iTemp = -iTemp;
			}
			if (iTemp != 0 && iTemp != iSessionIdOld) {
				break;
			}
		}
		return iTemp;
	}

	// Message send callback.
	this._MessageSendCallback = function(sCallID, sData) {
		try {
			return this._oCallback.onMessageSend(sCallID, sData);
		}
		catch (ex) {
			this._OutString("pgCallFlow._MessageSendCallback: 'onMessageSend' ex=" + ex.toString());
			return false;
		}
	}

	// Event process callback, include exception catch.
	this._HandleMessage = function(sParamList) {

		var iEventCode = -1;
		var iTermCode = -1;
		var sCallID = "";
		var sInfo = "";

		var sList = sParamList.split("|");
		for (var i = 0; i < sList.length; i++) {
			var sParam = sList[i].split("=", 2);
			if (sParam.length >= 2) {
				if (sParam[0] == "event") {
					iEventCode = this._ParseInt(sParam[1], 0);
				}
				else if (sParam[0] == "callid") {
					sCallID = sParam[1];
				}
				else if (sParam[0] == "info") {
					sInfo = sParam[1];
				}
				else if (sParam[0] == "termcode") {
					iTermCode = this._ParseInt(sParam[1], 0);
				}
			}
		}

		this._EventCallbackPriv(iEventCode, sCallID, sInfo, iTermCode);
	}

	this._EventCallback = function(iEventCode, sCallID, sInfo, iTermCode) {
		var bHandlerOK = false;
		try {
			var sParamList = "event=" + iEventCode + "|callid=" + sCallID
				+ "|info=" + sInfo + "|termcode=" + iTermCode;
			var sExeScript = "_pgCallFlowCallback.OnHandleMessage" + this._iInd + "('" + sParamList + "');";
			window.setTimeout(sExeScript, 1);
			bHandlerOK = true;
		}
		catch (ex) {
			this._OutString("pgCallFlow._EventCallback: handler send. ex=" + ex.toString());
		}
		
		if (!bHandlerOK) {
			this._EventCallbackPriv(iEventCode, sCallID, sInfo, iTermCode);
		}
	}

	this._EventCallbackPriv = function(iEventCode, sCallID, sInfo, iTermCode) {
		switch (iEventCode) {
		case this.EVENT_Request:
			try {
				this._oCallback.onRequest(sCallID, sInfo);
			}
			catch (ex) {
				this._OutString("pgCallFlow._EventCallbackPriv: 'onRequest' ex=" + ex.toString());
			}
			break;

		case this.EVENT_Ringing:
			try {
				this._oCallback.onRinging(sCallID, sInfo);
			}
			catch (ex) {
				this._OutString("pgCallFlow._EventCallbackPriv: 'onRinging' ex=" + ex.toString());
			}
			break;

		case this.EVENT_Establish:
			try {
				this._oCallback.onEstablish(sCallID, sInfo);
			}
			catch (ex) {
				this._OutString("pgCallFlow._EventCallbackPriv: 'onEstablish' ex=" + ex.toString());
			}
			break;

		case this.EVENT_Terminate:
			try {
				this._oCallback.onTerminate(sCallID, iTermCode, sInfo);
			}
			catch (ex) {
				this._OutString("pgCallFlow._EventCallbackPriv: 'onTerminate' ex=" + ex.toString());
			}
			break;
		}
	}


	//--------------------------------------------------------------------------
	// Timers
	this._iIdTimerNoAnswer = -1;
	this._iIdTimerReply = -1;
	
	this._bTimerNoAnswerRun = false;
	this._bTimerReplyRun = false;

	this._TimerProcNoAnswer = function() {
		try {
			var iEventCode = -1;
			var iEventTermCode = -1;
			var sEventCallID = "";
			var sEventInfo = "";

			if (this._iCallingSide == 0) {
				if (this._iCallStatus == this.CALLED_Requesting) {
					var sDataReply = this._MessageBuild(this._iSessionID,
						this._sCallID, this._sLocalID, this.ACTION_NoAnswer, this._sInfo);
					this._MessageSendCallback(this._sCallID, sDataReply);

					sEventCallID = this._StatusReset();
					iEventCode = this.EVENT_Terminate;
					iEventTermCode = pgCallFlowTerm.NoAnswer;
					sEventInfo = "";
				}
			}
			else if (this._iCallingSide == 1) {
				if (this._iCallStatus == this.CALLING_Requesting
					|| this._iCallStatus == this.CALLING_Ringing)
				{
					var sDataReply = this._MessageBuild(this._iSessionID,
						this._sLocalID, this._sCallID, this.ACTION_Cancel, this._sInfo);
					this._MessageSendCallback(this._sCallID, sDataReply);

					sEventCallID = this._StatusReset();
					iEventCode = this.EVENT_Terminate;
					iEventTermCode = pgCallFlowTerm.NoAnswer;
					sEventInfo = "";
				}
			}

			this._bTimerNoAnswerRun = false;
			this._iIdTimerNoAnswer = -1;

			if (sEventCallID != "") {
				this._EventCallback(iEventCode, sEventCallID, sEventInfo, iEventTermCode);
			}
		}
		catch (ex) {
			this._OutString("pgCallFlow._TimerProcNoAnswer: ex=" + ex.toString());
			this._bTimerNoAnswerRun = false;
			this._iIdTimerNoAnswer = -1;
		}
	}
	
	this._TimerProcReply = function() {
		try {
			var iEventCode = -1;
			var iEventTermCode = -1;
			var sEventCallID = "";
			var sEventInfo = "";

			if (this._iCallingSide == 0) {
				if (this._iCallStatus == this.CALLED_Establishing
					|| this._iCallStatus == this.CALLED_Terminating)
				{
					var sDataReply = this._MessageBuild(this._iSessionID,
						this._sCallID, this._sLocalID, this.ACTION_TermRequest, this._sInfo);
					this._MessageSendCallback(this._sCallID, sDataReply);

					sEventCallID = this._StatusReset();
					iEventCode = this.EVENT_Terminate;
					iEventTermCode = pgCallFlowTerm.NoReply;
					sEventInfo = "";
				}
			}
			else if (this._iCallingSide == 1) {
				if (this._iCallStatus == this.CALLING_Terminating) {
					sEventCallID = this._StatusReset();
					iEventCode = this.EVENT_Terminate;
					iEventTermCode = pgCallFlowTerm.NoReply;
					sEventInfo = "";
				}
			}

			this._bTimerReplyRun = false;
			this._iIdTimerReply = -1;

			if (sEventCallID != "") {
				this._EventCallback(iEventCode, sEventCallID, sEventInfo, iEventTermCode);
			}
		}
		catch (ex) {
			this._OutString("pgCallFlow._TimerProcReply: ex=" + ex.toString());
			this._bTimerReplyRun = false;
			this._iIdTimerReply = -1;
		}
	}

	this._TimerStartNoAnswer = function() {
		try {
			var bResult = false;
			if (!this._bTimerNoAnswerRun) {
				var sExeScript = "_pgCallFlowCallback.OnTimerNoAnswer" + this._iInd + "();";
				this._iIdTimerNoAnswer = window.setTimeout(sExeScript, this._iTimeoutNoAnswer);
				this._bTimerNoAnswerRun = true;
				bResult = true;
			}
			return bResult;
		}
		catch (ex) {
			this._OutString("pgCallFlow._TimerStartNoAnswer: ex=" + ex.toString());
			return false;
		}
	}

	this._TimerStartReply = function() {
		try {
			var bResult = false;
			if (!this._bTimerReplyRun) {
				var sExeScript = "_pgCallFlowCallback.OnTimerReply" + this._iInd + "();";
				this._iIdTimerReply = window.setTimeout(sExeScript, this._iTimeoutReply);
				this._bTimerReplyRun = true;
				bResult = true;
			}
			return bResult;
		}
		catch (ex) {
			this._OutString("pgCallFlow._TimerStartReply: ex=" + ex.toString());
			return false;
		}
	}

	this._TimerCancelNoAnswer = function() {
		try {
			if (this._bTimerNoAnswerRun) {
				if (this._iIdTimerNoAnswer != null) {
					window.clearTimeout(this._iIdTimerNoAnswer);
					this._iIdTimerNoAnswer = -1;
				}
				this._bTimerNoAnswerRun = false;
			}
		}
		catch (ex) {
			this._OutString("pgCallFlow._TimerCancelNoAnswer: ex=" + ex.toString());
			this._bTimerNoAnswerRun = false;
		}
	}

	this._TimerCancelReply = function() {
		try {
			if (this._bTimerReplyRun) {
				if (this._iIdTimerReply != null) {
					window.clearTimeout(this._iIdTimerReply);
					this._iIdTimerReply = -1;
				}
				this._bTimerReplyRun = false;
			}
		}
		catch (ex) {
			this._OutString("pgCallFlow._TimerCancelReply: ex=" + ex.toString());
			this._bTimerReplyRun = false;
		}
	}


	//--------------------------------------------------------------------------
	// Static utilize methods
	this._ParseInt = function(sVal, idefVal) {
		try {
			if (sVal == "") {
				return idefVal;
			}
			return parseInt(sVal);
		}
		catch (ex) {
			return idefVal;
		}
	}

	this._OutString = function(sOut) {
		try {
			this._oCallback.onLogOutput(sOut);
		}
		catch (ex) {
			console.log("pgCallFlow.LogOutput: " + sOut);
		}
	}
}


// Message build and parse.
function _MsgData()
{
	this.bResult = false;
	this.iSessionID = 0;
	this.iAction = 0;
	this.sCallerID = "";
	this.sCalledID = "";
	this.sInfo = "";
}


// Call flow callback.
var _pgCallFlowCallback = {

	aInstList:new Array(null, null, null, null),
	
	OnHandleMessage0:function(sParamList) {
		if (_pgCallFlowCallback.aInstList[0]) {
			_pgCallFlowCallback.aInstList[0]._HandleMessage(sParamList);
		}
	},
	OnTimerNoAnswer0:function() {
		if (_pgCallFlowCallback.aInstList[0]) {
			_pgCallFlowCallback.aInstList[0]._TimerProcNoAnswer();
		}
	},
	OnTimerReply0:function() {
		if (_pgCallFlowCallback.aInstList[0]) {
			_pgCallFlowCallback.aInstList[0]._TimerProcReply();
		}
	},

	OnHandleMessage1:function(sParamList) {
		if (_pgCallFlowCallback.aInstList[1]) {
			_pgCallFlowCallback.aInstList[1]._HandleMessage(sParamList);
		}
	},
	OnTimerNoAnswer1:function() {
		if (_pgCallFlowCallback.aInstList[1]) {
			_pgCallFlowCallback.aInstList[1]._TimerProcNoAnswer();
		}
	},
	OnTimerReply1:function() {
		if (_pgCallFlowCallback.aInstList[1]) {
			_pgCallFlowCallback.aInstList[1]._TimerProcReply();
		}
	},

	OnHandleMessage2:function(sParamList) {
		if (_pgCallFlowCallback.aInstList[2]) {
			_pgCallFlowCallback.aInstList[2]._HandleMessage(sParamList);
		}
	},
	OnTimerNoAnswer2:function() {
		if (_pgCallFlowCallback.aInstList[2]) {
			_pgCallFlowCallback.aInstList[2]._TimerProcNoAnswer();
		}
	},
	OnTimerReply2:function() {
		if (_pgCallFlowCallback.aInstList[2]) {
			_pgCallFlowCallback.aInstList[2]._TimerProcReply();
		}
	},

	OnHandleMessage3:function(sParamList) {
		if (_pgCallFlowCallback.aInstList[3]) {
			_pgCallFlowCallback.aInstList[3]._HandleMessage(sParamList);
		}
	},
	OnTimerNoAnswer3:function() {
		if (_pgCallFlowCallback.aInstList[3]) {
			_pgCallFlowCallback.aInstList[3]._TimerProcNoAnswer();
		}
	},
	OnTimerReply3:function() {
		if (_pgCallFlowCallback.aInstList[3]) {
			_pgCallFlowCallback.aInstList[3]._TimerProcReply();
		}
	}
};

