/*
 * Copyright 2007 Sun Microsystems, Inc.
 *
 * This file is part of jVoiceBridge.
 *
 * jVoiceBridge is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation and distributed hereunder
 * to you.
 *
 * jVoiceBridge 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, see <http://www.gnu.org/licenses/>.
 *
 * Sun designates this particular file as subject to the "Classpath"
 * exception as provided by Sun in the License file that accompanied this
 * code.
 */

package com.sun.voip.server;

import com.sun.voip.CallParticipant;
import com.sun.voip.CallEvent;
import com.sun.voip.CallEventListener;
import com.sun.voip.CallState;
import com.sun.voip.Logger;
import com.sun.voip.TreatmentDoneListener;
import com.sun.voip.TreatmentManager;

import java.io.IOException;

import java.net.InetSocketAddress;
import java.net.SocketException;

import java.util.NoSuchElementException;
import java.util.Vector;

import java.text.ParseException;

/**
 * Common code for handling incoming outgoing calls.
 */
public abstract class CallHandler extends Thread {
    protected ConferenceManager conferenceManager;
    protected ConferenceMember member;
    protected MemberSender memberSender;
    protected MemberReceiver memberReceiver;
    protected CallSetupAgent csa;
    protected CallParticipant cp;

    protected boolean done = false;
    protected String reasonCallEnded;

    protected boolean suppressStatus;

    /*
     * maintain a list of active calls so individual calls
     * can be terminated.
     */
    protected static Vector activeCalls = new Vector();

    private Vector callEventListeners = new Vector();

    /*
     * One receiver per conference is ideal for scaling.
     * In order for this to work, we must be able to distinguish
     * calls based on the source address in packets we receive
     * from each call.  The SIP port exchange only involves destination
     * ports.  For most implmentations (Cisco Gateway, Cisco IP Phone,
     * conference bridge software) the source and destination ports
     * are the same.  Sip-Communicator is one exception.
     *
     * So when we see a sip URI for a target phone number, we create
     * a separate conference receiver specifically for that call.
     */
    private static boolean oneReceiverPerConference = true;

    private static int duplicateCallLimit = 100;

    private static boolean enablePSTNCalls = true;

    /*
     * Used by the OutgoingCallHandler to handle two party calls.
     * This is here so that dtmf keys can be forwarded with two party calls.
     */
    protected CallHandler otherCall;

    public void suppressStatus(boolean suppressStatus) {
	this.suppressStatus = suppressStatus;
    }

    /*
     * Mostly for debugging
     */
    public String getCallState() {
	String s = "\n" + cp.toString();

	if (member != null) {
	    s += "  ConferenceId: "
		+ member.getConferenceManager().getId() + "\n";

	    s += "\tStarted " + member.getTimeStarted() + "\n";
	} else {
	    s += "\n";
	}

	if (csa != null) {
	    s += "\tState = " + csa.getCallState() + "\n";
	} else {
	    s += "\tNo Call Setup Agent" + "\n";
	}

	s += "\tIsDistributedBridge " + cp.isDistributedBridge() + "\n";


	if (cp.getCallTimeout() == 0) {
	    s += "\tNo timeout\n";
	} else {
	    s += "\tCall timeout in " + (cp.getCallTimeout() / 1000)
	    + " seconds\n";
	}

	if (member != null) {
	    s += " " + member.getMemberState();
	}

	return s;
    }

    public static String getCallStateForAllCalls() {
	String s = "";

	synchronized(activeCalls) {
            for (int i = 0; i < activeCalls.size(); i++) {
                CallHandler call = (CallHandler)activeCalls.elementAt(i);

		s += call.getCallState() + "\n";
	    }
	}

	return s;
    }

    public static String getAllMixDescriptors() {
        String s = "";

        synchronized(activeCalls) {
            for (int i = 0; i < activeCalls.size(); i++) {
                CallHandler call = (CallHandler)activeCalls.elementAt(i);

		s += "MixDescriptors for " + call + "\n";
                s += call.getMember().getMixDescriptors() + "\n";
            }
        }

        return s;
    }

    public static String getAllAbbreviatedMixDescriptors() {
        String s = "";

        synchronized(activeCalls) {
            for (int i = 0; i < activeCalls.size(); i++) {
                CallHandler call = (CallHandler)activeCalls.elementAt(i);

                s += "MixDescriptors for " + call + "\n";
                s += call.getMember().getAbbreviatedMixDescriptors() + "\n";
            }
        }

        return s;
    }

    /*
     * Overridden by OutgoingCallhandler.  There is no request handler
     * for incoming calls.
     */
    public abstract CallEventListener getRequestHandler();

    public CallParticipant getCallParticipant() {
	return cp;
    }

    /*
     * Used to switch a call from one conference to another.
     */
    public void setConferenceManager(ConferenceManager conferenceManager) {
	this.conferenceManager = conferenceManager;
    }

    public ConferenceManager getConferenceManager() {
	return conferenceManager;
    }

    public ConferenceMember getMember() {
	return member;
    }

    public MemberSender getMemberSender() {
	return memberSender;
    }

    public MemberReceiver getMemberReceiver() {
	return memberReceiver;
    }

    /*
     * This method is called by a CallSetupAgent once the endpoint
     * address is known.  The endpoint address is the address from which
     * we expect to receive RTP packets and to which we will send RTP packets.
     */
    public void setEndpointAddress(InetSocketAddress isa, byte mediaPayload,
    	    byte receivePayload, byte telephoneEventPayload) {

	setEndpointAddress(isa, mediaPayload, receivePayload, telephoneEventPayload, null);
    }

    public void setEndpointAddress(InetSocketAddress isa, byte mediaPayload,
    	    byte receivePayload, byte telephoneEventPayload, InetSocketAddress rtcpAddress) {

    	member.initialize(this, isa, mediaPayload, receivePayload,
    	    telephoneEventPayload, rtcpAddress);
    }

    /*
     * true if call is established
     */
    public boolean isCallEstablished() {
        if (done || csa == null) {
            return false;
        }
        return csa.isCallEstablished();
    }

    /*
     * true is call is ending
     */
    public boolean isCallEnding() {
        if (done || csa == null) {
            return true;
        }
        return csa.isCallEnding();
    }

    public void addCallEventListener(CallEventListener listener) {
        synchronized (callEventListeners) {
            callEventListeners.add(listener);
        }
    }

    public void removeCallEventListener(CallEventListener listener) {
        synchronized (callEventListeners) {
            callEventListeners.remove(listener);
        }
    }

    public void sendCallEventNotification(CallEvent callEvent) {
	if (cp.getCallId() != null) {
	    callEvent.setCallId(cp.getCallId());
	} else {
	    callEvent.setCallId("CallIdNotInitialized");
	}

	callEvent.setConferenceId(cp.getConferenceId());

	callEvent.setCallInfo(cp.getCallOwner());

	if (csa != null) {
	    callEvent.setCallState(csa.getCallState());
	} else {
	    callEvent.setCallState(new CallState(CallState.UNINITIALIZED));
	}

        synchronized (callEventListeners)
        {
            for (int i = 0; i < callEventListeners.size(); i++)
            {
                CallEventListener listener = (CallEventListener) callEventListeners.elementAt(i);
                listener.callEventNotification(callEvent);
            }
        }
    }

    /*
     * The subclasses must override this.
     */
    public abstract void callEventNotification(CallEvent callEvent);

    /**
     * Send indication when speaker starts or stops speaking.
     */
    public static int totalSpeaking;

    public void speakingChanged(boolean isSpeaking) {
	if (isSpeaking) {
	    totalSpeaking++;

	    CallEvent callEvent =
		new CallEvent(CallEvent.STARTED_SPEAKING);

	    callEvent.setStartedSpeaking();
	    sendCallEventNotification(callEvent);
	} else {
	    totalSpeaking--;

	    CallEvent callEvent =
		new CallEvent(CallEvent.STOPPED_SPEAKING);

	    callEvent.setStoppedSpeaking();
	    sendCallEventNotification(callEvent);
	}
    }

    public static int getTotalSpeaking() {
	return totalSpeaking;
    }

    /**
     * Send indication when a dtmf key is pressed
     */
    public void dtmfKeys(String dtmfKeys) {
	//if (Logger.logLevel >= Logger.LOG_MOREINFO) {
	    Logger.println(cp + " got dtmf keys " + dtmfKeys + " " 	+ cp.dtmfDetection());
	//}

	if (isCallEstablished()) {
	    if (cp.dtmfDetection()) {
	        member.stopTreatment(null);

		CallEvent callEvent = new CallEvent(CallEvent.DTMF_KEY);
		callEvent.setDtmfKey(dtmfKeys);
	        sendCallEventNotification(callEvent);
	    }

	    if (otherCall != null) {
			Logger.println("Call " + cp + " forwarding dtmf key "  + dtmfKeys + " to " + otherCall);
			otherCall.getMemberSender().setDtmfKeyToSend(dtmfKeys);
	    } else {
			getMemberSender().setDtmfKeyToSend(dtmfKeys);
		}
	} else {
	    if (Logger.logLevel >= Logger.LOG_MOREINFO) {
	        Logger.println(cp + " Call not established, ignoring dtmf");
	    }
	    stopCallAnsweredTreatment();
	}
    }

    public void stopCallAnsweredTreatment() {
	if (done || csa == null) {
	    return;
	}
	csa.stopCallAnsweredTreatment();
    }

    public void stopCallEstablishedTreatment() {
        if (done || csa == null) {
            return;
        }
        csa.stopCallEstablishedTreatment();
    }

    /*
     * terminate a call.
     */
    public void cancelRequest(String reason) {
	if (done) {
	    return;
	}

	done = true;

	Logger.println(cp + " Cancel request " + reason);

        if (csa != null) {
            csa.cancelRequest(reason);
        }
    }

    /*
     * Add a treatment for the caller
     */
    public void addTreatment(TreatmentManager treatmentManager) {
	member.addTreatment(treatmentManager);
    }

    /*
     * unique call identifier incremented for each new call.
     */
    private static int callNumber = 0;

    public static synchronized String getNewCallId() {
	String s;

	do {
	    callNumber++;
            s = String.valueOf(callNumber);

	    String location = Bridge.getBridgeLocation();

	    if (location.equalsIgnoreCase("Unknown") == false) {
	        s += "_" + Bridge.getBridgeLocation();
	    }
	} while (CallHandler.findCall(s) != null);

        return s;
    }

    /**
     * Find the new call of a call migration.
     */
    public static CallHandler findMigratingCall(String callId) {
	synchronized(activeCalls) {
            for (int i = 0; i < activeCalls.size(); i++) {
                CallHandler call = (CallHandler)activeCalls.elementAt(i);

                CallParticipant cp = call.getCallParticipant();

                if (match(cp, callId) && cp.migrateCall()) {
                    if (Logger.logLevel >= Logger.LOG_DETAIL) {
                        Logger.println("findMigratingCall:  found " + callId);
                    }

                    return call;
                }
            }
        }
        return null;

    }

    /**
     * Find a call by callId.
     *
     * Calls are kept in the activeCalls list and uniquely identified
     * by <callId>::<name>@<phoneNumber> for a phone call and
     *
     * This method searches for a call with the callId.
     */
    public static CallHandler findCall(String callId) {
	if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    Logger.println("findCall:  looking for " + callId
		+ ", " + activeCalls.size() + " active calls");
	}

	synchronized(activeCalls) {
            for (int i = 0; i < activeCalls.size(); i++) {
                CallHandler call = (CallHandler)activeCalls.elementAt(i);

		CallParticipant cp = call.getCallParticipant();

		if (Logger.logLevel >= Logger.LOG_DETAIL) {
		    Logger.println("findCall:  looking for "
			+ callId + " got " + cp.getCallId());
		}

		if (match(cp, callId)) {
		    if (Logger.logLevel >= Logger.LOG_DETAIL) {
		    	Logger.println("findCall:  found " + callId);
		    }

		    return call;
		}
	    }
	}
	return null;
    }

    private static boolean match(CallParticipant cp, String callId) {
	if (cp.getCallId().equals(callId)) {
	    return true;
	}

	if (ConferenceManager.allowShortNames() == false) {
	    return false;
	}

	String name = cp.getName();

	if (name != null) {
	    if (name.equals(callId)) {
	        return true;
	    }

	    name = name.replaceAll(" ", "_");

	    if (name.equals(callId)) {
	        return true;
 	    }
	}

	String number = cp.getPhoneNumber();

	if (number == null) {
	    return false;
	}

	if (number.equals(callId)) {
	    return true;
	}

	if (number.indexOf("sip:") == 0) {
	    int ix = number.indexOf("@");

	    if (ix >= 0) {
	        number = number.substring(4, ix);

	        if (number.equals(callId)) {
		    return true;
	        }
	    }
	}

	return false;
    }

    /*
     * Add call to list of active calls
     */
    public void addCall(CallHandler callHandler) {
        synchronized(activeCalls) {
            activeCalls.add(callHandler);      // add to list of active calls
        }
    }

    /*
     * Remove call from list of active calls
     */
    public void removeCall(CallHandler callHandler) {
        synchronized(activeCalls) {
            activeCalls.remove(callHandler); // remove call from list

	    Logger.println("");
            Logger.println("calls still in progress:  " + activeCalls.size());
	    Logger.println("");
        }
    }

    /*
     * End all calls.
     */
    public static void shutdown() {
	shutdown(0);
    }

    public static void shutdown(int delaySeconds) {
	if (delaySeconds == 0) {
	    /*
	     * Quick shutdown right now!
	     */
	    hangup("0", "System shutdown");
	    return;
	}

	/*
	 * Notify the active calls that the MC bridge is shutting down
	 */
	long start = System.currentTimeMillis();

	Logger.println("Shutting down in " + delaySeconds + " seconds");

	synchronized(activeCalls) {
	    for (int i = 0; i < activeCalls.size(); i++) {
                CallHandler call = (CallHandler)activeCalls.elementAt(i);

		String id = call.getCallParticipant().getCallId();

		try {
    	 	    playTreatmentToCall(id, "joinBELL.au;shutdown.au;tts:" +
			delaySeconds + ";seconds.au");
		} catch (IOException e) {
		    Logger.println("Can't play shutdown treatment to call "
			+ id + " " + e.getMessage());
		}
	    }

	    /*
	     * Wait at most a minute in case something is severly broken.
	     */
	    while (System.currentTimeMillis() - start < 60000) {
		boolean hasTreatments = false;

	        for (int i = 0; i < activeCalls.size(); i++) {
                    CallHandler call = (CallHandler)activeCalls.elementAt(i);

		    hasTreatments = call.getMember().hasTreatments();

		    if (hasTreatments) {
			break;
		    }
		}

		if (hasTreatments == false) {
		    break;  // no treatments left to play
		}
	    }
	}

	if (delaySeconds != 0) {
	    int sleepTime = (int)((delaySeconds * 1000) -
		(System.currentTimeMillis() - start));

	    if (sleepTime > 0) {
	        try {
		    Thread.sleep(sleepTime);
	        } catch (InterruptedException e) {
	        }
	    }
	}

	hangup("0", "System shutdown");
    }

    /*
     * Cancel a specified call.  If callid is 0, all calls are cancelled.
     */
    public static void hangup(String callId, String reason) {
	Vector callsToCancel = new Vector();

	synchronized(activeCalls) {
	    /*
	     * Make a list of all the calls we want to cancel, then cancel them.
	     * We have to cancel them while not synchronized or
	     * we could deadlock.
	     */
	    for (int i = 0; i < activeCalls.size(); i++) {
                CallHandler call = (CallHandler)activeCalls.elementAt(i);

		CallParticipant cp = call.getCallParticipant();

	        if (callId.equals("0") || match(cp, callId)) {
		    callsToCancel.add(call);
		}
	    }
	}

	cancel(callsToCancel, reason, false);
    }

    public static void hangupOwner(String ownerId, String reason) {
		Vector callsToCancel = new Vector();

		synchronized(activeCalls) {
			/*
			 * Make a list of all the calls we want to cancel, then cancel them.
			 * We have to cancel them while not synchronized or
			 * we could deadlock.
			 */
	    	for (int i = 0; i < activeCalls.size(); i++) {
                CallHandler call = (CallHandler)activeCalls.elementAt(i);

				CallParticipant cp = call.getCallParticipant();

	        	if (cp.getCallOwner().equals(ownerId)) {
		    		callsToCancel.add(call);
				}
	    	}
		}

		cancel(callsToCancel, reason, false);
    }


    public static void suspendBridge() {
	cancel(activeCalls, "bridge suspended", true);
    }

    private static void cancel(Vector callsToCancel, String reason,
	    boolean suppressStatus) {

	while (callsToCancel.size() > 0) {
            CallHandler call = (CallHandler)callsToCancel.remove(0);
	    call.suppressStatus(suppressStatus);
	    call.cancelRequest(reason);
	}
    }

    public String getReasonCallEnded() {
	return reasonCallEnded;
    }

    /*
     * Set cnThresh for the speech detector for a conference member.
     */
    public static void setCnThresh(String callId, int cnThresh) {
        synchronized(activeCalls) {
            for (int i = 0; i < activeCalls.size(); i++) {
                CallHandler call = (CallHandler)activeCalls.elementAt(i);

                CallParticipant cp = call.getCallParticipant();

                if (match(cp, callId)) {
                    MemberReceiver memberReceiver = call.getMemberReceiver();

                    if (memberReceiver != null) {
                        memberReceiver.setCnThresh(cnThresh);
                    }
                }
            }
        }
    }

    /*
     * force packets to be dropped for debugging.
     */
    public static void setDropPackets(String callId, int dropPackets) {
	if (callId == null) {
	    return;
	}

	synchronized(activeCalls) {
            for (int i = 0; i < activeCalls.size(); i++) {
                CallHandler call = (CallHandler)activeCalls.elementAt(i);

		CallParticipant cp = call.getCallParticipant();

		if (match(cp, callId)) {
		    MemberReceiver memberReceiver = call.getMemberReceiver();

		    if (memberReceiver != null) {
			memberReceiver.setDropPackets(dropPackets);
		    }
                }
            }
        }
    }

    /**
     * Mute or unmute a conference member.
     */
    public void setMuted(boolean isMuted) {
	MemberReceiver memberReceiver = getMemberReceiver();

	if (memberReceiver != null) {
	    memberReceiver.setMuted(isMuted);
	}
    }

    public static void setMuted(String callId, boolean isMuted) {
	if (callId == null) {
	    return;
	}

	synchronized(activeCalls) {
            for (int i = 0; i < activeCalls.size(); i++) {
                CallHandler call = (CallHandler)activeCalls.elementAt(i);

		CallParticipant cp = call.getCallParticipant();

		if (match(cp, callId)) {
                    if (Logger.logLevel >= Logger.LOG_DETAIL) {
			String s = "";

		        if (isMuted == false) {
			    s = "un";
		        }
                        Logger.println(cp.getCallId() + ":  " + s + "muted");
		    }

		    MemberReceiver memberReceiver = call.getMemberReceiver();

		    if (memberReceiver != null) {
			memberReceiver.setMuted(isMuted);
		    }
                }
	    }
        }
    }

    public void setRemoteMediaInfo(String sdp) throws ParseException {
	csa.setRemoteMediaInfo(sdp);
    }

    public static void setRemoteMediaInfo(String callId, String sdp)
	    throws ParseException {

        synchronized(activeCalls) {
            for (int i = 0; i < activeCalls.size(); i++) {
                CallHandler call = (CallHandler)activeCalls.elementAt(i);

                CallParticipant cp = call.getCallParticipant();

                if (match(cp, callId)) {
		    call.setRemoteMediaInfo(sdp);
		    return;
                }
            }
        }

	throw new ParseException("Invalid callId: " + callId, 0);
    }

    /*
     * Say the number of calls in the conference
     */
    public int getNumberOfCalls() {
	return conferenceManager.getNumberOfMembers();
    }

    /**
     * Mute or unmute member in a whisperGroup
     */
    public static void setMuteWhisperGroup(String callId, boolean isMuted) {
        if (callId == null) {
            return;
        }

        synchronized(activeCalls) {
            for (int i = 0; i < activeCalls.size(); i++) {
                CallHandler call = (CallHandler)activeCalls.elementAt(i);

                CallParticipant cp = call.getCallParticipant();

                if (match(cp, callId)) {
                    if (Logger.logLevel >= Logger.LOG_DETAIL) {
                        String s = "";

                        if (isMuted == false) {
                            s = "un";
                        }
                        Logger.println(cp.getCallId() + ":  " + s + "muted");
                    }

                    MemberReceiver memberReceiver = call.getMemberReceiver();

                    if (memberReceiver != null) {
                        memberReceiver.setMuteWhisperGroup(isMuted);
                    }
                }
            }
        }
    }

    /**
     * Mute or unmute a conference from a particular call.
     */
    public static void setConferenceMuted(String callId, boolean isMuted) {
        if (callId == null) {
            return;
        }

        synchronized(activeCalls) {
            for (int i = 0; i < activeCalls.size(); i++) {
                CallHandler call = (CallHandler)activeCalls.elementAt(i);

                CallParticipant cp = call.getCallParticipant();

                if (match(cp, callId)) {
                    if (Logger.logLevel >= Logger.LOG_DETAIL) {
                        String s = "";

                        if (isMuted == false) {
                            s = "un";
                        }
                        Logger.println(cp.getCallId() + ":  conference " + s
			    + "muted");
                    }

                    ConferenceMember member = call.getMember();

                    if (member!= null) {
                        member.setConferenceMuted(isMuted);
                    }
                }
            }
        }
    }

    /**
     * Mute or unmute the main conference from a particular call.
     */
    public static void setConferenceSilenced(String callId, boolean isSilenced) {
        synchronized(activeCalls) {
            for (int i = 0; i < activeCalls.size(); i++) {
                CallHandler call = (CallHandler)activeCalls.elementAt(i);

                CallParticipant cp = call.getCallParticipant();

                if (match(cp, callId)) {
                    if (Logger.logLevel >= Logger.LOG_DETAIL) {
                        String s = "";

                        if (isSilenced == false) {
                            s = "un";
                        }

                        Logger.println(cp.getCallId()
			    + ":  silenceMainonference " + s + "muted");
                    }

                    ConferenceMember member = call.getMember();

                    if (member!= null) {
                        member.setConferenceSilenced(isSilenced);
                    }
                }
            }
        }
    }

    /*
     * Set powerThresholdLimit for the speech detector for a member.
     */
    public static void setPowerThresholdLimit(String callId,
	    double powerThresholdLimit) {

        synchronized(activeCalls) {
            for (int i = 0; i < activeCalls.size(); i++) {
                CallHandler call = (CallHandler)activeCalls.elementAt(i);

                CallParticipant cp = call.getCallParticipant();

                if (match(cp, callId)) {
                    MemberReceiver memberReceiver = call.getMemberReceiver();

                    if (memberReceiver != null) {
                        memberReceiver.setPowerThresholdLimit(
			    powerThresholdLimit);
                    }
                }
            }
        }
    }

    /**
     * set dmtfSuppression flag
     */
    private static boolean dtmfSuppression = true;

    public static void setDtmfSuppression(String callId,
        boolean dtmfSuppression) throws NoSuchElementException {

        if (callId.equals("0")) {
            CallHandler.dtmfSuppression = dtmfSuppression;
            return;
        }

	CallHandler callHandler = findCall(callId);

	if (callHandler == null) {
	    throw new NoSuchElementException("Invalid callId specified:  "
		+ callId);
	}

	callHandler.getCallParticipant().setDtmfSuppression(dtmfSuppression);
    }

    /**
     * Set flag to do voice detection while muted
     */
    public static void setVoiceDetectionWhileMuted(String callId,
	    boolean voiceDetectionWhileMuted) {

        if (callId == null) {
            return;
        }

        synchronized(activeCalls) {
            for (int i = 0; i < activeCalls.size(); i++) {
                CallHandler call = (CallHandler)activeCalls.elementAt(i);

		CallParticipant cp = call.getCallParticipant();

                if (match(cp, callId)) {
		    cp.setVoiceDetectionWhileMuted(voiceDetectionWhileMuted);

                    if (Logger.logLevel >= Logger.LOG_DETAIL) {
			Logger.println(cp.getCallId()
			    + " voice detection while muted is "
			    + voiceDetectionWhileMuted);
                    }
                }
            }
	}
    }

    /**
     * Get global dtmfSuppression flag
     */
    public static boolean dtmfSuppression() {
	return dtmfSuppression;
    }

    public static void setDoNotRecord(String callId, boolean doNotRecord)
	    throws NoSuchElementException {

        CallHandler callHandler = findCall(callId);

        if (callHandler == null) {
	    throw new NoSuchElementException("Invalid callId specified:  "
		+ callId);
	}

        if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    String s = "";

	    if (doNotRecord == true) {
		s = "NOT";
	    }
            Logger.println(callHandler + ":  " + s + " okay to record");
	}

	callHandler.getMemberReceiver().setDoNotRecord(doNotRecord);
    }

    /**
     * Record data sent to or from a member
     */
    public static void recordMember(String callId, boolean enabled,
	    String recordingFile, String recordingType, boolean fromMember)
		throws NoSuchElementException, IOException {

        CallHandler callHandler = findCall(callId);

        if (callHandler == null) {
	    throw new NoSuchElementException("Invalid callId specified:  "
		+ callId);
	}

	if (fromMember) {
	    callHandler.getMemberReceiver().setRecordFromMember(enabled,
	        recordingFile, recordingType);
	} else {
	    callHandler.getMemberSender().setRecordToMember(enabled,
	        recordingFile, recordingType);
	}
    }

    /**
     * Play a treament to a member
     */
    public static void playTreatmentToCall(String callId, String treatment)
	    throws NoSuchElementException, IOException {

	playTreatmentToCall(callId, treatment, (TreatmentDoneListener) null);
   }

    public static void playTreatmentToCall(String callId, String treatment,
	    double[] volume) throws NoSuchElementException, IOException {
    }

    public static void playTreatmentToCall(String callId, String treatment,
	    TreatmentDoneListener treatmentDoneListener)
	    throws NoSuchElementException, IOException {

        CallHandler callHandler = findCall(callId);

        if (callHandler == null) {
            throw new NoSuchElementException("Invalid callId specified:  "
		+ callId);
        }

        if (callHandler.isCallEstablished() == false) {
            throw new IOException("Call is not ESTABLISHED:  " + callId);
        }

	callHandler.playTreatmentToCall(treatment, treatmentDoneListener);
    }

    public TreatmentManager playTreatmentToCall(String treatment)
	    throws IOException {

	return playTreatmentToCall(treatment, (TreatmentDoneListener) null);
    }

    public TreatmentManager playTreatmentToCall(String treatment,  TreatmentDoneListener treatmentDoneListener)
	    throws IOException {

        if (Logger.logLevel >= Logger.LOG_MOREINFO) {
	    Logger.println("Playing treatment " + treatment + " to "
		+ cp.getCallId());
	}

        TreatmentManager treatmentManager = new TreatmentManager(treatment, 0,
	    conferenceManager.getMediaInfo().getSampleRate(),
	    conferenceManager.getMediaInfo().getChannels());

	if (treatmentDoneListener != null) {
	    treatmentManager.addTreatmentDoneListener(treatmentDoneListener);
	}

	addTreatment(treatmentManager);
	return treatmentManager;
    }

    /**
     * get the IP address and port used to receive packets for this call.
     */
    public InetSocketAddress getReceiveAddress() {
	return memberReceiver.getReceiveAddress();
    }

    /**
     * get the IP address and port used to send packets to this call.
     */
    public InetSocketAddress getSendAddress() {
	return memberSender.getSendAddress();
    }

    /**
     * Determine if this is the first member to join the conference.
     * This is called to determine if a special audio treatment
     * should be played.
     */
    public boolean isFirstMember() {
        return conferenceManager.isFirstMember();
    }

    /**
     * For debugging...
     */
    public static boolean tooManyDuplicateCalls(String phoneNumber) {
	synchronized(activeCalls) {
	    int n = 0;

            for (int i = 0; i < activeCalls.size(); i++) {
                CallHandler call = (CallHandler)activeCalls.elementAt(i);

                CallParticipant cp = call.getCallParticipant();

		if (cp.getPhoneNumber().equals(phoneNumber)) {
		    n++;
		}
	    }

	    if (n > duplicateCallLimit) {
		return true;
	    }

	    return false;
	}
    }

    public static void setDuplicateCallLimit(int duplicateCallLimit) {
	CallHandler.duplicateCallLimit = duplicateCallLimit;
    }

    public static int getDuplicateCallLimit() {
	return duplicateCallLimit;
    }

    public static void enablePSTNCalls(boolean enablePSTNCalls) {
	CallHandler.enablePSTNCalls = enablePSTNCalls;
    }

    public static boolean enablePSTNCalls() {
	return enablePSTNCalls;
    }

    /**
     * String representation of this Caller
     * @return the string representation of this Caller
     */
    public String toString() {
        return cp.toString();
    }

}
