/*
 * 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.ConferenceEvent;
import com.sun.voip.DistributedBridge;
import com.sun.voip.Logger;
import com.sun.voip.MediaInfo;
import com.sun.voip.RtpPacket;
import com.sun.voip.SdpManager;
import com.sun.voip.TreatmentManager;

import java.io.IOException;

import java.text.ParseException;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

import java.util.NoSuchElementException;
import java.util.ArrayList;

import org.ifsoft.rayo.RayoComponent;

/**
 * Manage a conference consisting of members.
 * Each conference has a unique String identifying it.
 * Members can join and leave the conference.
 */
public class ConferenceManager {
    private static ArrayList conferenceList = new ArrayList();

    private String             conferenceId;	      // conference identifier

    private String	       displayName;	      // user readable name
    private String	       callId;	      // target call id

    private ArrayList 	       memberList;	      // for iterating members

    private boolean	       isFirstMember = true;

    private boolean	       privateCall = false;

    private boolean	       transferCall = false;

    private CallParticipant	heldCall = null;

    private String 		   groupName = null;

    /*
     * If useSingleSender is true, a single
     * conferenceSender will be used for all conferences.
     */
    private static boolean useSingleSender = true;
    private static ConferenceSender loneConferenceSender;

    private static int loneReceiverPort = 0;
    private static ConferenceReceiver loneConferenceReceiver;

    private ConferenceSender   conferenceSender;      // sender thread
    private WGManager	       wgManager;	      // whisper group manager
    private ConferenceReceiver conferenceReceiver;    // receiver thread

    private boolean	       permanent = false;

    private static int	       totalMembers = 0;

    private String 	       mediaPreference;	      // new media preference
    private MediaInfo	       mediaInfo;

    private long	       conferenceStartTime;

    private static 	       DistributedBridge distributedBridge;

    private boolean done = false;

    static {
	String s = System.getProperty("com.sun.voip.server.LONE_RECEIVER_PORT");

	if (s != null && s.length() > 0) {
	    try {
		loneReceiverPort = Integer.parseInt(s);
	    } catch (NumberFormatException e) {
		Logger.println("Invalid port for lone receiver:  " + s);
	    }
	}
    }

    /**
     * Constructor
     *
     * Create a new conference
     * @param conferenceId String identifying the conference
     */
    private ConferenceManager(String conferenceId, String mediaPreference,
	    String displayName) throws SocketException {

        this.conferenceId = conferenceId;

	memberList = new ArrayList();

	try {
	    setMediaInfo(mediaPreference);
	} catch (ParseException e) {
	    Logger.println(conferenceId
		+ ":  Can't set meeting media setting to "
		+ mediaPreference + ": " + e.getMessage());
	}

	this.displayName = displayName;

	if (useSingleSender == true) {
	    if (loneConferenceSender == null) {
		loneConferenceSender = new ConferenceSender(conferenceList);
	    }
	    conferenceSender = loneConferenceSender;
	} else {
	    conferenceSender = new ConferenceSender(this);
	}

	if (loneReceiverPort != 0) {
	    if (loneConferenceReceiver == null) {
		loneConferenceReceiver =
	    	    new ConferenceReceiver("Singleton", loneReceiverPort);  // start receiver
	    }
	    conferenceReceiver = loneConferenceReceiver;
	} else {
	    conferenceReceiver = new ConferenceReceiver(conferenceId, 0);  // start receiver
	}
    }

    public void setMediaInfo(String mediaPreference) throws ParseException {
	/*
	 * Conference id may be qualified by the media parameters.
	 * The syntax is <conferenceId>:<PCM[U]|SPEEX/<sampleRate>/<channels>
	 */
	mediaInfo = parseMediaPreference(mediaPreference);
	mediaPreference = null;

	if (wgManager == null) {
	    /*
	     * Use the conference Id as the name of the main conference
	     * whisper group.
	     */
	    wgManager = new WGManager(conferenceId, mediaInfo);
	} else {
	    wgManager.setMediaInfo(mediaInfo);
	}
    }

    private MediaInfo parseMediaPreference(String mediaPreference)
	    throws ParseException {

	if (mediaPreference == null) {
	    if (mediaInfo != null) {
		return mediaInfo;
	    }

	    return SdpManager.findMediaInfo(RtpPacket.PCMU_ENCODING, 8000, 1);
	}

	int ix;

	int encoding = RtpPacket.PCMU_ENCODING;
	int sampleRate = 8000;
	int channels = 1;

	try {
	    if (mediaPreference.indexOf("PCMU/") == 0) {
		encoding = RtpPacket.PCMU_ENCODING;
		mediaPreference = mediaPreference.substring(5);
	    } else if (mediaPreference.indexOf("PCM/") == 0) {
		encoding = RtpPacket.PCM_ENCODING;
		mediaPreference = mediaPreference.substring(4);
	    } else if (mediaPreference.indexOf("SPEEX/") == 0) {
		encoding = RtpPacket.SPEEX_ENCODING;
		mediaPreference = mediaPreference.substring(6);
	    } else if (mediaPreference.indexOf("PCM") == 0) {
			// do nothing
	    } else {
		Logger.println("Invalid media specification " + mediaPreference);
	    }

	    if ((ix = mediaPreference.indexOf("/")) < 0) {
		Logger.println("Invalid media specification " + mediaPreference);
	    } else {
	        sampleRate = Integer.parseInt(mediaPreference.substring(0, ix));
	        channels = Integer.parseInt(mediaPreference.substring(ix + 1));
	    }
	} catch (IndexOutOfBoundsException e) {
	    Logger.println("Invalid media specification " + mediaPreference);
	} catch (NumberFormatException e) {
	    Logger.println("Invalid media specification " + mediaPreference);
	}

	if (sampleRate == 8000 && channels == 1 &&
	        encoding == RtpPacket.PCM_ENCODING) {

	    encoding = RtpPacket.PCMU_ENCODING;
	}

	MediaInfo mediaInfo =
	    SdpManager.findMediaInfo(encoding, sampleRate, channels);

	Logger.println("conference " + conferenceId
	    + " using media settings " + mediaInfo);

	conferenceStartTime = System.currentTimeMillis();

	return mediaInfo;
    }

    public long getConferenceStartTime() {
        return conferenceStartTime;
    }

    public WGManager getWGManager() {
	return wgManager;
    }

    public static WhisperGroup createWhisperGroup(String conferenceId,
	    String whisperGroupId, double attenuation) throws ParseException {

	synchronized (conferenceList) {
	    ConferenceManager conferenceManager =
		findConferenceManager(conferenceId);

	    return conferenceManager.createWhisperGroup(whisperGroupId,
		attenuation);
	}
    }

    public WhisperGroup createWhisperGroup(String whisperGroupId,
            double attenuation) throws ParseException {

	synchronized (conferenceList) {
	    return wgManager.createWhisperGroup(whisperGroupId, attenuation);
	}
    }

    public static void destroyWhisperGroup(String conferenceId,
	    String whisperGroupId) throws ParseException {

        synchronized (conferenceList) {
            ConferenceManager conferenceManager =
                findConferenceManager(conferenceId);

            conferenceManager.destroyWhisperGroup(whisperGroupId);
        }
    }

    public void destroyWhisperGroup(String whisperGroupId)
	    throws ParseException {

	synchronized (conferenceList) {
	    synchronized (this) {
	        wgManager.destroyWhisperGroup(whisperGroupId);
	    }
	}
    }

    public static String getAbbreviatedWhisperGroupInfo(boolean showMembers) {
	String s = "";

	synchronized (conferenceList) {
            for (int i = 0; i < conferenceList.size(); i++) {
	        ConferenceManager conferenceManager = (ConferenceManager)
	            conferenceList.get(i);

	        s += "Whisper groups for conference "
		    + conferenceManager.getId() + "\n";

	        s += conferenceManager.getWGManager().getAbbreviatedWhisperGroupInfo(true);
	        s += "\n";
	    }
	}

	return s;
    }

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

	synchronized (conferenceList) {
            for (int i = 0; i < conferenceList.size(); i++) {
	        ConferenceManager conferenceManager = (ConferenceManager)
	            conferenceList.get(i);

	        s += "Whisper groups for conference "
		    + conferenceManager.getId() + "\n";
	        s += conferenceManager.getWGManager().getWhisperGroupInfo();
	        s += "\n";
	    }
	}

	return s;
    }

    public static void setTransientWhisperGroup(String conferenceId,
	    String whisperGroupId, boolean isTransient) throws ParseException {

	synchronized (conferenceList) {
	    ConferenceManager conferenceManager =
		findConferenceManager(conferenceId);

	    conferenceManager.getWGManager().setTransientWhisperGroup(
		whisperGroupId, isTransient);
	}
    }

    public static void setLockedWhisperGroup(String conferenceId,
	    String whisperGroupId, boolean isLocked) throws ParseException {

	synchronized (conferenceList) {
	    ConferenceManager conferenceManager =
		findConferenceManager(conferenceId);

	    conferenceManager.getWGManager().setLockedWhisperGroup(
		whisperGroupId, isLocked);
	}
    }

    public static void setWhisperGroupAttenuation(String conferenceId,
	    String whisperGroupId, double attenuation) throws ParseException {

	synchronized (conferenceList) {
	    ConferenceManager conferenceManager =
		findConferenceManager(conferenceId);

	    conferenceManager.getWGManager().setWhisperGroupAttenuation(
		whisperGroupId, attenuation);
	}
    }

    public static void setWhisperGroupNoCommonMix(String conferenceId,
	    String whisperGroupId, boolean noCommonMix) throws ParseException {

	synchronized (conferenceList) {
	    ConferenceManager conferenceManager =
		findConferenceManager(conferenceId);

	    synchronized (conferenceManager) {
		WGManager wgManager = conferenceManager.getWGManager();

		synchronized (wgManager.getWhisperGroups()) {
	            wgManager.setWhisperGroupNoCommonMix(whisperGroupId,
			noCommonMix);

		    ArrayList memberList = conferenceManager.getMemberList();

	 	    synchronized (memberList) {
			for (int i = 0; i < memberList.size(); i++) {
			    ConferenceMember member = (ConferenceMember)
				memberList.get(i);

			    /*
			     * Tell members
			     */
			    if (Logger.logLevel >= Logger.LOG_INFO) {
			        Logger.println("Call " + member
				    + " no common mix");
			    }
			    member.setNoCommonMix(whisperGroupId);
			}
		    }
		}
	    }
	}
    }

    private void end() {
        try {
            recordConference(false, null, null);
        } catch (ParseException e) {
            Logger.println(conferenceId
                + ":  Failed to stop recording conference! "
                + e.getMessage());
        }

	Logger.writeFile("ending conf " + conferenceId
	    + ":  permanent " + permanent
	    + ", mediaPreference " + mediaPreference);

	if (permanent) {
	    conferenceSender.printStatistics();

	    if (mediaPreference != null) {
		try {
		    setMediaInfo(mediaPreference);
		} catch (ParseException e) {
		    Logger.println(conferenceId
			+ ":  Can't change meeting media setting to "
			+ mediaPreference + ": " + e.getMessage());
		}

		mediaPreference = null;
	    }
	} else {
	    if (done) {
	        return;
	    }

	    done = true;

	    ConferenceManager.conferenceEventNotification(new ConferenceEvent(ConferenceEvent.CONFERENCE_ENDED, conferenceId));

	    synchronized(conferenceList) {
	        conferenceList.remove(this);
	    }

	    if (conferenceReceiver != loneConferenceReceiver) {
	        conferenceReceiver.end();
	    }

	    conferenceSender.printStatistics();
	}

	int activeConferences = 0;

	synchronized(conferenceList) {
            for (int i = 0; i < conferenceList.size(); i++) {
	        ConferenceManager conferenceManager =
		    (ConferenceManager) conferenceList.get(i);

	        if (conferenceManager.getMemberList().size() > 0) {
		    activeConferences++;
	        }
	    }
	}

	Logger.println("");
	Logger.println("Conference:  '" + conferenceId + "' has ended.  "
	    + "conferences still in progress:  " + activeConferences);
	Logger.println("");

	Logger.flush();

	if (totalMembers == 0) {
            /*
             * This is a great time to do a full garbage collection
             */
	     Logger.println("No conferences in progress, doing a full GC...");
             System.gc();
	}
    }

    public static void setDistributedBridge(DistributedBridge distributedBridge) {
	ConferenceManager.distributedBridge = distributedBridge;
    }

    private static void conferenceEventNotification(ConferenceEvent event)
    {
		if (distributedBridge != null)
		{
			try {
				distributedBridge.conferenceEventNotification(event);
			} catch (Exception e) {
				Logger.println("conferenceEventNotification exception:  " + e.getMessage());
				e.printStackTrace();
			}
		}

		RayoComponent.self.notifyConferenceMonitors(event);
    }

    public static int getNumberOfConferences() {
	return conferenceList.size();
    }

    public static int getTotalMembers() {
	return totalMembers;
    }

    public static int getNumberOfMembers(String conferenceId)
	    throws ParseException {

	ConferenceManager conferenceManager =
	    findConferenceManager(conferenceId);

	return conferenceManager.getNumberOfMembers();
    }

    public int getNumberOfMembers() {
	if (distributedBridge != null) {
	    return distributedBridge.getNumberOfMembers(conferenceId);
	}

	return getMemberList().size();
    }

    /**
     * Get the conference identifier.
     *
     * @return conferenceId String identifying this conference
     */
    public String getId() {
	return conferenceId;
    }


    public String getCallId() {
		return callId;
    }

    public void setCallId(String callId) {
		this.callId = callId;
    }

    public static String getCallId(String conferenceId)
    {
		try {
			ConferenceManager conferenceManager = findConferenceManager(conferenceId);
			return conferenceManager.getCallId();
		} catch (ParseException e) {
			return null;
		}
    }

    public static void setCallId(String conferenceId, String callId)
    {
		try {
			ConferenceManager conferenceManager = findConferenceManager(conferenceId);
			conferenceManager.setCallId(callId);
		} catch (ParseException e) {

		}
    }

    public String getDisplayName() {
		return displayName;
    }


    public void setDisplayName(String displayName) {
		this.displayName = displayName;
    }

    public static String getDisplayName(String conferenceId)
    {
		try {
			ConferenceManager conferenceManager = findConferenceManager(conferenceId);
			return conferenceManager.getDisplayName();
		} catch (ParseException e) {
			return null;
		}
    }

    public static void setDisplayName(String conferenceId, String displayName)
    {
		try {
			ConferenceManager conferenceManager = findConferenceManager(conferenceId);
			conferenceManager.setDisplayName(displayName);
		} catch (ParseException e) {

		}
    }

    public void setPermanent(boolean permanent) {
	this.permanent = permanent;
    }

    public boolean isPermanent() {
	return permanent;
    }

    /**
     * Get the conferenceSender for this conference.
     *
     * @return conferenceSender ConferenceSender for this conference
     */
    public ConferenceSender getConferenceSender() {
    	return conferenceSender;
    }

    public ConferenceReceiver getConferenceReceiver() {
	return conferenceReceiver;
    }

    private void endConferenceSender() {
	synchronized (this) {
	    if (conferenceSender != loneConferenceSender) {
	        conferenceSender.end();
	    }
	    if (conferenceReceiver != loneConferenceReceiver) {
	        conferenceReceiver.end();
	    }
	}
    }

    public void setMediaPreference(String mediaPreference) {
	this.mediaPreference = mediaPreference;
	permanent = true;
    }

    /**
     * Get the memberList for this conference.
     *
     * @return memberList ArrayList of members for this conference.
     */
    public ArrayList getMemberList() {
	return memberList;
    }

    /**
     * Is this the first member.  The first member to call this gets true.
     * Others get false.  This is so that the first member can get a special
     * audio treatment.
     * @return true if this is the first member, false otherwise
     */
    public boolean isFirstMember()
    {
		synchronized (memberList) {
			if (isFirstMember == false) {
				return false;
			}

			isFirstMember = false;

			return memberList.isEmpty();
		}
    }

    public boolean isTransferCall()
    {
		return transferCall;
	}

    public static boolean isTransferCall(String conferenceId)
    {
		try {
			ConferenceManager conferenceManager = findConferenceManager(conferenceId);
			return conferenceManager.isTransferCall();
		} catch (ParseException e) {
			return false;
		}
    }

    public void setTransferCall(boolean transferCall)
    {
		this.transferCall = transferCall;
	}

    public boolean isPrivateCall()
    {
		return privateCall;
	}

    public void setPrivateCall(boolean privateCall)
    {
		this.privateCall = privateCall;
	}

    public CallParticipant getHeldCall()
    {
		return heldCall;
	}


    public static CallParticipant getHeldCall(String conferenceId)
    {
		try {
			ConferenceManager conferenceManager = findConferenceManager(conferenceId);
			return conferenceManager.getHeldCall();
		} catch (ParseException e) {
			return null;
		}
    }

    public void setHeldCall(CallParticipant heldCall)
    {
		this.heldCall = heldCall;
	}

    public static void setHeldCall(String conferenceId, CallParticipant heldCall)
    {
		try {
			ConferenceManager conferenceManager = findConferenceManager(conferenceId);
			conferenceManager.setHeldCall(heldCall);
		} catch (ParseException e) {

		}
    }

    public String getGroupName()
    {
		return groupName;
	}

    public void setGroupName(String groupName)
    {
		this.groupName = groupName;
	}

    /**
     * Add a new member to the conference
     *
     * @param cp  CallParticipant wishing to join the conference.
     * @return ConferenceMember
     */
    public ConferenceMember joinConference(CallParticipant cp)
	    throws IOException {

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

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

        if (conferenceAnswerTreatment != null) {
            cp.setCallEstablishedTreatment(conferenceAnswerTreatment);
	    cp.setCallAnsweredTreatment(null);
	    cp.setJoinConfirmationTimeout(0);
        }

	if (displayName != null) {
	    cp.setDisplayName(displayName);
	    cp.setConferenceDisplayName(displayName);
	}

	ConferenceMember member = new ConferenceMember(this, cp);

	joinConference(member);
	return member;
    }

    private void joinConference(ConferenceMember member) throws IOException {
        synchronized (memberList) {
            memberList.add(member);
	    totalMembers++;

	    String s = "";

	    conferenceReceiver.addMember(member);

            Logger.println("conferenceManager:  '" + conferenceId + "',"
                + " new member " + member + s
                + " total members:  " + memberList.size());
        }

		ConferenceEvent event = new ConferenceEvent(ConferenceEvent.MEMBER_JOINED, conferenceId);
		event.setCallId(member.getCallParticipant().getCallId());
		event.setMemberAddress(member.getMemberSender().getSendAddress());
		event.setMemberCount(memberList.size());
		conferenceEventNotification(event);
    }

    public static boolean hasCommonMix(String conferenceId) {
	synchronized(conferenceList) {
	    try {
                ConferenceManager conferenceManager =
		    findConferenceManager(conferenceId);
	        return conferenceManager.hasCommonMix();
	    } catch (ParseException e) {
		Logger.println(e.getMessage());
	    }

	    return false;
	}
    }

    public boolean hasCommonMix() {
	return wgManager.hasCommonMix();
    }

    public void joinDistributedConference(ConferenceMember member) {
	ConferenceEvent event = new ConferenceEvent(
	    ConferenceEvent.MEMBER_JOINED, conferenceId);

	event.setCallId(member.getCallParticipant().getCallId());
	event.setMemberAddress(
	    member.getMemberSender().getSendAddress());
	event.setIsDistributedBridge(
            member.getCallParticipant().isDistributedBridge());

	conferenceEventNotification(event);
    }

    /*
     * Transfer an incoming call to the target conference.
     */
    public void transferMember(ConferenceManager newConferenceManager,  ConferenceMember member) throws IOException
    {
		leave(member, true);		   // leave the temporary conference
		member.reinitialize(newConferenceManager, true);
		newConferenceManager.joinConference(member); // join the new conference
    }

    /**
     * Remove a member from a conference
     * @param member ConferenceMember leaving the conference
     */
    public void leave(ConferenceMember member) {
	leave(member, false);
    }

    /*
     * keepMember is set to true when a member transfers
     * from one conference to another.
     * This is used for incoming calls which require a temporary conference
     * until the caller specified the desired conference to enter.
     */
    public void leave(ConferenceMember member, boolean keepMember) {
	conferenceReceiver.removeMember(member);

	synchronized (this) {
	    synchronized (memberList) {
	        memberList.remove(member);
	        totalMembers--;

	        if (keepMember == false) {
            	    member.end();
	        }

	        Logger.println("conferenceManager:  '" + conferenceId
		    + "':  member " + member.toString()
		    + " leaving, remaining:  " + memberList.size());
	    }

	    if (member.joinedDistributedConference())
	    {
			ConferenceEvent event = new ConferenceEvent(ConferenceEvent.MEMBER_LEFT, conferenceId);

			event.setCallId(member.getCallParticipant().getCallId());
			event.setMemberAddress(member.getMemberSender().getSendAddress());
			event.setIsDistributedBridge(member.getCallParticipant().isDistributedBridge());
			ConferenceManager.conferenceEventNotification(event);
	    }

		ConferenceEvent event = new ConferenceEvent(ConferenceEvent.MEMBER_LEFT, conferenceId);
		event.setCallId(member.getCallParticipant().getCallId());
		event.setMemberAddress(member.getMemberSender().getSendAddress());
		event.setMemberCount(memberList.size());
		ConferenceManager.conferenceEventNotification(event);

	    boolean endOfDistributedConference = true;

	    synchronized (memberList) {
		for (int i = 0; i < memberList.size(); i++) {
		    ConferenceMember m = (ConferenceMember) memberList.get(i);

		    if (m.getCallParticipant().isDistributedBridge() == false) {
			endOfDistributedConference = false;
			break;
		    }
		}
	    }

	    if (endOfDistributedConference) {
		endAllCalls();
	    }

	    if (memberList.size() == 0) {
	        end();	// last member left, the conference is over
	    }
	}
    }

    private void endAllCalls() {
	synchronized (memberList) {
	    for (int i = 0; i < memberList.size(); i++) {
                 ConferenceMember member = (ConferenceMember)
                     memberList.get(i);

		 member.cancelRequest("End of Distributed Conference");
	    }
	}
    }

    /**
     * Get the ArrayList identifying all conferences.
     * @return ArrayList list of Conferences
     */
    public static ArrayList getConferenceList() {
	return conferenceList;
    }

    public static void useSingleSender(boolean useSingleSender) {
	if (ConferenceManager.useSingleSender == useSingleSender) {
	    return;
	}

	if (useSingleSender == true) {
	    ConferenceManager.useSingleSender = true;

	    loneConferenceSender = new ConferenceSender(conferenceList);

            for (int i = 0; i < conferenceList.size(); i++) {
                ConferenceManager conferenceManager =
		    (ConferenceManager) conferenceList.get(i);

		synchronized (conferenceManager) {
		    conferenceManager.endConferenceSender();

		    conferenceManager.setNewConferenceSender(
			loneConferenceSender);
		}
	    }
	} else {
	    synchronized (conferenceList) {
		ConferenceManager.useSingleSender = false;

		if (loneConferenceSender != null) {
	            loneConferenceSender.end();
		    loneConferenceSender = null;
		}

                for (int i = 0; i < conferenceList.size(); i++) {
                    ConferenceManager conferenceManager =
                        (ConferenceManager) conferenceList.get(i);

		    conferenceManager.setNewConferenceSender();
                }
	    }
	}
    }

    public static void setLoneReceiverPort(int loneReceiverPort)
	    throws ParseException {

	if (ConferenceManager.loneReceiverPort == loneReceiverPort) {
	    return;
	}

	if (totalMembers != 0) {
	    Logger.println(
		"Can't change loneReceiverPort while conferences are in progress");
	    throw new ParseException(
		"Can't change loneReceiverPort while conferences are in progress", 0);
	}

	ConferenceManager.loneReceiverPort = loneReceiverPort;
    }

    private void setNewConferenceSender(ConferenceSender conferenceSender) {
        synchronized (this) {
            this.conferenceSender = conferenceSender;
	}
    }

    private void setNewConferenceSender() {
        synchronized (this) {
            conferenceSender = new ConferenceSender(this);
	}
    }

    public static boolean useSingleSender() {
	return useSingleSender;
    }

    public static int loneReceiverPort() {
	return loneReceiverPort;
    }

    /**
     * Create a new conference with the specified media settings
     */
    public static void createConference(String conferenceId,
	    String mediaPreference, String displayName) throws ParseException {

	synchronized(conferenceList) {
	    ConferenceManager conferenceManager = getConference(conferenceId, mediaPreference, displayName, true);

	    if (conferenceManager.getMemberList().size() == 0) {
		if (Logger.logLevel >= Logger.LOG_INFO) {
		    Logger.println("Conference " + conferenceId
		        + " setting media preference to "
		        + mediaPreference);
		}

		conferenceManager.setPermanent(true);

		try {
		    conferenceManager.setMediaInfo(mediaPreference);
		} catch (ParseException e) {
		    try {
		        removeConference(conferenceId);
		    } catch (ParseException ee) {
		    }

		    Logger.println(conferenceId
			+ ":  Can't change meeting media setting to "
			+ mediaPreference + ": " + e.getMessage());
		    throw new ParseException(conferenceId
			+ ":  Can't change meeting media setting to "
			+ mediaPreference + ": " + e.getMessage(), 0);
		}
	    } else {
		/*
		 * Defer until conference ends
		 */
		if (Logger.logLevel >= Logger.LOG_INFO) {
		    Logger.println("Conference " + conferenceId
		        + " defer setting media preference to "
		        + mediaPreference);
		}

		conferenceManager.setMediaPreference(mediaPreference);
	    }
	}
    }

    public static void removeConference(String conferenceId)
	    throws ParseException {

	synchronized(conferenceList) {
            ConferenceManager conferenceManager =
		findConferenceManager(conferenceId);

	    if (conferenceManager.getMemberList().size() > 0) {
		throw new ParseException("can't remove conference:  '"
            	    + conferenceId
		    + "' because there are still calls in progress", 0);
	    }

	    conferenceManager.setPermanent(false);
	    conferenceManager.end();
	}
    }

    /*
     * End a conference
     */
    public static void endConference(String conferenceId)
	    throws ParseException {

	ConferenceManager conferenceManager;

	synchronized (conferenceList) {
            conferenceManager = findConferenceManager(conferenceId);

	    ArrayList memberList = conferenceManager.getMemberList();

	    synchronized (memberList) {
		for (int i = 0; i < memberList.size(); i++) {
                    ConferenceMember member = (ConferenceMember) memberList.get(i);

		    CallHandler callHandler = member.getCallHandler();
		    callHandler.cancelRequest("Conference forced to end");
		}

	    	conferenceManager.end();
	    }
	}
    }

    private static boolean allowShortNames = true;

    public static void setAllowShortNames(boolean allowShortNames) {
	ConferenceManager.allowShortNames = allowShortNames;
    }

    public static boolean allowShortNames() {
	return allowShortNames;
    }

    public static ConferenceManager findConferenceManager(
	    String conferenceId) throws ParseException {

	for (int i = 0; i < conferenceList.size(); i++) {
	    ConferenceManager conferenceManager = (ConferenceManager)
		conferenceList.get(i);

	    if (conferenceManager.getId().equals(conferenceId)) {
		return conferenceManager;
	    }

	    if (allowShortNames == true) {
	        String displayName = conferenceManager.getDisplayName();

	        if (displayName != null && displayName.equals(conferenceId)) {
		    return conferenceManager;
		}
	    }
	}

        throw new ParseException("Non-existent conference "
            + conferenceId, 0);
    }

    /**
     * Start a new conference or return existing Conference with the
     * specified id
     * @param conferenceId String identifier of conference
     * @return ConferenceManager
     */
    public static ConferenceManager getConference(String conferenceId) {
	return getConference(conferenceId, null, null, false);
    }

    public static ConferenceManager getConference(CallParticipant cp) {

	return getConference(cp.getConferenceId(), cp.getMediaPreference(),
	    cp.getConferenceDisplayName(), false);
    }

    public static ConferenceManager getConference(String conferenceId,
	    String mediaPreference, String displayName) {

	return getConference(conferenceId, mediaPreference, displayName,
	    false);
    }

    public static ConferenceManager getConference(String conferenceId,
	    String mediaPreference, String displayName, boolean permanent) {

	ConferenceManager conferenceManager;

	try {
	    conferenceManager = findConferenceManager(conferenceId);

	    if (Logger.logLevel >= Logger.LOG_INFO) {
	        Logger.println("found existing conference:  '"
		    + conferenceId + "'");
	    }
	    return conferenceManager;
	} catch (ParseException e) {
	}

	try {
	    conferenceManager =
		new ConferenceManager(conferenceId, mediaPreference,
		    displayName);
	} catch (SocketException e) {
	    Logger.error("Can't create conference " + conferenceId
	        + " " + e.getMessage());

	    return null;
	}

	synchronized(conferenceList) {
	    conferenceList.add(conferenceManager);
	}

	Logger.println("starting new conference:  '"
	    + conferenceId + "'.  "
	    + " conferences in progress:  " + conferenceList.size());

	conferenceManager.setPermanent(permanent);

	String id = conferenceManager.getId();

	if (displayName != null) {
	    id += ":" + mediaPreference + ":" + displayName;
	}

	ConferenceManager.conferenceEventNotification(new ConferenceEvent(ConferenceEvent.CONFERENCE_STARTED, id));

	return conferenceManager;
    }

    public MediaInfo getMediaInfo() {
	return mediaInfo;
    }

    public static void dropDb() {
	if (distributedBridge != null) {
	    distributedBridge.dropDb();
	} else {
	    Logger.println(
		"The Distributed Conference Manager is not installed");
	}
    }

    public static String getDistributedConferenceInfo() {
	if (distributedBridge != null) {
	    return distributedBridge.getDistributedConferenceInfo();
	}

        Logger.println("The Distributed Bridge Manager is not installed");
	return "The Distributed Conference Manager is not installed";
    }

    /**
     * Display information about each conference
     * @param requestHandler RequestHandler with socket to write output to
     */
    public static String getBriefConferenceInfo() {
	return getConferenceInfo(0);
    }

    public static String getAbbreviatedConferenceInfo() {
	return getConferenceInfo(1);
    }

    public static String getDetailedConferenceInfo() {
	return getConferenceInfo(2);
    }

    private static String getConferenceInfo(int format) {
	synchronized(conferenceList) {
	    if (conferenceList.size() == 0) {
		if (format != 0) {
	            return ("\n\n\n\n");
		}

		return "";
	    }

	    String s = "";

	    for (int i = 0; i < conferenceList.size(); i++) {
		ConferenceManager conferenceManager = (ConferenceManager)
		    conferenceList.get(i);

		String id = conferenceManager.getId();

		String displayName = conferenceManager.getDisplayName();

		if (format != 1) {
		    if (displayName != null) {
			id += " '" + displayName + "'";
		    }
		} else {
		    if (displayName == null) {
			if (id.length() >= 14) {
		            id = id.substring(0, 13);
			}
		    } else {
		        id = "'" + displayName + "'";
		    }
		}

		s += "Conference Id: " + id + " ";

		s += conferenceManager.getMediaInfo().toString();

		s += " Members=" + conferenceManager.getMemberList().size();

		if (conferenceManager.isPermanent()) {
		    s += " persistent";
		}

		String recordingFile =
		    conferenceManager.getWGManager().getRecordingFile();

		if (recordingFile != null) {
		    s += " Recording to " + recordingFile;
		}

		s += "\n";

		if (format == 0) {
		    continue;
		}

		/*
		 * Copy the member list so we can avoid unnecessary
		 * synchronization
		 */
		ArrayList memberList = (ArrayList)
		    conferenceManager.getMemberList().clone();

		for (int n = 0; n < memberList.size(); n++) {
                    ConferenceMember member = (ConferenceMember)
			memberList.get(n);

		    MemberSender memberSender = member.getMemberSender();
		    MemberReceiver memberReceiver = member.getMemberReceiver();

		    String info = " ";

		    MediaInfo transmitMediaInfo = memberSender.getMediaInfo();

		    if (transmitMediaInfo != null) {
		        info += transmitMediaInfo.toString();
		    }

		    MediaInfo receiveMediaInfo = memberReceiver.getMediaInfo();

		    if (receiveMediaInfo != null) {
			if (transmitMediaInfo.getEncoding() !=
			        receiveMediaInfo.getEncoding() ||
			        transmitMediaInfo.getSampleRate() !=
			        receiveMediaInfo.getSampleRate() ||
			        transmitMediaInfo.getChannels() !=
			        receiveMediaInfo.getChannels()) {

			    /*
			     * The member is transmitting at a different
			     * media setting than it is receiving.
			     */
			    info += " Transmit:"
				+ memberReceiver.getMediaInfo();
			}
		    }

		    CallParticipant cp = member.getCallParticipant();

		    if (cp.isMuted()) {
		        info += " MUTED";
		    }

		    if (cp.isConferenceMuted()) {
		        info += " CONFERENCE_MUTED";
		    }

		    if (cp.isConferenceSilenced()) {
			info += " MAIN_CONFERENCE_SILENCED";
		    }

		    if (memberReceiver.doNotRecord() == true) {
		        info += " RECORDED NOT ALLOWED";
		    }

    	            if (memberReceiver.getFromRecordingFile() != null) {
    		        info += " Recording from member in "
    			    + memberReceiver.getFromRecordingFile();
    		    }

		    if (cp.isRecorder()) {
		        String toRecordingFile =
			    memberSender.getCallParticipant().getToRecordingFile();

    	                if (toRecordingFile != null) {
    		            info += " Recording to member in " + toRecordingFile;
    		        } else {
			    info += " Recorder";
			}
		    }

    		    if (cp.speexEncode()) {
    		        info += " SpeexEncode";
    		    }

    		    //
    		    // For debugging
    		    //
		    if (memberSender.getSendAddress() != null) {
			String gateway = "";

			String address =
			    memberSender.getSendAddress().toString();

			int ix = address.indexOf("/");

			if (ix >= 0) {
			    address = address.substring(ix + 1);

			    if ((ix = address.indexOf(":")) >= 0) {
				address = address.substring(0, ix);
			    }
			}

			if (address.equals("10.6.4.192")) {
			    gateway = " Menlo Park Gateway";
			} else if (address.equals("129.148.75.22")) {
			    gateway = " Burlington Gateway";
			} else if (address.equals("10.1.224.22")) {
			    gateway = " Broomfield Gateway";
			}

			info += gateway;
		    }

		    if (format == 2) {
		        info += conferenceManager.getWGManager().getWhisperGroupInfo(member);
		    }

		    cp = member.getCallParticipant();

		    id = cp.toString();

		    if (format == 1) {
			id = cp.toConsiseString();
		    }

		    s += "    " + id + info + "\n";
		}

		s += "\n";
            }

	    return s + "\n";
	}
    }

    /**
     * Record a specified conference
     */
    public static void recordConference(String conferenceId, boolean enabled,
	    String recordingFile, String recordingType) throws ParseException {

        synchronized(conferenceList) {
	    ConferenceManager conferenceManager;

	    if ((conferenceManager = findConferenceManager(conferenceId)) !=
		    null) {

		conferenceManager.recordConference(enabled, recordingFile,
		    recordingType);

		return;
	    }
	    throw new ParseException("No such conference " + conferenceId, 0);
	}
    }

    public void recordConference(boolean enabled, String recordingFile,
	    String recordingType) throws ParseException {

	if (wgManager == null) {
	    return;
	}

	try {
	    wgManager.recordConference(enabled, recordingFile, recordingType);
	} catch (IOException e) {
	    throw new ParseException(e.getMessage(), 0);
	}
    }

    public static void setConferenceJoinTreatment(String conferenceId,
            String treatment) throws ParseException {

        synchronized(conferenceList) {
	    ConferenceManager conferenceManager;

	    conferenceManager = findConferenceManager(conferenceId);

	    conferenceManager.setConferenceJoinTreatment(treatment);
        }
    }

    private String conferenceJoinTreatment;

    public void setConferenceJoinTreatment(String treatment) {
        conferenceJoinTreatment = treatment;
    }

    private String conferenceLeaveTreatment;

    public static void setConferenceLeaveTreatment(String conferenceId,
            String treatment) throws ParseException {

        synchronized(conferenceList) {
	    ConferenceManager conferenceManager;

	    conferenceManager = findConferenceManager(conferenceId);

	    conferenceManager.setConferenceLeaveTreatment(treatment);
        }
    }

    public void setConferenceLeaveTreatment(String treatment) {
        conferenceLeaveTreatment = treatment;
    }

    private String conferenceAnswerTreatment;

    public static void setConferenceAnswerTreatment(String conferenceId,
            String treatment) throws ParseException {

        synchronized(conferenceList) {
            ConferenceManager conferenceManager;

            conferenceManager = findConferenceManager(conferenceId);

            conferenceManager.setConferenceAnswerTreatment(treatment);
        }
    }

    public void setConferenceAnswerTreatment(String treatment) {
	if (Logger.logLevel >= Logger.LOG_INFO) {
	    Logger.println("Setting conference answer treatment to " + treatment);
	}
        conferenceAnswerTreatment = treatment;
    }

    public String getConferenceAnswerTreatment() {
	return conferenceAnswerTreatment;
    }

    public static void playTreatmentToAllConferences(String treatment,
	    double[] volume) throws ParseException {
    }

    public static void playTreatmentToAllConferences(String treatment)
	    throws ParseException {

        synchronized(conferenceList) {
            for (int i = 0; i < conferenceList.size(); i++) {
                ConferenceManager conferenceManager =
                    (ConferenceManager) conferenceList.get(i);

		playTreatment(conferenceManager.getId(), treatment);
	    }
	}
    }

    public static void playTreatment(String conferenceId,
	    String treatment, double[] volume) throws ParseException {
    }

    /**
     * Play a treatment to the specified conference
     */
    public static void playTreatment(String conferenceId,
	    String treatment) throws ParseException {

	if (Logger.logLevel >= Logger.LOG_MOREINFO) {
	    Logger.println("playing treatment " + treatment + " to "
		+ conferenceId);
	}

        synchronized(conferenceList) {
	    ConferenceManager conferenceManager;

	    conferenceManager = findConferenceManager(conferenceId);

	    try {
		conferenceManager.addTreatment(treatment);
	    } catch (IOException e) {
		throw new ParseException("bad treatment "
		    + " " + e.getMessage(), 0);
	    }
	    return;
        }
    }

    public void addTreatment(String treatment) throws IOException {
	if (!done) {
	    synchronized (this) {
		if (wgManager.hasCommonMix()) {
	            wgManager.addConferenceTreatment(new TreatmentManager(
			treatment, 0, mediaInfo.getSampleRate(),
			mediaInfo.getChannels()));
		} else {
                    synchronized (memberList) {
                        for (int i = 0; i < memberList.size(); i++) {
                            ConferenceMember member = (ConferenceMember)
                                memberList.get(i);

			    member.addTreatment(new TreatmentManager(
				    treatment, 0, mediaInfo.getSampleRate(),
				    mediaInfo.getChannels()));
			}
		    }
		}
	    }
	}
    }

    public static void pauseTreatment(String conferenceId, String treatment,
	    boolean isPaused) throws ParseException {

        if (Logger.logLevel >= Logger.LOG_MOREINFO) {
            Logger.println("pausing treatment " + treatment + " to "
                + conferenceId);
        }

        synchronized(conferenceList) {
            ConferenceManager conferenceManager;

            conferenceManager = findConferenceManager(conferenceId);

            conferenceManager.getWGManager().pauseConferenceTreatment(
                treatment, isPaused);
        }
    }

    public static void stopTreatment(String conferenceId, String treatment)
	    throws ParseException {

        if (Logger.logLevel >= Logger.LOG_MOREINFO) {
	    Logger.println("stopping treatment " + treatment + " to "
		+ conferenceId);
	}

        synchronized(conferenceList) {
            ConferenceManager conferenceManager;

            conferenceManager = findConferenceManager(conferenceId);

            conferenceManager.getWGManager().removeConferenceTreatment(
		treatment);
        }
    }

    /**
     * get conference statistics
     */
    public static void printStatistics() {
        synchronized(conferenceList) {
	    if (loneConferenceSender != null) {
		loneConferenceSender.printStatistics();
	    }

            for (int i = 0; i < conferenceList.size(); i++) {
                ConferenceManager conferenceManager =
		    (ConferenceManager) conferenceList.get(i);

		ConferenceSender conferenceSender =
                    conferenceManager.getConferenceSender();

		if (loneConferenceSender == null) {
		    conferenceSender.printStatistics();
		}

                ArrayList memberList = conferenceManager.getMemberList();

                for (int n = 0; n < memberList.size(); n++) {
                    ConferenceMember member = (ConferenceMember)
                        memberList.get(n);

                    member.printStatistics();
                }
            }
        }
        Logger.flush();
    }

}
