/***********************************************************************************
 *
 * 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 org.davic.mpeg.sections package implements the section filtering API
import org.davic.mpeg.sections.*;

// As with many other APIs, the section filtering API uses the DAVIC resource notification API
import org.davic.resources.*;

// The basic MPEG concepts API and the tuning API are used to find the transport
// stream we want to filter on.  We will see how these get used later.
import org.davic.mpeg.*;
import org.davic.net.tuning.*;



/**
  * This Xlet parses DSM-CC stream events from the transport stream by filtering the
  * appropriate sections, and then prints the event ID form the stream event.
  *
  * Since this is quite a complex Xlet that will take some time to filter the sections,
  * we do most of the work in a separate thread.  For this reason,  the Xlet implements
  * the Runnable interface.  It also implements the SectionFilterListener interface
  * from org.davic.mpeg.sections.*, because it needs this interface to get the filtered
  * sections.
  *
  * Finally, it implements the ResourceClient interface from org.davic.resources.* -
  * this allows the MHP middleware to tell the application if it has lost access to
  * any of the sections filters that it's using (since these are considered a scarce
  * resource)
  */
public class SectionFilterExample
	implements Xlet,
				Runnable,
				SectionFilterListener,
				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 SectionFilterExample()
	{
	}

	/**
	 * 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 it's good practise to do so.
		// We don't need it in this example, but that's beside the point.
		this.context = context;

		// The Xlet has not yet been started for the first time, so set
		// this variable to false.
		hasBeenStarted = false;
	}

	/**
	 * 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 section filtering.  These set up the filters, and display the
	 * filtered data.
	 */


	// The constants below are used to define what we filter for,and where we filter for it.

	// This table ID is used for DSM-CC stream events.  This can be changed to
	// filter any table ID that you are interested in.
	static final int TABLE_ID = 0x3D;

	// The PID of the stream we will be filtering.  You will need to change this to
	// reflect the PID you want to filter on.
	static final int STREAM_PID = 100;

	// The descriptor tag for a DSM-CC stream event descriptor
	static final int STREAM_EVENT_DESCRIPTOR_TAG = 26;

	// Constant defining the size of an MPEG section header
	static final int SECTION_HEADER_SIZE = 8;

	// Since we will use a RingSectionFilter, we need to define the size of the buffer.
	// Using a single element would be the same as using a SimpleSectionFilter.  Instead,
	// we will use a buffer size of 7, since we don't know how often we will filter a
	// new section.  This is a tradeoff between memory efficiency and the chances of
	// missing a section, and the size of the buffer really depends on the frequency at
	// which sections will be matched and how much work processing you will be doing
	// on each section.  If you're doing a lot of work, the easiest way is to simply
	// copy the section data to another data structure and free the buffer slot.  This
	// allows you to use a fairly small buffer for your filter, while using a data
	// structure more suitable to your application for actually holding the section
	// data.  If you want to change this value, you can.
	static final int NUMBER_OF_SECTIONS = 7;



	// Since we will use a RingSectionFilter to filter the sections, we need to keep
	// track of which slot in the buffer contains the current section.  This
	// variable will hold that index.
	int currentSlotIndex = 0;

	// Stream events have a version number.  To stop us printing out the event ID for
	// every stream event we see (because they may be repeated), we will keep track
	// of the version numbers for each section containing a stream event, and only
	// print a message when the version number changes.

	// The key of each entry in the hash table is the event ID of the stream event,
	// with the value being the version number.  The hash table is the easiest data
	// structure for tracking this, since event IDs can be arbitrarily assigned.
	java.util.Hashtable eventVersions = new java.util.Hashtable();

	// These variables will hold the section filter and section filter group that
	// we will use.
	private RingSectionFilter filter = null;
	private SectionFilterGroup filterGroup = null;




	/**
	 * The main method for the worker thread.  This is where most of the work is done.
	 */
	public void run()
	{
		// Check if we've already been started.  If we have been started, we don't
		// need to set up the section filter and section filter group.
		if (!hasBeenStarted) {
			
			hasBeenStarted = true;

			// Set up the filters
			setupFilters();

			// 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 are ready to start filtering.

		// In order to use the section filter, we need to attach it to a transport
		// stream.  However, we don't know which transport stream we should attach
		// it to.  We will use the current transport stream but we need ot get a
		// reference to an object that represents it.  To do this, we have to use
		// the tuning API.

		// First, we get a reference to our network interface.  This assumes we
		// only have one network interface in our receiver.  Remember that a
		// 'network interface' is an interface to the broadcast network, not to
		// an IP network.

		// Get a reference to the NetworkInterfaceManager from the tuning API
		NetworkInterfaceManager manager = NetworkInterfaceManager.getInstance();

		// Now get a reference ot the first network interface
		NetworkInterface networkInterface = manager.getNetworkInterfaces()[0];

		// Once we have a reference to the network interface, we can find out what
		// transport stream it's currently receiving.
		TransportStream ts = networkInterface.getCurrentTransportStream();

		// Now we can attach the section filter group to the transport stream.  This
		// reserves the section filter resources that will be used for the
		// filtering.  This method also tells the section filter group what event
		// listener it should notify when its state changes.  This does NOT tell us
		// when we have successfully filtered a section.
		try {
			filterGroup.attach(ts, this, null);
		}
		catch (org.davic.mpeg.TuningException te) {
			// There's a problem tuning to the transport stream.  This shouldn't get
			// thrown, because we're already tuned to the transport stream in this
			// example.
			te.printStackTrace();
		}
		catch (InvalidSourceException ise) {
			// The section source is not valid.  The exact meaning of this is
			// unclear, and it's a fairly general 'something went wrong' error
			// that's not related to tuning or resource issues.
			ise.printStackTrace();
		}
		catch (FilterResourceException fre1) {
			// There aren't enough resources to fulfill the request.
			fre1.printStackTrace();
		}

		// Once our filter group has been attached to the transport stream, we
		// can add a listener for events from the section filter.  This will
		// tell us when we have successfully filtered a section.
        filter.addSectionFilterListener(this);

		// We're now ready to start filtering.  In this case, we're using a fairly
		// simple filter that just filters on the PID and the table ID (more
		// complex ones are available that filter on different parts of the
		// section). This can generate a number of exceptions, so we enclose it
		// in a 'try' block.
		try {
			filter.startFiltering(null, STREAM_PID, TABLE_ID);
		}
		catch (NotAuthorizedException nae) {
			nae.printStackTrace();
		}
		catch (FilterResourceException fre2) {
			fre2.printStackTrace();
		}
		catch (ConnectionLostException cle) {
			cle.printStackTrace();
		}

	}

	/**
	 * Set up the section filter and section filter group, ready for us to start
	 * filtering.
	 */
	private void setupFilters() {
		// Before we can use a section filter, we have to create a section filter group.
		// The argument specifies the number of filters we should use for this group
		// (more filters means better performance).  We only need one filter.
		filterGroup = new SectionFilterGroup(1);

		// Create the section filter.  We use a ring section filter, because we are
		// filtering many sections (which makes a simple section filter less useful)
		// where the sections are not parts of the same table (which rules out using
		// a table section filter).  The ring section filter is generally the more
		// flexible of the three types available to us.
		//
		//  We need to specify the number of buffer slots that our ring section
		// filter will have - we use the constant we defined earlier.
		filter = filterGroup.newRingSectionFilter(NUMBER_OF_SECTIONS);
	}


	/**
	 * 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() {
		filterGroup.detach();

		// 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() {
		// Start the thread.  The section filter group gets attached
		// to the transport stream when we do this
		myWorkerThread.start();
	}

	/**
     * Once we have a section, this method can be used to parse it and get the information
     * about the stream event that we need.
     */
    private void parseSection(Section section) {

		// Holds the section data
		byte[] sectionbytes;

		// The ID and version of the parsed stream event
		int eventId;
		int eventVersion;


		try {
			// First, we read the contents of the section into our buffer.  Once we've done
			// this, we can start manipulating it.
			sectionbytes = section.getData();

			// The filter is a little too broad, and will match some sections which
			// don't contain stream events.  This means we have to do some filtering
			// ourselves.  We can make our lives easier by filtering out those
			// sections that are too short to contain a stream event descriptor.
			if (section.section_length() < 12) return;

			// Skip the section header, because it's of no interest to us.
			int i = SECTION_HEADER_SIZE;

			// Do we have a stream event descriptor?
			if (sectionbytes[i] == STREAM_EVENT_DESCRIPTOR_TAG) {
				// Get the event ID from the descriptor
				eventId = ((0xFF & (int)sectionbytes[i+2]) << 8) +
						   (0xFF & (int)sectionbytes[i+3]);

				// Get the version number for the section.
				eventVersion = section.version_number();

				// Check if we have a record for this event ID in the hash table.
				// By only printing a message when we receive a new event ID or a new
				// version of an existing event ID, we reduce the output to a manageable
				// level.  This is also what we'd need to do if we were handling stream
				// events in a real application, since we only want to respond to an
				// event once .
				Integer previousVersion = (Integer) eventVersions.get(new Integer(eventId));
				// if we don't have an entry for this event ID in the hash table, or the
				// version is smaller, update the hash table and print the details of the
				// new event
				if ((previousVersion == null) || (previousVersion.intValue() < eventVersion)){
					eventVersions.put(new Integer(eventId), new Integer(eventVersion));
					System.out.println("event ID: " + eventId);
					System.out.println("version: " + section.version_number());
				}
			}
		}
		catch (Exception e) {
			e.printStackTrace();
		}
		return;
	}

	/**
	 * This method is inherited from the SectionFilterListener interface, and gets called
	 * any time a section is filtered successfully.  Since many sections could get
	 * filtered in close succession, this method is declared as synchronized to ensure
	 * thread safety.
	 */
	public synchronized void sectionFilterUpdate(SectionFilterEvent event){

		// Check the type of event to see whether it's indicating that a section
		// has been filtered.
		if(event instanceof SectionAvailableEvent)
		{
			// A new section is available, so we parse it.
		try {

			// Get the buffer that is used by the section filter
			Section[] sections = filter.getSections();

			// Increment the current slot index.  Since this is a ring section
			// filter, we do this modulo the size of the buffer
			currentSlotIndex++;
			currentSlotIndex = currentSlotIndex % NUMBER_OF_SECTIONS;

			// We've got a complete section, so we can now do what we like with it.
			// In this case, we parse the DSM-CC event ID from it
			parseSection(sections[currentSlotIndex]);

			// When we've finished with the section, we flag this buffer slot as
			// empty.  The section filter will stop when no more empty slots are
			// available, so it's important to do this as soon as we don't need
			// the slot any more.
			sections[currentSlotIndex].setEmpty();

		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}
}

	/**************************************************************************
	 *
	 * 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) {
		// We detach the section filter group from the transport stream.  Doing
		// this automatically stops the section filter(s) and releases the
		// resources that they are using.
		filterGroup.detach();
	}

	/**
	 * 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) {
		// In this case, we also detach the section filter group from
		// the transport stream to make sure everything is in a known state.
		filterGroup.detach();
	}

}


