/*
Copyright (c) 2006, Geomatics and Cartographic Research Centre, Carleton 
University All rights reserved.

Redistribution and use in source and binary forms, with or without 
modification, are permitted provided that the following conditions are met:

 - Redistributions of source code must retain the above copyright notice, 
   this list of conditions and the following disclaimer.
 - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
 - Neither the name of the Geomatics and Cartographic Research Centre, 
   Carleton University nor the names of its contributors may be used to 
   endorse or promote products derived from this software without specific 
   prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.

$Id$
*/
package ca.carleton.gcrc.atlas;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

/**
 * Manager class for creating and initializing the <code>StandardMidiFilePlayer</code>
 * which handles the details of MIDI file playback and interactive control of the
 * playback.
 * 
 * @author Glenn Brauen
 *
 */
public class MidiManager {
	/**
	 * Base URL of the document containing the applet. All relative
	 * URL for tracks should be computed from this URL.
	 */
	private URL m_documentBase;
	
	/**
	 * The player for the standard MIDI file.
	 */
	private StandardMidiFilePlayer m_player;

	/**
	 * boolean indicating whether a global mute is in effect.
	 */
	private boolean m_allTracksMuted;
	
	/**
	 * Constructor based on a URL that represents the document's base.
	 * @param documentBase_
	 */
	public MidiManager(URL documentBase_) {
		init(documentBase_);
	}
	
	/**
	 * Constructor based on a string that represents the document base URL.
	 * @param documentBase_ URL of the document where all relative URL are computed from.
	 * @throws MalformedURLException Thrown if the given URL string is not a well formed 
	 * URL.  
	 */
	public MidiManager(String documentBase_) throws MalformedURLException {
		URL temp = new URL(documentBase_);
		init(temp);
	}
	
	private void init(URL documentBase_) {
		m_documentBase = documentBase_;
		m_player = null;
		m_allTracksMuted = false;
	} // end init
	
	protected final class Debug {
		  protected static final int standardLevel = 0;
		  protected static final int detailLevel = 1;
		  
		  protected static final boolean ON = false;
		  protected static final int LEVEL = standardLevel;
	} 
	
	protected void DebugLog(String str) {
		if (Debug.ON) System.out.println(str);
	}
	
	/**
	 * This method should be called by an applet 'start' method.
	 */
	public void resume() {
		if (null != m_player) {
			m_player.resume();
		}
	}

	/**
	 * This method should be called to stop all sound activity immediately. It is useful
	 * for applets and should be called from the method 'stop' in the applet.
	 */
	public void pause() {
		if (null != m_player) {
			m_player.pause();
		}
	}

	/**
	 * This method should be called to release all resources held by tracks. Useful
	 * for applets when 'destroy' is called.
	 */
	public void cleanUp() {
		if (null != m_player) {
			m_player.destroy();
		}
	}
	
	/**
	 * Set muting on all tracks in the current sequence according to the input flag_.
	 * @param flag_ true means mute, false means unmute.
	 */
	public void setMutingForAllSounds(boolean flag_) {
		m_allTracksMuted = true;
		if (null != m_player) {
			m_player.setAllTrackMutes(flag_);
		}
	} // setMutingForAllSounds()
	
	/**
	 * Set muting on the specified track in the current sequence according to the input flag_.
	 * If the track specified is out of range for the current sequence the request is ignored.
	 * @param track_ the track to be muted/unmuted.
	 * @param flag_ true means mute, false means unmute.
	 */
	public void setMutingForTrack(int track_, boolean flag_) {
		if (null != m_player) {
			m_player.setTrackMute(track_, flag_);
		}
	} // setMutingForTrack()

	/**
	 * This method converts a string to a URL. It attempts to convert relative URL
	 * to absolute ones using the document base URL.
	 * @param urlString_ Desired URL in a String. 
	 * @return A URL instance representing the string given in paramter.
	 * @throws SoundException If the resulting URL is not valid.
	 */
	private URL urlFromStringAddress(String urlString_) throws SoundException {
		URL result;
		try {
			
			result = new URL(urlString_); // attempt to use input as absolute URL
			
		} catch (MalformedURLException e) {
			
			// use string as relative to documentBase stored earlier...
			URI tempURI;
			try {
				tempURI = m_documentBase.toURI();
			} catch (URISyntaxException e2) {
				DebugLog("SoundApplet MidiManager malformed URI: " + m_documentBase.toString());
				throw new SoundException("Malformed URI: " + m_documentBase.toString());
			}
			try {
				result = tempURI.resolve(urlString_).toURL();
			} catch (MalformedURLException e1) {
				DebugLog("SoundApplet MidiManager malformed URL: " + urlString_);
				throw new SoundException("Malformed URL: " + urlString_);
			}
			
		}
		
		return result;
	} // end urlFromStringAddress()
	
	/**
	 * Load a sequence using the specified urlString_.  This creates a
	 * <code>StandardMidiFilePlayer</code> object containing all of the components required
	 * to play the sequence.  The sequence does not begin to play until one of
	 * <code>playSequence</code> or <code>loopSequence</code> is called.
	 * 
	 * @param urlString_ string speciifying a relative or absolute URL for a MIDI sequence
	 *                   to be loaded.
	 */
	public void loadMidiSequence(String urlString_) {
		unloadMidiSequence(); // if a sequence already loaded, unload it first.
		
		try {
			m_player = new StandardMidiFilePlayer(urlFromStringAddress(urlString_));
		} catch (SoundException e) {
			m_player = null;
			return;
		}

		m_player.setAllTrackMutes(m_allTracksMuted);
	} // end loadMidiSequence()
	
	/**
	 * Unload the current sequence and destroy the <code>StandardMidiFilePlayer</code>.
	 */
	public void unloadMidiSequence() {
		// only load one sequence concurrently so don't need a name to unload.
		if (null != m_player) {
			m_player.destroy();
			m_player = null;
		}
	} // end unloadMidiSequence()
	
	/**
	 * Play the current sequence once.  Only a single sequence may be concurrently loaded
	 * so no sequencer identifier is required.
	 */
	public void playSequence() {
		if (null != m_player) {
			m_player.start();
		}
	} // end playSequence()

	/**
	 * Play the current sequence indefinitely in a loop.  Only a single sequence may
	 * be concurrently loaded so no sequencer identifier is required.
	 */
	public void loopSequence() {
		if (null != m_player) {
			m_player.setLooping(true);
			m_player.start();
		}
	} // end playSequence()

	/**
	 * Stop the current sequence.  Playback parameters (such as the loop setting or
	 * the current playback position) are not modified.  Only a single sequence may
	 * be concurrently loaded so no sequencer identifier is required.
	 */
	public void stopSequence() {
		if (null != m_player) {
			m_player.stop();
		}
	} // end playSequence()
	
	/**
	 * Set the master gain scale multiplier as a percentage.  This value is used to
	 * adjust the gain settings contained in the playing sequence.
	 * 
	 * @param gainScale_ master volume gain multiplier (as a percentage).
	 */
	public void setMasterGainScaleMultiplier(int gainScale_) {
		if (null != m_player) {
			m_player.setMasterGainScaleMultiplier(gainScale_);
		}
	} // setGainScaleMultiplier()
	
	/**
	 * Set the master gain scale multiplier for the specified channel_ as a percentage.
	 * This value is used to
	 * adjust the gain settings for the specified channel_ contained in the playing sequence.
	 * 
	 * @param channel_ the MIDI channel to adjust volumes for.
	 * @param gainScale_ master volume gain multiplier (as a percentage).
	 */
	public void setChannelGainScaleMultiplier(int channel_, int gainScale_) {
		if (null != m_player) {
			m_player.setChannelGainScaleMultiplier(channel_, gainScale_);
		}
	} // setChannelGainScaleMultiplier()
	
	public void setNoteTranspose(int transpose_) {
		if (null != m_player) {
			m_player.setNoteTranspose(transpose_);
		}
	} // setNoteTranspose()

	/*
	 * D e b u g   d i s p l a y   r o u t i n e s
	 */
	
	/**
	 * Display status of Midi Manager on the java console.  Non-command-path display routine
	 * used to periodically dump the current state of the applet.
	 */
	public void displayAppletState() {
		if (null != m_player) {
			m_player.displayAppletState();
		}
	}
	
} // end MidiManager