package org.voicebridge;


import java.util.*;
import java.text.ParseException;
import java.net.*;
import java.io.File;
import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.voip.server.*;
import com.sun.voip.client.*;
import com.sun.voip.*;


public class Application implements  CallEventListener  {

    private static final Logger log = LoggerFactory.getLogger(Application.class);

	private String version = "0.0.0.1";
	private Config config;

	private Map< String, Object > callObjects = new HashMap< String, Object >();
	private Map< String, CallParticipant > callPartipants = new HashMap< String, CallParticipant >();
    private static ArrayList<ConferenceMonitor> conferenceMonitors = new ArrayList<ConferenceMonitor>();
    private static ArrayList<Object> incomingCallListeners = new ArrayList<Object>();
    private static ArrayList<Object> outgoingCallListeners = new ArrayList<Object>();

    public boolean appStart(File pluginDirectory) {

		try{
			String logDir = pluginDirectory.getAbsolutePath() + File.separator + ".." + File.separator + ".." + File.separator + "logs" + File.separator;
			loginfo(String.format("VoiceBridge logs %s", logDir));

			config = Config.getInstance();
			config.initialise();

			String webHome = pluginDirectory.getAbsolutePath()  + File.separator + ".." + File.separator + ".." + File.separator + "resources" + File.separator + "spank" + File.separator + "rayo";

			System.setProperty("com.sun.voip.server.LOGLEVEL", "99");
			System.setProperty("com.sun.voip.server.FIRST_RTP_PORT", "3200");
			System.setProperty("com.sun.voip.server.LAST_RTP_PORT", "3899");
			System.setProperty("com.sun.voip.server.FIRST_VIDEOBRIDGE_RTP_PORT", "3900");
			System.setProperty("com.sun.voip.server.LAST_VIDEOBRIDGE_RTP_PORT", "3999");
			System.setProperty("com.sun.voip.server.Bridge.logDirectory", logDir);
			System.setProperty("com.sun.voip.server.BRIDGE_LOG", "bridge.log");
			System.setProperty("com.sun.voip.server.LOGLEVEL", "99");
			System.setProperty("com.sun.voip.server.PUBLIC_IP_ADDRESS", config.getPublicHost());
			System.setProperty("com.sun.voip.server.PROTOCOL", config.getDefaultProtocol());
			System.setProperty("com.sun.voip.server.SIP_PORT", config.getDefaultSIPPort());
			System.setProperty("com.sun.voip.server.Bridge.recordDirectory", webHome + File.separator + "recordings");
			System.setProperty("com.sun.voip.server.Bridge.soundsDirectory", webHome + File.separator + "sounds");
			System.setProperty("com.sun.voip.server.Bridge.soundPath", "/com/sun/voip/server/sounds:" + webHome + File.separator + "sounds");

			System.setProperty("freetts.voices", "com.sun.speech.freetts.en.us.cmu_us_kal.KevinVoiceDirectory");

			Properties properties = new Properties();

			properties.setProperty("javax.sip.STACK_NAME", "JAIN SIP 1.1");
			properties.setProperty("javax.sip.RETRANSMISSION_FILTER", "on");
			properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "99");
			properties.setProperty("gov.nist.javax.sip.MIN_KEEPALIVE_TIME_SECONDS", "360");
			properties.setProperty("gov.nist.javax.sip.SERVER_LOG", logDir + "sip_server.log");
			properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", logDir + "sip_debug.log");
			properties.setProperty("javax.sip.IP_ADDRESS",config.getPrivateHost());

			Bridge.setPublicHost(config.getPublicHost());
			Bridge.setPrivateHost(config.getPrivateHost());
			Bridge.setBridgeLocation("LCL");

			new SipServer(config, properties);
            FreeTTSClient.initialize();

		} catch (Exception e) {

			e.printStackTrace();
		}
        return true;
    }

    public void appStop()
    {
        loginfo( "VoiceBridge stopping");

		Object service = this;

		synchronized (incomingCallListeners)
		{
			boolean removeIncomingCallHandler = false;

			for (Object theService : incomingCallListeners)
			{
				if (theService == service) {
					removeIncomingCallHandler = true;
				}
			}

			if (removeIncomingCallHandler)
			{
				monitorIncomingCalls(false);
			}

			monitorOutgoingCalls(false);
		}

		synchronized(conferenceMonitors)
		{
			ArrayList<ConferenceMonitor> monitorsToRemove = new ArrayList<ConferenceMonitor>();

			for (ConferenceMonitor m : conferenceMonitors)
			{
				if (service == m.getService())
				{
					monitorsToRemove.add(m);
				}
			}

			for (ConferenceMonitor m : monitorsToRemove)
			{
				loginfo("Removing conference monitor for " + m.getConferenceId());
				conferenceMonitors.remove(m);
			}
		}

		CallHandler.shutdown();
		config.terminate();
    }


    public void callEventNotification(CallEvent callEvent)
    {
 		loginfo( "VoiceBridge callEventNotification " + callEvent.toString());

		if (conferenceMonitors.size() > 0)
		{
			notifyConferenceMonitors(callEvent);
		}

		Application.reportCallEventNotification(null, callEvent, "monitorCallStatus");
    }


    public static void reportCallEventNotification(Object service, CallEvent callEvent, String monitorName)
    {
		if ( service != null )
		{
			String myEvent = CallEvent.getEventString(callEvent.getEvent());
			String callState = callEvent.getCallState().toString();

			String info = callEvent.getInfo() == null ? "" : callEvent.getInfo();
			String dtmf = callEvent.getDtmfKey() == null ? "" : callEvent.getDtmfKey();
			String treatmentdId = callEvent.getTreatmentId() == null ? "" : callEvent.getTreatmentId();
			int noOfCalls = callEvent.getNumberOfCalls();
			String callId = callEvent.getCallId() == null ? "" : callEvent.getCallId();
			String confId = callEvent.getConferenceId() == null ? "" : callEvent.getConferenceId();
			String callInfo = callEvent.getCallInfo() == null ? "" : callEvent.getCallInfo();

			//service.invoke("callsEventNotification", new Object[] {monitorName, myEvent, callState, info, dtmf, treatmentdId, String.valueOf(noOfCalls), callId, confId, callInfo });
 		}
    }

    public static void registerNotification(String status, ProxyCredentials credentials)
    {
		log.info("registerNotification " + status + " " + credentials.getXmppUserName());

		try {
			Config.updateStatus(credentials.getXmppUserName(), status);

		} catch (Exception e) {

			System.out.println("registerNotification " + e);
		}
    }

    public void manageCallParticipant(String uid, String parameter, String value)
    {
    	loginfo("VoiceBridge manageParticipant");

        if ( callPartipants.containsKey(uid) == false)
        {
			callPartipants.put(uid, new CallParticipant());
			reportInfo("Call Participant " + uid + " created");
		}

		CallParticipant cp = callPartipants.get(uid);

		try {
			parseCallParameters(parameter, value, cp, uid);
			reportInfo("manageCallParticipant processing " + parameter + " value: " + value);

		} catch (Exception e) {

			reportError(e.toString());
		}
	}

    public void manageVoiceBridge(String parameter, String value)
    {
    	loginfo("VoiceBridge manageConference");

		try {

			parseBridgeParameters(parameter, value);
			reportInfo("manageVoiceBridge processing " + parameter + " value: " + value);

		} catch (Exception e) {

			reportError(e.toString());
		}
	}

    private void makeOutgoingCall(CallParticipant cp, String uid)
    {
    	loginfo("VoiceBridge makeOutgoingCall");

		try {

			if (validateAndAdjustParameters(cp))
			{
				if (cp.getSecondPartyNumber() == null)
				{
					OutgoingCallHandler outgoingCallHandler = new OutgoingCallHandler(this, cp);
					outgoingCallHandler.start();

					callObjects.put(uid, outgoingCallHandler);

					reportInfo("Outgoing call made to " + cp.getPhoneNumber() + " id: " + cp.getCallId());

				} else {

					TwoPartyCallHandler twoPartyCallHandler = new TwoPartyCallHandler(this, cp);
					twoPartyCallHandler.start();

					callObjects.put(uid, twoPartyCallHandler);

					reportInfo("Two party call made to " + cp.getPhoneNumber() + " id: " + cp.getCallId());
				}
			}

		} catch (Exception e) {

			reportError(e.toString());
		}
	}

    private void migrateCall(CallParticipant cp)
    {
    	loginfo("VoiceBridge migrateCall");

		try {

			if (validateAndAdjustParameters(cp))
			{
				if (cp.migrateCall())
				{
					new CallMigrator(this, cp).start();

					reportInfo("Call migrated to " + cp.getSecondPartyNumber() + " id: " + cp.getCallId());

				} else {

					reportError("Call participant is not configured for migration");
				}
			}

		} catch (Exception e) {

			reportError(e.toString());
		}
	}

	private void reportError(String error)
	{
		logerror("VoiceBridge " + error);
	}

	private void reportInfo(String info)
	{
		loginfo(info);
	}

    private void loginfo( String s ) {

        log.info( s );
        System.out.println( s );
    }

    private void logerror( String s ) {

        log.error( s );
        System.out.println( "[ERROR] " + s );
    }

    private void parseBridgeParameters(String parameter, String value)
    {
		if ("nAvg".equalsIgnoreCase(parameter))
		{
			try {
				LowPassFilter.setNAvg(getInteger(value));
			} catch (Exception e) {
				reportError("parseBridgeParameters : nAvg " + value + " is not numeric" );
			}
			return;
		}

		if ("lpfv".equalsIgnoreCase(parameter))
		{
			try {
				LowPassFilter.setLpfVolumeAdjustment(getDouble(value));
			} catch (Exception e) {
				reportError("parseBridgeParameters : lpfv " + value + " is not numeric" );
			}
			return;
		}

		if ("forceGatewayError".equalsIgnoreCase(parameter))
		{
			try {
				SipTPCCallAgent.forceGatewayError(stringToBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("addCallToWhisperGroup".equalsIgnoreCase(parameter))
		{
			String[] tokens = value.split(":");

			if (tokens.length != 2)
			{
				reportError("You must specify both a whisperGroupId and a callId");
				return;
			}

			String whisperGroupId = tokens[0];
			String callId = tokens[1];

			CallHandler callHandler = CallHandler.findCall(callId);

			if (callHandler == null)
			{
				reportError("Invalid callId:  " + callId);
				return;
			}

			try {
				callHandler.getMember().addCall(whisperGroupId);
			} catch (ParseException e) {
				reportError(e.toString());
			}

			return;
		}

		if ("allowShortNames".equalsIgnoreCase(parameter))
		{
			try {
				ConferenceManager.setAllowShortNames(stringToBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("bridgeLocation".equalsIgnoreCase(parameter))
		{
			Bridge.setBridgeLocation(value);
			return;
		}

		if ("cancelCall".equalsIgnoreCase(parameter))
		{
			CallHandler.hangup(value, "User requested call termination");
			return;
		}

		if ("cancelMigration".equalsIgnoreCase(parameter))
		{
			CallMigrator.hangup(value, "User requested call termination");
			return;
		}

		if ("cnThresh".equalsIgnoreCase(parameter))
		{
			try {
				CallHandler.setCnThresh(getQualifierString(value), getInteger(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
            return;
		}

		if ("comfortNoiseType".equalsIgnoreCase(parameter))
		{
			try {
				MemberSender.setComfortNoiseType(getInteger(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
            return;
		}

		if ("comfortNoiseLevel".equalsIgnoreCase(parameter))
		{
			try {
				RtpPacket.setDefaultComfortNoiseLevel((byte) getInteger(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
            return;
		}

		if ("commonMixDefault".equalsIgnoreCase(parameter))
		{
			try {
				WhisperGroup.setCommonMixDefault(stringToBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("conferenceInfoShort".equalsIgnoreCase(parameter))
		{
			reportInfo(ConferenceManager.getAbbreviatedConferenceInfo());
			return;
		}

		if ("conferenceInfo".equalsIgnoreCase(parameter))
		{
			reportInfo(ConferenceManager.getDetailedConferenceInfo());
			return;
		}

		if ("createConference".equalsIgnoreCase(parameter))
		{
			String[] tokens = value.split(":");

			if (tokens.length < 2)
			{
				reportError("Missing parameters");
				return;
			}

			String conferenceId = tokens[0];

			if (tokens[1].indexOf("PCM") != 0 && tokens[1].indexOf("SPEEX") != 0)
			{
				reportError("invalid media specification");
				return;
			}

			String mediaPreference = tokens[1];

			String displayName = null;

			if (tokens.length > 2)
			{
				displayName = tokens[2];
			}

			try {
				ConferenceManager.createConference(conferenceId, mediaPreference, displayName);
			} catch (ParseException e) {
				reportError(e.toString());
			}

			return;
		}

		if ("createWhisperGroup".equalsIgnoreCase(parameter))
		{
			String[] tokens = value.split(":");

			if (tokens.length < 2)
			{
				reportError("You must specify both a conferenceId and a whisperGroupId");
				return;
			}

			try {
				String conferenceId = tokens[0];
				String whisperGroupId = tokens[1];

				double attenuation = WhisperGroup.getDefaultAttenuation();

				if (tokens.length == 3)
				{
					attenuation = getDouble(tokens[2]);
				}

				ConferenceManager.createWhisperGroup(conferenceId,	whisperGroupId, attenuation);

			} catch (ParseException e) {

				reportError("Can't create Whisper group " + tokens[1] + " " + e.getMessage());
			}
			return;
		}

		if ("deferMixing".equalsIgnoreCase(parameter))
		{
			try {
				MemberReceiver.deferMixing(getBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("destroyWhisperGroup".equalsIgnoreCase(parameter))
		{
			String[] tokens = value.split(":");

			if (tokens.length != 2)
			{
				reportError("You must specify both a conferenceId and a whisperGroupId");
				return;
			}

			try {
				ConferenceManager.destroyWhisperGroup(tokens[0], tokens[1]);
			} catch (ParseException e) {
				reportError(e.toString());
			}

			return;
		}

		if ("callAnswerTimeout".equalsIgnoreCase(parameter))
		{
			try {
				CallSetupAgent.setDefaultCallAnswerTimeout(getInteger(value));
			} catch (Exception e) {
				reportError("callAnswerTimeout " + value + " is not numeric" );
			}
			return;
		}

		if ("doNotRecord".equalsIgnoreCase(parameter))
		{
			try {
				boolean booleanValue = getBoolean(value);
				String callId = getQualifierString(value);

				if (callId == null)
				{
					reportError("Call id is missing");
					return;
				}

				try {

					CallHandler.setDoNotRecord(callId, booleanValue);

				} catch (NoSuchElementException e) {
					reportError("Invalid callId specified:  " + value);
				}

			} catch (Exception e) {
				reportError(e.toString());
			}

			return;
		}

		if ("dtmfSuppression".equalsIgnoreCase(parameter))
		{
			try {
				boolean booleanValue = getBoolean(value);
				String callId = getQualifierString(value);

				if (callId == null)
				{
					reportError("Call id is missing");
					return;
				}

				try {

					CallHandler.setDtmfSuppression(callId, booleanValue);

				} catch (NoSuchElementException e) {
					reportError("Invalid callId specified:  " + value);
				}

			} catch (Exception e) {
				reportError(e.toString());
			}

			return;
		}

		if ("drop".equalsIgnoreCase(parameter))
		{
			try {
				int integerValue = getInteger(value);
				String callId = getQualifierString(value);

				if (callId == null)
				{
					reportError("Call id is missing");
					return;
				}

				try {

					CallHandler.setDropPackets(callId, integerValue);

				} catch (NoSuchElementException e) {
					reportError("Invalid callId specified:  " + value);
				}

			} catch (Exception e) {
				reportError(e.toString());
			}

			return;
		}

		if ("duplicateCallLimit".equalsIgnoreCase(parameter))
		{
			try {
				CallHandler.setDuplicateCallLimit(getInteger(value));
			} catch (Exception e) {
				reportError("duplicateCallLimit " + value + " is not numeric" );
			}
			return;
		}


		if ("directConferencing".equalsIgnoreCase(parameter))
		{
			try {
				IncomingCallHandler.setDirectConferencing(getBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("distributedConferenceInfo".equalsIgnoreCase(parameter))
		{
			reportInfo(ConferenceManager.getDistributedConferenceInfo());
			return;
		}

		if ("dropDb".equalsIgnoreCase(parameter))
		{
			ConferenceManager.dropDb();
			return;
		}

		if ("enablePSTNCalls".equalsIgnoreCase(parameter))
		{
			try {
				CallHandler.enablePSTNCalls(getBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("endConference".equalsIgnoreCase(parameter))
		{
			try {
				ConferenceManager.endConference(value);

			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("firstRtpPort".equalsIgnoreCase(parameter))
		{
			try {
				ConferenceMember.setFirstRtpPort(getInteger(value));
			} catch (Exception e) {
				reportError("firstRtpPort " + value + " is not numeric" );
			}
			return;
		}

		if ("setForcePrivateMix".equalsIgnoreCase(parameter))
		{
			try {
				MixManager.setForcePrivateMix(getBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("forwardData".equalsIgnoreCase(parameter))
		{
			try {
				String[] tokens = value.split(":");

				if (tokens.length < 2)
				{
					reportError("Missing parameters:  " + value);
					return;
				}

				CallHandler dest = CallHandler.findCall(tokens[0]);

				if (dest == null)
				{
					reportError("Invalid callId:  " + tokens[0]);
					return;
				}

				CallHandler src = CallHandler.findCall(tokens[1]);

				if (src == null)
				{
					reportError("Invalid callId:  " + tokens[1]);
					return;
				}

				src.getMember().getMemberReceiver().addForwardMember(dest.getMember().getMemberSender());

			} catch (Exception e) {
				reportError(e.toString());
			}
			return;
		}


		if ("forwardDtmfKeys".equalsIgnoreCase(parameter))
		{
			try {
				MemberReceiver.setForwardDtmfKeys(getBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("gc".equalsIgnoreCase(parameter))
		{
			System.gc();
			return;
		}

		if ("gcs".equalsIgnoreCase(parameter))
		{
			reportInfo(CallHandler.getCallStateForAllCalls());
			return;
		}

		if ("getCallState".equalsIgnoreCase(parameter))
		{
			String callId = getString(value);

			CallHandler callHandler = CallHandler.findCall(callId);

			if (callHandler == null)
			{
				reportError("Invalid callId:  " + callId);
				return;
			}

			reportInfo(callHandler.getCallState());

			return;
		}

		if ("getAllAbbreviatedMixDescriptors".equalsIgnoreCase(parameter))
		{
			reportInfo(CallHandler.getAllAbbreviatedMixDescriptors());
			return;
		}

		if ("getAbbreviatedMixDescriptors".equalsIgnoreCase(parameter))
		{
			String callId = getString(value);

			CallHandler callHandler = CallHandler.findCall(callId);

			if (callHandler == null)
			{
				reportError("Invalid callId:  " + callId);
				return;
			}

	        reportInfo(callHandler.getMember().getAbbreviatedMixDescriptors());
			return;
		}

		if ("getAllMixDescriptors".equalsIgnoreCase(parameter))
		{
			reportInfo(CallHandler.getAllMixDescriptors());
			return;
		}

		if ("getMixDescriptors".equalsIgnoreCase(parameter))
		{
			String callId = getString(value);

			CallHandler callHandler = CallHandler.findCall(callId);

			if (callHandler == null)
			{
				reportError("Invalid callId:  " + callId);
				return;
			}

	        reportInfo(callHandler.getMember().getMixDescriptors());
			return;
		}

		if ("getStatistics".equalsIgnoreCase(parameter))
		{
			/*
			{ String.valueOf(ConferenceManager.getNumberOfConferences()),
			  String.valueOf(ConferenceManager.getTotalMembers()),
			  String.valueOf(CallHandler.getTotalSpeaking()),
			  String.valueOf(Math.round(ConferenceSender.getTimeBetweenSends() * 10000) / 10000.),
			  String.valueOf(Math.round(ConferenceSender.getAverageSendTime() * 10000) / 10000.),
			  String.valueOf(Math.round(ConferenceSender.getMaxSendTime() * 10000) / 10000.)

			});
			*/
		}

		if ("getBriefConferenceInfo".equalsIgnoreCase(parameter))
		{
			reportInfo(ConferenceManager.getBriefConferenceInfo());
			return;
		}

		if ("gc".equalsIgnoreCase(parameter))
		{
			System.gc();
			return;
		}

		if ("incomingCallTreatment".equalsIgnoreCase(parameter))
		{
			IncomingCallHandler.setIncomingCallTreatment(value);
			return;
		}

		if ("incomingCallVoiceDetection".equalsIgnoreCase(parameter))
		{
			try {
				IncomingCallHandler.setIncomingCallVoiceDetection(getBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("internationalPrefix".equalsIgnoreCase(parameter))
		{
			if (value.equals("\"\"") || value.equals("''"))
			{
				value = "";
			}

			config.setInternationalPrefix(value);
			return;
		}

		if ("internalExtenLength".equalsIgnoreCase(parameter))
		{
			try {
				config.setInternalExtenLength(getInteger(value));
			} catch (Exception e) {
				reportError("internalExtenLength " + value + " is not numeric" );
			}
		}

		if ("conferenceJoinTreatment".equalsIgnoreCase(parameter))
		{
			try {
				String[] tokens = value.split(":");

				if (tokens.length != 2)
				{
				   reportError("conferenceJoinTreatment requires two inputs");
				   return;
				}

				ConferenceManager.setConferenceJoinTreatment(tokens[1], tokens[0]);

			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("conferenceLeaveTreatment".equalsIgnoreCase(parameter))
		{
			try {
				String[] tokens = value.split(":");

				if (tokens.length != 2)
				{
				   reportError("conferenceLeaveTreatment requires two inputs");
				   return;
				}

				ConferenceManager.setConferenceLeaveTreatment(tokens[1], tokens[0]);

			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}


		if ("lastRtpPort".equalsIgnoreCase(parameter))
		{
			try {
				ConferenceMember.setLastRtpPort(getInteger(value));
			} catch (Exception e) {
				reportError("lastRtpPort " + value + " is not numeric" );
			}
			return;
		}


		if ("loneReceiverPort".equalsIgnoreCase(parameter))
		{
			try {
				ConferenceManager.setLoneReceiverPort(getInteger(value));
			} catch (Exception e) {
				reportError("loneReceiverPort " + value + " is not numeric" );
			}
			return;
		}

		if ("longDistancePrefix".equalsIgnoreCase(parameter))
		{
			if (value.equals("\"\"") || value.equals("''"))
			{
				value = "";
			}

			config.setLongDistancePrefix(value);
			return;
		}

		if ("migrateToBridge".equalsIgnoreCase(parameter))
		{
			try {
				String[] tokens = value.split(":");

				if (tokens.length != 3)
				{
				   reportError("migrateToBridge requires three inputs");
				   return;
				}

				String bridge = tokens[0];
				String port = tokens[1];
				String callId = tokens[2];

				CallHandler callHandler = CallHandler.findCall(callId);

				if (callHandler == null)
				{
					reportError("Invalid callId: " + callId);
					return;
				}

				CallParticipant cp = callHandler.getCallParticipant();

				if (cp.getInputTreatment() != null)
				{
					cp.setPhoneNumber(null);
				}
/*
				BridgeConnector bridgeConnector;

				int serverPort;

				try {
					serverPort = Integer.parseInt(port);

				} catch (NumberFormatException e) {

					reportError("Invalid bridge server port:  " + port);
					return;
				}

				try {
					bridgeConnector = new BridgeConnector(bridge, serverPort, 5000);

				} catch (IOException e) {
					reportError("Unable to connect to bridge " + bridge	+ " " + e.getMessage());
					return;
				}

				callHandler.suppressStatus(true);

				try {
					String s = cp.getCallSetupRequest();
					s = s.substring(0, s.length() - 1);  // get rid of last new line
					bridgeConnector.sendCommand(s);

				} catch (IOException e) {

					reportError("Unable to send command to bridge:  " + e.getMessage());
					return;
				}

				bridgeConnector.addCallEventListener(requestHandler);

				// XXX need to figure out how to deal with Private Mixes now that the call has moved!
*/
			} catch (Exception e) {
				reportError(e.toString());
			}
			return;
		}

		if ("senderThreads".equalsIgnoreCase(parameter))
		{
			try {
				ConferenceSender.setSenderThreads(getInteger(value));
			} catch (Exception e) {
				reportError("senderThreads " + value + " is not numeric" );
			}
			return;
		}

		if ("minJitterBufferSize".equalsIgnoreCase(parameter))
		{
			try {
				String[] tokens = value.split(":");

				if (tokens.length != 2)
				{
				   reportError("minJitterBufferSize requires two inputs");
				   return;
				}

				int minJitterBufferSize = getInteger(tokens[0]);
				CallHandler callHandler = CallHandler.findCall(tokens[1]);

				if (callHandler == null)
				{
					reportError("Invalid callId:  " + tokens[1]);
					return;
				}

				callHandler.getMember().getMemberReceiver().setMinJitterBufferSize(minJitterBufferSize);

			} catch (Exception e) {
				reportError(e.toString());
			}
			return;
		}

		if ("maxJitterBufferSize".equalsIgnoreCase(parameter))
		{
			try {
				String[] tokens = value.split(":");

				if (tokens.length != 2)
				{
				   reportError("maxJitterBufferSize requires two inputs");
				   return;
				}

				int minJitterBufferSize = getInteger(tokens[0]);
				CallHandler callHandler = CallHandler.findCall(tokens[1]);

				if (callHandler == null)
				{
					reportError("Invalid callId:  " + tokens[1]);
					return;
				}

				callHandler.getMember().getMemberReceiver().setMaxJitterBufferSize(minJitterBufferSize);

			} catch (Exception e) {
				reportError(e.toString());
			}
			return;
		}

		if ("monitorCallStatus".equalsIgnoreCase(parameter))
		{
			try {
				boolean booleanValue = getBoolean(value);
				String callId = getQualifierString(value);

				if (callId == null)
				{
					reportError("Call id is missing");
					return;
				}

				try {
					CallHandler callHandler = CallHandler.findCall(callId);

					if (callHandler == null)
					{
						reportError("No such callId:  " + callId);
						return;
					}

					if (booleanValue == true)
					{
						callHandler.addCallEventListener(this);

					} else {

						callHandler.removeCallEventListener(this);
					}

				} catch (NoSuchElementException e) {
					reportError("Invalid callId specified:  " + value);
				}

			} catch (Exception e) {
				reportError(e.toString());
			}

			return;
		}

		if ("monitorConferenceStatus".equalsIgnoreCase(parameter))
		{
			try {
				boolean booleanValue = getBoolean(value);
				String conferenceId = getQualifierString(value);

				if (conferenceId == null)
				{
					reportError("conferenceId id is missing");
					return;
				}

				monitorConferenceStatus(conferenceId, booleanValue);

			} catch (Exception e) {
				reportError(e.toString());
			}

			return;
		}

		if ("monitorIncomingCalls".equalsIgnoreCase(parameter))
		{
			try {
				boolean booleanValue = getBoolean(value);

				if (monitorIncomingCalls(booleanValue) == false)
				{
					reportInfo("There is already an incoming call monitor!");
					return;
				}

			} catch (Exception e) {
				reportError(e.toString());
			}

			return;
		}

		if ("monitorOutgoingCalls".equalsIgnoreCase(parameter))
		{
			try {
				boolean booleanValue = getBoolean(value);
				monitorOutgoingCalls(booleanValue);

			} catch (Exception e) {
				reportError(e.toString());
			}

			return;
		}

		if ("muteCall".equalsIgnoreCase(parameter))
		{
			try {
				boolean booleanValue = getBoolean(value);
				String callId = getQualifierString(value);

				if (callId == null)
				{
					reportError("Call id is missing");
					return;
				}

				try {
					CallHandler callHandler = CallHandler.findCall(callId);

					if (callHandler == null)
					{
						reportError("No such callId:  " + callId);
						return;
					}

	    			CallHandler.setMuted(callId, booleanValue);

				} catch (NoSuchElementException e) {
					reportError("Invalid callId specified:  " + value);
				}

			} catch (Exception e) {
				reportError(e.toString());
			}

			return;
		}

		if ("muteWhisperGroup".equalsIgnoreCase(parameter))
		{
			try {
				boolean booleanValue = getBoolean(value);
				String callId = getQualifierString(value);

				if (callId == null)
				{
					reportError("Call id is missing");
					return;
				}

				try {
					CallHandler callHandler = CallHandler.findCall(callId);

					if (callHandler == null)
					{
						reportError("No such callId:  " + callId);
						return;
					}

	    			CallHandler.setMuteWhisperGroup(callId, booleanValue);

				} catch (NoSuchElementException e) {
					reportError("Invalid callId specified:  " + value);
				}

			} catch (Exception e) {
				reportError(e.toString());
			}

			return;
		}

		if ("muteConference".equalsIgnoreCase(parameter))
		{
			try {
				boolean booleanValue = getBoolean(value);
				String callId = getQualifierString(value);

				if (callId == null)
				{
					reportError("Call id is missing");
					return;
				}

				try {
					CallHandler callHandler = CallHandler.findCall(callId);

					if (callHandler == null)
					{
						reportError("No such callId:  " + callId);
						return;
					}

	    			CallHandler.setConferenceMuted(callId, booleanValue);

				} catch (NoSuchElementException e) {
					reportError("Invalid callId specified:  " + value);
				}

			} catch (Exception e) {
				reportError(e.toString());
			}

			return;
		}

		if ("numberOfCalls".equalsIgnoreCase(parameter))
		{
			try {
				String[] tokens = value.split(":");

				if (tokens.length != 1)
				{
					reportError("You must specify a conference id");
					return;
				}

				CallEvent event = new CallEvent(CallEvent.NUMBER_OF_CALLS);
				event.setNumberOfCalls(ConferenceManager.getNumberOfMembers(tokens[0]));
				//callEventNotification(event);

			} catch (Exception e) {
				reportError(e.toString());
			}
			return;
		}

		if ("outsideLinePrefix".equalsIgnoreCase(parameter))
		{
			if (value.equals("\"\"") || value.equals("''"))
			{
				value = "";
			}

			config.setOutsideLinePrefix(value);
			return;
		}

		if ("receiverPause".equalsIgnoreCase(parameter))
		{
			try {
				ConferenceReceiver.setReceiverPause(getInteger(value));
			} catch (Exception e) {
				reportError("pause " + value + " is not numeric" );
			}
			return;
		}

		if ("pauseTreatmentToCall".equalsIgnoreCase(parameter))
		{
			try {
				String[] tokens = value.split(":");

				CallHandler callHandler = CallHandler.findCall(tokens[0]);

				if (callHandler == null)
				{
					reportError("Invalid callId:  " + tokens[0]);
					return;
				}

				String treatmentId = null;

				if (tokens.length > 1)
				{
					treatmentId = tokens[1];
				}

           	 	callHandler.getMember().pauseTreatment(treatmentId, true);

			} catch (Exception e) {
				reportError(e.toString());
			}
			return;
		}

		if ("pauseTreatmentToConference".equalsIgnoreCase(parameter))
		{
			try {
				String treatment = getTreatment(value);
				value = value.substring(treatment.length());
				String conferenceId = getQualifierString(value);

				if (conferenceId == null)
				{
					reportError("conferenceId must be specified:  " + value);
					return;
				}

            	ConferenceManager.pauseTreatment(conferenceId, treatment, true);

			} catch (Exception e) {
				reportError(e.toString());
			}
			return;
		}

		if ("playTreatmentToCall".equalsIgnoreCase(parameter))
		{
			try {
				String treatment = getTreatment(value);
				double volume[] = getVolume(value);

				value = value.substring(treatment.length());
				String callId = getQualifierString(value);

				if (callId == null)
				{
					reportError("callId must be specified:  " + value);
					return;
				}

				try {
					CallHandler.playTreatmentToCall(callId, treatment);

				} catch (NoSuchElementException e) {
					reportError("Invalid callId specified:  " + value);
					return;

				} catch (IOException e) {
					reportError("Unable to read treatment file " + treatment + " " + e.getMessage());
					return;
				}

			} catch (Exception e) {
				reportError(e.toString());
			}
			return;
		}

		if ("playTreatmentToConference".equalsIgnoreCase(parameter))
		{
			try {
				String treatment = getTreatment(value);
				double volume[] = getVolume(value);

				value = value.substring(treatment.length());
				String conferenceId = getQualifierString(value);

				if (conferenceId == null)
				{
					reportError("conference Id must be specified:  " + value);
					return;
				}

				try {
					 ConferenceManager.playTreatment(conferenceId, treatment);

				} catch (NoSuchElementException e) {
					reportError("Invalid conference Id specified:  " + value);
					return;
				}

			} catch (Exception e) {
				reportError(e.toString());
			}
			return;
		}

		if ("playTreatmentToAllConferences".equalsIgnoreCase(parameter))
		{
			try {
				String treatment = getTreatment(value);
				double volume[] = getVolume(value);
				value = value.substring(treatment.length());

				try {
					 ConferenceManager.playTreatmentToAllConferences(treatment);

				} catch (Exception e) {
					reportError("Error playing treatment :  " + value);
					return;
				}

			} catch (Exception e) {
				reportError(e.toString());
			}
			return;
		}

		if ("transferCall".equalsIgnoreCase(parameter))
		{
			try {
				String callId = getString(value);

				if (callId == null)
				{
					reportError("Call id is missing");
					return;
				}

				try {
					CallHandler callHandler = CallHandler.findCall(callId);

					if (callHandler == null)
					{
						reportError("No such callId:  " + callId);
						return;
					}

					String conferenceId = getQualifierString(value);
					IncomingCallHandler.transferCall(callId, conferenceId);

				} catch (NoSuchElementException e) {
					reportError("Invalid callId specified:  " + value);
				}

			} catch (Exception e) {
				reportError(e.toString());
			}

			return;
		}

		if ("sendDtmfKey".equalsIgnoreCase(parameter))
		{
			try {
				String callId = getString(value);

				if (callId == null)
				{
					reportError("Call id is missing");
					return;
				}

				try {
					CallHandler callHandler = CallHandler.findCall(callId);

					if (callHandler == null)
					{
						reportError("No such callId:  " + callId);
						return;
					}

					String dtmfKey = getQualifierString(value);
					callHandler.dtmfKeys(dtmfKey);

				} catch (NoSuchElementException e) {
					reportError("Invalid callId specified:  " + value);
				}

			} catch (Exception e) {
				reportError(e.toString());
			}

			return;
		}
	}


    private void parseCallParameters(String parameter, String value, CallParticipant cp, String uid)
    {
		if ("makeCall".equalsIgnoreCase(parameter))
		{
			try {
				makeOutgoingCall(cp, uid);
			} catch (Exception e) {
				reportError(e.toString());
			}
			return;
		}

		if ("migrateCall".equalsIgnoreCase(parameter))
		{
			try {
				migrateCall(cp);
			} catch (Exception e) {
				reportError(e.toString());
			}
			return;
		}

		if ("cancelCall".equalsIgnoreCase(parameter))
		{
			CallHandler.hangup(cp.getCallId(), "User requested call termination");
			return;
		}

		if ("sendDtmfKey".equalsIgnoreCase(parameter))
		{
			try {
				CallHandler callHandler = CallHandler.findCall(cp.getCallId());
				callHandler.dtmfKeys(value);

			} catch (NoSuchElementException e) {
				reportError("Invalid callId specified:  " + value);
			}

			return;
		}

		if ("conferenceJoinTreatment".equalsIgnoreCase(parameter))
		{
			cp.setConferenceJoinTreatment(value);
			return;
		}

		if ("conferenceLeaveTreatment".equalsIgnoreCase(parameter))
		{
			cp.setConferenceLeaveTreatment(value);
			return;
		}

		if ("callAnsweredTreatment".equalsIgnoreCase(parameter))
		{
			 cp.setCallAnsweredTreatment(value);
			return;
		}

		if ("callanswertimeout".equalsIgnoreCase(parameter))
		{
			try {
				cp.setCallAnswerTimeout(getInteger(value));
			} catch (Exception e) {
				reportError("callAnswerTimeout " + value + " is not numeric" );
			}
			return;
		}

		if ("calltimeout".equalsIgnoreCase(parameter))
		{
			try {
				cp.setCallTimeout(getInteger(value) * 1000);
			} catch (Exception e) {
				reportError("callTimeout " + value + " is not numeric" );
			}
			return;
		}

		if ("callendtreatment".equalsIgnoreCase(parameter))
		{
			cp.setCallEndTreatment(value);
			return;
		}

		if ("callestablishedtreatment".equalsIgnoreCase(parameter))
		{
			cp.setCallEstablishedTreatment(value);
			return;
		}

		if ("callid".equalsIgnoreCase(parameter))
		{
			cp.setCallId(value);
			return;
		}

		if ("conferenceid".equalsIgnoreCase(parameter))
		{
			try {
				String[] tokens = value.split(":");

				cp.setConferenceId(tokens[0].trim());

				if (tokens.length > 1) {
					cp.setMediaPreference(tokens[1]);
				}

				if (tokens.length > 2) {
					cp.setConferenceDisplayName(tokens[2]);
				}

			} catch (Exception e) {
				reportError("conferenceId " + value + " is invalid" );
			}
			return;
		}

		if ("displayname".equalsIgnoreCase(parameter))
		{
			cp.setDisplayName(value);
			return;
		}

		if ("distributedbridge".equalsIgnoreCase(parameter))
		{
			try {
				cp.setDistributedBridge(stringToBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("donotrecord".equalsIgnoreCase(parameter))
		{
			try {
				cp.setDoNotRecord(stringToBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("dtmfdetection".equalsIgnoreCase(parameter))
		{
			try {
				cp.setDtmfDetection(stringToBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("dtmfsuppression".equalsIgnoreCase(parameter))
		{
			try {
				cp.setDtmfSuppression(stringToBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("forwarddatafrom".equalsIgnoreCase(parameter))
		{
			cp.setForwardingCallId(value);
			return;
		}

		if ("ignoretelephoneevents".equalsIgnoreCase(parameter))
		{
			try {
				cp.setIgnoreTelephoneEvents(stringToBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("inputtreatment".equalsIgnoreCase(parameter))
		{
			cp.setInputTreatment(value);
			return;
		}

		if ("encryptkey".equalsIgnoreCase(parameter))
		{
			cp.setEncryptionKey(value);
			return;
		}

		if ("encryptalgorithm".equalsIgnoreCase(parameter))
		{
			cp.setEncryptionAlgorithm(value);
			return;
		}

		if ("firstConferenceMemberTreatment".equalsIgnoreCase(parameter))
		{
			cp.setFirstConferenceMemberTreatment(value);
			return;
		}

		if ("handlesessionprogress".equalsIgnoreCase(parameter))
		{
			try {
				cp.setHandleSessionProgress(stringToBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("joinconfirmationkey".equalsIgnoreCase(parameter))
		{
			try {
				MemberReceiver.setJoinConfirmationKey(value);

			} catch (Exception e) {
				reportError(e.toString());
			}
			return;
		}

		if ("joinconfirmationtimeout".equalsIgnoreCase(parameter))
		{
			try {
				cp.setJoinConfirmationTimeout(getInteger(value));
			} catch (Exception e) {
				reportError("callAnswerTimeout " + value + " is not numeric" );
			}
			return;
		}

		if ("mediapreference".equalsIgnoreCase(parameter))
		{
			cp.setMediaPreference(value);
			return;
		}

		if ("migrate".equalsIgnoreCase(parameter))
		{
            String callId = getFirstString(value);
            cp.setCallId(callId);

			/*
			 * The second party number may be a sip address
			 * with colons.  So we treat everything after the
			 * first colon as the second party number.
			 */

			int ix;

			if ((ix = value.indexOf(":")) < 0) {

				reportError("secondPartyNumber must be specified:  " + value);
				return;
			}

            String secondPartyNumber = value.substring(ix + 1);

            if (secondPartyNumber == null)
            {
                reportError("secondPartyNumber must be specified:  " + value);
				return;
            }

			try {
				cp.setSecondPartyNumber(secondPartyNumber);
				cp.setMigrateCall(true);

			} catch (Exception e) {
				reportError(e.toString());
			}

            return;
		}

		if ("mute".equalsIgnoreCase(parameter))
		{
			try {
				cp.setMuted(stringToBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("mutewhispergroup".equalsIgnoreCase(parameter))
		{
			try {
				cp.setMuteWhisperGroup(stringToBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("muteconference".equalsIgnoreCase(parameter))
		{
			try {
				cp.setConferenceMuted(stringToBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("name".equalsIgnoreCase(parameter))
		{
			cp.setName(value);
			return;
		}

		if ("phonenumber".equalsIgnoreCase(parameter))
		{
			cp.setPhoneNumber(value);
			return;
		}

		if ("phonenumberlocation".equalsIgnoreCase(parameter))
		{
			cp.setPhoneNumberLocation(value);
		}

		if ("protocol".equalsIgnoreCase(parameter))
		{
			if (value.equalsIgnoreCase("SIP") == false && value.equalsIgnoreCase("NS") == false && value.equalsIgnoreCase("WebRtc") == false && value.equalsIgnoreCase("Rtmfp") == false && value.equalsIgnoreCase("Speaker") == false)
			{
				reportError("Invalid protocol:  " + value);
				return;
			}

			cp.setProtocol(value);
			return;
		}

		if ("recorder".equalsIgnoreCase(parameter))
		{
			try {
				cp.setRecorder(stringToBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}


		if ("recorddirectory".equalsIgnoreCase(parameter))
		{
			cp.setRecordDirectory(value);
			return;
		}

		if ("remotecallid".equalsIgnoreCase(parameter))
		{
			cp.setRemoteCallId(value);
			return;
		}

		if ("voiceDetectionWhileMuted".equalsIgnoreCase(parameter))
		{
			try {
				cp.setVoiceDetectionWhileMuted(stringToBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("useConferenceReceiverThread".equalsIgnoreCase(parameter))
		{
			try {
				cp.setUseConferenceReceiverThread(stringToBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("voiceDetection".equalsIgnoreCase(parameter))
		{
			try {
				cp.setVoiceDetection(stringToBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("whisperGroup".equalsIgnoreCase(parameter))
		{
			cp.setWhisperGroupId(value);
			return;
		}
		if ("voipGateway".equalsIgnoreCase(parameter))
		{
			cp.setVoIPGateway(value);
			return;
		}

		if ("secondPartyCallId".equalsIgnoreCase(parameter))
		{
			cp.setSecondPartyCallId(value);
			return;
		}
		if ("sipProxy".equalsIgnoreCase(parameter))
		{
			cp.setSipProxy(value);
			return;
		}

		if ("secondPartyCallId".equalsIgnoreCase(parameter))
		{
			cp.setSecondPartyCallId(value);
			return;
		}
		if ("secondPartyCallEndTreatment".equalsIgnoreCase(parameter))
		{
			cp.setSecondPartyCallEndTreatment(value);
			return;
		}

		if ("secondPartyName".equalsIgnoreCase(parameter))
		{
			cp.setSecondPartyName(value);
			return;
		}

		if ("secondPartyNumber".equalsIgnoreCase(parameter))
		{
			cp.setSecondPartyNumber(value);
			return;
		}

		if ("secondPartyTreatment".equalsIgnoreCase(parameter))
		{
			cp.setSecondPartyTreatment(value);
			return;
		}

		if ("secondpartyVoiceDetection".equalsIgnoreCase(parameter))
		{
			try {
				cp.setSecondPartyVoiceDetection(stringToBoolean(value));
			} catch (ParseException e) {
				reportError(e.toString());
			}
			return;
		}

		if ("secondPartyTimeout".equalsIgnoreCase(parameter))
		{
			try {
				cp.setSecondPartyTimeout(getInteger(value));
			} catch (Exception e) {
				reportError("setSecondPartyTimeout " + value + " is not numeric" );
			}
			return;
		}
    }

    private String getString(String value)
    {
		int n;

		if ((n = value.lastIndexOf(":")) > 0) {
			value = value.substring(0, n);
		}

		return value;
    }

    private double[] getVolume(String value) throws ParseException
    {
		String v = new String(value);

		int n;

			if ((n = v.indexOf(":volume=")) < 0) {
			return null;
		}

		v = v.substring(n + 8);

		String[] tokens = v.split(":");

		double[] volume = new double[tokens.length];

		for (int i = 0; i < volume.length; i++)
		{
			try {
				volume[i] = Double.parseDouble(tokens[i]);

			} catch (NumberFormatException e) {

			throw new ParseException("Invalid floating point value: "	+ tokens[i], 0);
			}
		}

		return volume;
    }

    private String getTreatment(String value)
    {
		String v = new String(value);

		int n;

		if ((n = v.indexOf(":volume=")) >= 0)
		{
			v = v.substring(0, n);
		}

		if ((n = v.lastIndexOf(":")) < 0) {
			/*
			 * There's no ":", so the whole string is the treatment
			 */
			return v;
		}

		String s = v.substring(0, n);

		if (s.equalsIgnoreCase("f") || s.equalsIgnoreCase("file")
			   || s.equalsIgnoreCase("d") || s.equalsIgnoreCase("dtmf")
			   || s.equalsIgnoreCase("t") || s.equalsIgnoreCase("tts")) {

			/*
			 * The only ":" is preceded by the type of treatment.
			 * The whole string is the treatment.
			 */
			return v;
		}

		/*
		 * The treatment is the string up to the last ":".
		 */
		return s;
    }

    private boolean getBoolean(String value) throws ParseException
    {
		int n;

		if ((n = value.indexOf(":")) > 0) {
			value = value.substring(0, n);
		}

		return stringToBoolean(value);
    }


    private boolean stringToBoolean(String value) throws ParseException
    {
		if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("t")) {
			return true;
		}

		if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("F")) {
			return false;
		}

        throw new ParseException("Invalid boolean value, must be true or false:  " + value, 0);
    }

    private int getInteger(String value) throws ParseException
    {
		int n;

		if ((n = value.indexOf(":")) > 0) {
			value = value.substring(0, n);
		}

		int i = 0;

		try {
				i = Integer.parseInt(value);
		} catch (NumberFormatException e) {
			throw new ParseException("Invalid integer value: " + value, 0);
		}

		return i;
    }

    private String getQualifierString(String value)
    {
		int n;

		if (value == null) {
			return null;
		}

		if ((n = value.lastIndexOf(":")) >= 0) {
			return value.substring(n+1);
		}

		return null;
    }

    private double getDouble(String value) throws ParseException
    {
		int n;

		if ((n = value.indexOf(":")) > 0) {
			value = value.substring(0, n);
		}

		double f = 0.0;

		try {
				f = Double.parseDouble(value);
		} catch (NumberFormatException e) {
			throw new ParseException("Invalid double value: " + value, 0);
		}

		return f;
    }

    private String getFirstString(String value)
    {
		int ix;

		if ((ix = value.indexOf(":")) < 0) {
			return value;
		}

		return value.substring(0, ix);
    }

    private boolean validateAndAdjustParameters(CallParticipant cp) throws ParseException
    {
		String callId = cp.getCallId();

		if (callId == null) {
			cp.setCallId(CallHandler.getNewCallId());

		} else {

			if (callId.equals("0"))
			{
				reportError("Zero is an invalid callId");
				return false;
			}

			if (cp.migrateCall() == false)
			{
				CallHandler callHandler = CallHandler.findCall(callId);

				if (callHandler != null)
				{
					if (callHandler.isCallEnding() == false)
					{
						reportError("CallId " + callId + " is already in use");
						return false;

					} else {
						reportError("Reusing callId for ending call " + callId);
					}
				}
			}
		}

        handleCallAttendant(cp);

		cp.setSecondPartyNumber(config.formatPhoneNumber(cp.getSecondPartyNumber(), cp.getPhoneNumberLocation()));

		if (cp.migrateCall() == false)
		{
			if (cp.getProtocol() == null || "SIP".equals(cp.getProtocol()))
			{
				cp.setPhoneNumber(config.formatPhoneNumber(cp.getPhoneNumber(), cp.getPhoneNumberLocation()));

				if (cp.getPhoneNumber() == null)
				{
					if (cp.getInputTreatment() == null)
					{
						reportError("You must specify a phone number or a soft phone URI");
						return false;

					} else {

						if (cp.getInputTreatment().equals("null"))
						{
							cp.setInputTreatment("");
						}

						cp.setPhoneNumber(cp.getInputTreatment());
						cp.setProtocol("NS");
					}
				}
			}
		}

        if (cp.getName() == null || cp.getName().equals(""))
        {
            cp.setName("Anonymous");
		}

		if (cp.migrateCall() == true)
		{
	    	if (cp.getCallId() == null || cp.getSecondPartyNumber() == null)
	    	{
				reportError("You must specify old and new phone numbers to migrate a call");
				return false;
	    	}
		}

		if (cp.getConferenceId() == null &&	cp.getSecondPartyNumber() == null)
		{
			reportError("You must specify a conference Id");
			return false;
		}

		if (cp.getDisplayName() == null)
		{
			if (cp.getSecondPartyNumber() == null)
			{
				cp.setDisplayName(cp.getConferenceId());

			} else {

				if (cp.getSecondPartyName() != null)
				{
					cp.setDisplayName(cp.getSecondPartyName());
				} else {
					cp.setDisplayName(cp.getSecondPartyNumber());
				}
			}
		}

		/*
		 * For two party calls.
		 */

		if (cp.getConferenceId() != null)
		{
			if (cp.getSecondPartyTreatment() != null)
			{
				cp.setConferenceJoinTreatment(cp.getSecondPartyTreatment());
			}

			if (cp.getSecondPartyCallEndTreatment() != null)
			{
				cp.setConferenceLeaveTreatment(
				cp.getSecondPartyCallEndTreatment());
			}
		}

		if (cp.getSecondPartyNumber() != null)
		{
			if (cp.getConferenceId() == null)
			{
				cp.setConferenceId(cp.getPhoneNumber());
			}
		}

		return true;
    }


    /*
     * Some Sun sites such as China have an automated call attendent
     * which asks the user to enter the extension again.
     *
     * If a phone number has ">" in it, we replace the phone number
     * to call with everything before the ">" and set the call
     * answer treatment to be dtmf keys of everything after the ">".
     */

    private void handleCallAttendant(CallParticipant cp)
    {
        String phoneNumber = cp.getPhoneNumber();

        int ix;

        if (phoneNumber != null && phoneNumber.indexOf("sip:") < 0 && phoneNumber.indexOf("@") < 0)
        {
            if ((ix = phoneNumber.indexOf(">")) > 0)
            {
                /*
                 * Must have 5 digit extension
                 */
                if (phoneNumber.length() >= ix + 1 + 5) {
                    cp.setCallAnsweredTreatment("dtmf:" +
                        phoneNumber.substring(ix + 1));

                    cp.setPhoneNumber(phoneNumber.substring(0, ix));
                }
            }
        }

        phoneNumber = cp.getSecondPartyNumber();

        if (phoneNumber != null &&
                phoneNumber.indexOf("sip:") < 0 &&
                phoneNumber.indexOf("@") < 0) {

            if ((ix = phoneNumber.indexOf(">")) > 0) {
                /*
                 * Must have 5 digit extension
                 */
                if (phoneNumber.length() >= ix + 1 + 5) {
                    cp.setSecondPartyTreatment("dtmf:" +
                        phoneNumber.substring(ix + 1));

                    cp.setSecondPartyNumber(phoneNumber.substring(0, ix));
                }
            }
        }
    }

    private void monitorConferenceStatus(String conferenceId, boolean monitor)
    {
        if (monitor)
        {
            synchronized(conferenceMonitors)
            {
				loginfo("adding conference monitor for " + conferenceId);
                conferenceMonitors.add(new ConferenceMonitor(null, conferenceId));
            }

		} else {

            synchronized(conferenceMonitors)
            {
	        	ArrayList<ConferenceMonitor> monitorsToRemove = new ArrayList<ConferenceMonitor>();

				for (ConferenceMonitor m : conferenceMonitors)
				{
					if (null != m.getService())
					{
						continue;
					}

					if (conferenceId.equals(m.getConferenceId()))
					{
						monitorsToRemove.add(m);
					}
				}

				for (ConferenceMonitor m : monitorsToRemove)
				{
		    		loginfo("Removing conference monitor for " + conferenceId);
                    conferenceMonitors.remove(m);
                }
            }
        }
    }

    public void monitorOutgoingCalls(boolean monitor)
    {
        synchronized(outgoingCallListeners)
        {
            if (monitor == true) {
                outgoingCallListeners.add(this);
            } else {
                outgoingCallListeners.remove(this);
            }
        }
    }

    public boolean monitorIncomingCalls(boolean monitor)
    {
		synchronized(incomingCallListeners)
		{
			if (monitor == true)
			{
				if (incomingCallListeners.contains(this))
				{
					reportError("Client already has an incomingCallListener");
					return false;
				}

				incomingCallListeners.add(this);
				IncomingCallHandler.setDirectConferencing(false);

				loginfo("adding incoming call monitor, setting directConferencing to false");

			} else {

				if (incomingCallListeners.contains(this) == false)
				{
					return false;
				}

				incomingCallListeners.remove(this);

				if (incomingCallListeners.size() == 0)
				{
					IncomingCallHandler.setDirectConferencing(true);
					loginfo("removing last incoming call monitor setting directConferencing to true");
				}
			}
		}
		return true;
    }

    public static void notifyConferenceMonitors(CallEvent callEvent)
    {
		//System.out.println("notifyConferenceMonitors " + callEvent.toString());

        synchronized(conferenceMonitors)
        {
			/*
			 * Notify conference listeners
			 */

			String conferenceId = callEvent.getConferenceId();

			String s = callEvent.toString();

			if (conferenceId == null)
			{
				int ix;

				String search = "ConferenceId='";

				if ((ix = s.indexOf(search)) < 0) {
					return;
				}

				conferenceId = s.substring(search.length());

				int end;

				if ((end = conferenceId.indexOf("'")) < 0) {
					return;
				}

				conferenceId = conferenceId.substring(0, end);
			}

			for (ConferenceMonitor m : conferenceMonitors)
			{
					if (conferenceId.equals(m.getConferenceId()))
					{
						Application.reportCallEventNotification(m.getService(), callEvent, "monitorConferenceStatus");
					}
			}
		}
    }

    public static void incomingCallNotification(CallEvent callEvent)
    {
		//System.out.println("incomingCallNotification " + callEvent.toString());

		synchronized (incomingCallListeners)
		{
	    	for (Object service : incomingCallListeners)
	    	{
	        	Application.reportCallEventNotification(service, callEvent, "monitorIncomingCalls");
	    	}
		}

		notifyConferenceMonitors(callEvent);
    }

    public static void outgoingCallNotification(CallEvent callEvent)
    {
		//System.out.println("outgoingCallNotification " + callEvent.toString());

        synchronized(outgoingCallListeners)
        {
	    	for (Object service : outgoingCallListeners)
	    	{
	        	Application.reportCallEventNotification(service, callEvent, "monitorOutgoingCalls");

            }
        }
    }


    class ConferenceMonitor {

        private Object service;
        private String conferenceId;

        public ConferenceMonitor(Object service, String conferenceId) {
            this.service = service;
            this.conferenceId = conferenceId;
        }

        public Object getService() {
            return service;
        }

        public String getConferenceId() {
            return conferenceId;
        }

    }

}
