/***********************************************************************************
 *
 * THIS SOFTWARE IS COPYRIGHT STEVEN MORRIS 2002. ALL RIGHTS RESERVED
 *
 * THIS SOFTWARE IS FREE FOR NON COMMERCIAL USE FOR THE PURPOSE OF LEARNING MHP.  ANY
 * USE FOR OTHER PURPOSES IS PROHIBITED UNLESS WRITTEN AGREEMENT IS OBTAINED.
 *
 * DISTRIBUTION OF THIS CODE IS PROHIBITED
 */

// Import the standard package that we need to be an Xlet
import javax.tv.xlet.*;

// The tuning API is located in the org.davic.tuning.* package.  As with
// so many other media-related operations, we also need the DvbLocator
// class.
import org.davic.net.tuning.*;
import org.davic.net.dvb.DvbLocator;

// The tuning API is one of the APIs that uses the resource notification API.
import org.davic.resources.*;


/**
 * This class tunes to a different transport stream using the tuning API.
 * This may be useful in the case where a private data stream is being
 * broadcast on a different transport stream, and you need to tune to it
 * in order to access the private data.
 *
 * Since this is quite a complex Xlet that may take a while to finish
 * tuning, we do most of the work in a separate thread.  For this reason,
 * the Xlet implements the Runnable interface.  It also implements the
 * org.davic.net.tuning.NetworkInterfaceListener interface to receive
 * tuning-related events, and the org.davic.resources.ResourceClient
 * interface to deal with resource management issues.
 */
public class TuningExample
	implements Xlet,
	           Runnable,
	           NetworkInterfaceListener,
	           ResourceClient {

	// A private variable to store our Xlet context.  In this case, we do actually
	// use this, showing why it's a good idea to keep a reference to the Xlet context.
	private XletContext context;

	// A private variable that keeps a reference to the thread that
	// will do all of the work.
	private Thread myWorkerThread;

		// A private field to hold the current state.  This is needed because the startXlet()
	// method is called both to start the Xlet for the first time and also to make the
	// Xlet resume from the paused state.  This filed lets us keep track of whether we're
	// starting for the first time.
	private boolean hasBeenStarted;


	/**************************************************************************
	 *
	 * The methods below this line are the standard Xlet methods.  These mostly do
	 * nothing surprising,since we do most of the work in a separate thread.
	 */

	/**
	 * A default constructor, included for completeness.
	 */
	public TuningExample()
	{
	}

	/**
	 * Initialise the Xlet.  In this case, we have no special initialisation
	 * that we need to do.
	 */
	public void initXlet(XletContext context)
		throws XletStateChangeException
	{
		// We keep a reference to our Xlet context because we will need it later.
		this.context = context;
	}

	/**
	 * Start the Xlet.  This is where we actually start doing useful work.
	 */
	public void startXlet()
		throws XletStateChangeException
	{
		// Check if the Xlet has been started and resuming from the paused state,
		// or whether it's being started for the first time.  If it's being
		// started for the first time, we reate a new thread to do the work
		if (!hasBeenStarted) {
			// startXlet() should not block for too long, and waiting for sections
			// to be filtered is definitely too long.  To solve this, we start
			// another thread to do the work.
			myWorkerThread = new Thread(this);
			myWorkerThread.start();
		}
		else {
			// If we're resuming from the paused state, we don't need to set
			// up the thread.
			doResume();
		}
	}

	/**
	 * Pause the Xlet.
	 */
	public void pauseXlet()
	{
		// The doPause() method does everything we need to pause
		// the Xlet
		doPause();
	}

	/**
	 * Destroy the Xlet.
	 */
	public void destroyXlet(boolean unconditional)
		throws XletStateChangeException
	{
		//tell the user that the method has been called
		System.out.println("destroyXlet() called");

		if (unconditional) {
			// We have been ordered to terminate, so we obey the
			// order politely and release any  scarce resources
			// that we are holding.
		}
		else {
			// We have had a polite request to die, so we can
			// refuse this request if we want.
			throw new XletStateChangeException("Please don't let me die!");
		}
	}


	/**************************************************************************
	 *
	 * The methods below this point are the ones which actually do all of the work
	 * related to the tuning operation.
	 */

	// These fields are used to hold the various objects form the tuning
	// API that we use to do the tuning.
	NetworkInterfaceManager manager;
	NetworkInterfaceController controller;
	NetworkInterface networkInterface;

	// This variable will hold the locator referring to the transport
	// stream that we will tune to.  If we're using the tuning API,
	// we use the DvbLocator that's defined in the DAVIC APIs.
	DvbLocator targetTransportStream;

	// Since tuning can take a little time, we need some way of synchronizing
	// so that we know when tuning has finished.  This object is used to
	// provide that synchronization.
	Object tuningFinished = new Object();

	// A boolean variable to tell us when tuning has finished.
	// We use this to avoid tuning again if the Xlet is
	//paused and restarted after tuning has completed.  Using the
	// synchronization object above would get messy, so we'll
	// use a separate variable
	boolean tuningHasFinished;

	/**
	 * Tune to a new transport stream, using the DAVIC tuning API.
	 */
	public void run() {

		// Check if we've already been started.  If we have been started, we don't
		// need to set up the network interface and associated objects.
		if (!hasBeenStarted) {

			hasBeenStarted = true;

			// We haven't started tuning yet, so it can't have finished.
			tuningHasFinished = false;

			// Set up the network interface and network interface controller
			// that we need for the tuning operation.
			setupNetworkInterface();

			// Set the flag to tell us we've been started.  We do this here, instead
			// of in startXlet() as in some other examples, because doing the check
			// here makes the thread handling code simpler
			hasBeenStarted = true;
		}

		// Now we're ready to perform the tuning operation

		// We need to reserve the network interface controller before
		// we can use it.  This reserves all the resources that we
		// need to be able to tune to a new transport stream.  Since
		// this (and ther tuning operation itself) can throw an exception,
		// we enclose it in a 'try' block.
		try {
			controller.reserve(networkInterface,null);

			// Now we can tune to the transport stream we want.  This is an
			// asynchronous request, so calling this method doesn't mean we've
			// actually finished tuning
			controller.tune(targetTransportStream);

			// We've started the tuning action, so we could release the resource
			// and let other applications access it if we wanted to. In this
			// case, we will do it when the tuning action has completed in
			// order to make sure there are no problems.

			// The important thing is to make sure that we do release the
			// resources when we can, and that we do co-operate nicely with other
			// applications.  Good citizenship is an important feature in an
			// MHP application.
		}
		catch (NetworkInterfaceException nie) {
			// There's not much we can do if an exception is thrown, so we will
			// just print an error message and exit.
			System.out.println("Problem with tuning:");
			nie.printStackTrace();
			return;
		}

		// The other work is done in the event handler below, after the tuning
		// operation completes.

		// Before this method exits, we wait for the tuning operation to complete.  We
		// do this by using the synchronisation object that we have.
		synchronized(tuningFinished) {
			try {
				// The 'notify' that corresponds to this 'wait' takes place in
				// the event handler method below.
				tuningFinished.wait();
			}
			catch (InterruptedException ie) {
				// Ignore the exception, since there's not much we can do about it.
			}
		}
	}

	private void setupNetworkInterface() {
		// First, we create a locator that refers to the transport stream
		// that we will tune to.
		// This can throw an exception so we need to enclose it in a
		// 'try' block.
		try {
			targetTransportStream = new DvbLocator("dvb://122.100.3");
		}
		catch (org.davic.net.InvalidLocatorException ile) {
			// We should never have to catch this, because we know
			// that our locator is well-formed.
			ile.printStackTrace();
		}

		// Before we can do any tuning, we need to get the resources that we need.
		// We need to get a network interface that can tune to the correct
		// transport stream (because if we have a box with two different network
		// interfaces, e.g. cable and satellite, different transport streams may
		// be available on the two interfaces).  Remember that a network
		// interface is an interface to the broadcast network, NOT an IP network.

		// First, we get a reference to the network interface manager
		manager = NetworkInterfaceManager.getInstance();
		//  Now that we have that, we can get a reference to a network interface.
		// In this case, we can be pretty sure we only have one, so we will choose
		// the first network interface in the array.  Real applications should
		// check to see that the network interface can receive the transport
		// stream they want, and choose another network interface if it can't.
		networkInterface = manager.getNetworkInterfaces()[0];

		// We need to keep track of what the network interface is doing, so we
		// add ourselves as an event listener for it.
		networkInterface.addNetworkInterfaceListener(this);

		// A NetworkInterface object gives read-only access to the network
		// interface.  To actually control it, we need a
		// NetworkInterfaceController.  We pass a reference to ourselves
		// as the resource client for the controller.
		controller = new NetworkInterfaceController(this);

		// Creating it does nothing - we have to use it to reserve the network
		// interface before we can do anything useful.  This action, and the
		// tuning operation itself, can throw an exception, so we enclose
		// all of this in a 'try' block.
	}

	/**
	 * Take the actions needed to pause the Xlet.  In this case, that means
	 * stopping the section filters and releasing them, since they are
	 * scarce resources.  We also kill the filtering thread, since this
	 * isn't needed once the Xlet is paused and we should kill unnecessary
	 * threads when we pause ourselves.
	 */
	private void doPause() {

		// Release the resources that we're holding for the tuning operation.
		// This can throw an exception, so we have to enclose it in a
		// 'try' block.
		try {
			controller.release();
		}
		catch (NetworkInterfaceException nie) {
			// Ignore the exception
			nie.printStackTrace();
		}

		// We also need to notify the main method (and anything else that is
		// waiting for tuning to finish) that tuning has finished.
		synchronized(tuningFinished) {
				tuningFinished.notify();
		}
		// Now kill the thread
		myWorkerThread.stop();
	}

	/**
	 * Take the actions needed to resume the Xlet from the paused state.
	 * In our case, that means restarting the thread that does the
	 * section filtering.
	 */
	private void doResume() {

		if (tuningHasFinished) {
			// We've done everything we wanted to, so there's no
			// point doing anything here
			return;
		}
		else {
			// Start the thread and retry the tuning
			// operation
			myWorkerThread.start();
		}
	}

	/**
	 * This method is inherited from org.davic.net.tuning.NetworkInterfaceListener,
	 * and gets called when the tuning API generates an event for the
	 * NetworkInterface object that we have registered ourselves as a listener for.
	 */
	public void receiveNIEvent(NetworkInterfaceEvent event) {
		// If the event indicates that the tuning operation is over, we
		// release the resources that we claimed.


		if (event instanceof NetworkInterfaceTuningOverEvent) {

			// This can throw an exception, so we have to enclose it in a
			// 'try' block.
			try {
				controller.release();
			}
			catch (NetworkInterfaceException nie) {
				// Ignore the exception
				nie.printStackTrace();
			}

		// Set the flag that says tuning has finished.
		tuningHasFinished = true;

		// We also need to notify the main method (and anything else that is
		// waiting for tuning to finish) that tuning has finished.
			synchronized(tuningFinished) {
				tuningFinished.notify();
			}
		}
	}


	/**************************************************************************
	 *
	 * These methods are inherited from the ResourceClient interface and are used
	 * to tell the application when it has lost access to its resources (or
	 * when it is about to lose access to them).  This gives the application a
	 * chance to clean up when it loses access to a resource, and gives it a
	 * chance to handle things gracefully.
	 */


	/**
	 * This method gets called when the resource manager requests that we give
	 * up a resource.  We can refuse to do so, and that's what we do in this
	 * case (even though we shouldn't).
	 */
	public boolean requestRelease(ResourceProxy proxy, Object requestData) {
		return false;
	}

	/**
	 * This method gets called when the resource manager informs us that we must
	 * release a resource
	 */
	public void release(ResourceProxy proxy) {
		// Release the network interface that we have reserved for this Xlet.
		// This can throw an exception, so we have to enclose it in a 'try' block.
		try {
			controller.release();
		}
		catch (NetworkInterfaceException nie) {
			// Ignore the exception
			nie.printStackTrace();
		}

		// We also need to notify the main method (and anything else that is
		// waiting for tuning to finish) that tuning has finished.
		synchronized(tuningFinished) {
			tuningFinished.notify();
		}
	}

	/**
	 * This method gets called when the resource manager tells us we've
	 * lost access to a resource and we should clean up after ourselves.
	 */
	public void notifyRelease(ResourceProxy proxy) {
		// Release the network interface that we have reserved for this Xlet.
		// This can throw an exception, so we have to enclose it in a 'try' block.
		// We've lost the resource, so whether we actually release it is really
		// academic, but it's good practise
		try {
			controller.release();
		}
		catch (NetworkInterfaceException nie) {
			nie.printStackTrace();
		}

		// Tell everything that tuning has finished, even though it didn't.  This
		// avoids any chance of deadlocks.
		synchronized(tuningFinished) {
			tuningFinished.notify();
		}
	}


}