/* ***************************************************************************+
 * ITX package (cnrg.itx) for telephony application programming.              *
 * Copyright (c) 1999  Cornell University, Ithaca NY                          *
 *                                                                            *
 * This program is free software; you can redistribute it and/or modify       *
 * it under the terms of the GNU General Public Liense as published by        *
 * the Free Software Foundation, either version 2 of the License, or (at      * 
 * your option) any later version.                                            *
 *                                                                            *
 * The ITX package 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.                                                          * 
 *                                                                            *
 * A copy of the license is distributed with this package.  Look in the docs  *
 * directory, filename GPL.                                                   *
 *                                                                            * 
 * Contact information:                                                       *
 * Donna Bergmark                                                             *
 * 484 Rhodes Hall                                                            *
 * Cornell University                                                         *
 * Ithaca, NY 14853-3801                                                      *
 * bergmark@cs.cornell.edu                                                    *
 ******************************************************************************/
package client;

import shared.*;
import cnrg.itx.datax.*;
import cnrg.itx.datax.devices.*;
import java.io.*;

/**
 * A <code>ClientSPOTControl</code> is a server-based SPOT client presentation control.
 * A SPOT server is used to control the presentation, thus allowing remote presentation
 * playback.  It implements the <code>ClientPresentationControl</code> interface to 
 * synchronize PPT slide transitions and the presentation audio stream with presentation
 * slide changes (as specified by the SPOT server).
 * 
 * @author Jason Howes
 * @version 1.0, 2/24/1999
 * @see cnrg.apps.spot.shared.RADInputStream
 * @see cnrg.apps.spot.client.ClientPresentationControl
 * @see cnrg.apps.spot.client.ClientException
 * @see cnrg.apps.spot.client.PowerPointControl
 * @see cnrg.apps.spot.shared.PowerPointException
 */
public class ClientSPOTControl extends Thread implements ClientPresentationControl
{
	/**
	 * Debug flag (if true, the control thread outputs control info).
	 */
	private static final boolean DEBUG = false;
	
	/**
	 * Client control session and client
	 */
	private ClientSession mClientSession;
	private Client mClient;

	/**
	 * PowerPoint control.
	 */
	private PowerPointControl mPPTControl;

	/**
	 * Presentation slide numbers.
	 */
	private int mCurrentPresentationSlideNum;
	private int mNumPresentationSlides;
	private PresentationInfo mPresentationInfo;

	/**
	 * ClientPAMControl thread flags and semaphore.
	 */
	private boolean mStopRequest;
	private boolean mAlive;
	private boolean mStart;
	private boolean mStop;
	private Object mSema;

	/**
	 * Class constructor.
	 * 
	 * @parm client the calling Client
	 * @param clientSession the controlling client session
	 * @param numPresentationSlides the number of presentation slides
	 * @throws <code>ClientException</code> on error.
	 */
	public ClientSPOTControl(Client client, 
							 ClientSession clientSession, 
							 PresentationInfo presentationInfo, 
							 int numPresentationSlides) throws ClientException
	{
		// Initialize object
		mPPTControl = new PowerPointControl();
		mSema = new Object();
		mStopRequest = false;
		mAlive = false;
		mStart = false;
		mStop = false;
		mPresentationInfo = presentationInfo;
		mNumPresentationSlides = numPresentationSlides;
		mCurrentPresentationSlideNum = 0;
		mClient = client;
		mClientSession = clientSession;

		// Start the thread
		startup();
	}

	/**
	 * Starts the presentation.
	 * 
	 * @param PPTfilename name of the PPT presentation file.
	 * @param w the horizontal size of the PPT presentation window (in pixels).
	 * @param h the vertical size of the PPT presentation window (in pixels).
	 * @param x the horizontal screen position of the PPT presentation window.
	 * @param y the vertical screen position of the PPT presentation window.
	 * @throws <code>ClientException</code, <code>ClientSessionException</code>, 
	 * <code>PowerPointControlException</code> on error.
	 */
	public void startPresentation(String filename,
		int w, 
		int h, 
		float x, 
		float y) throws ClientException, ClientSessionException, PowerPointControlException
	{
		SPOTMessage message;
		
		// Are we already started?
		if (mStart)
		{
			throw new ClientException(Client.PRESENTATION_IN_PROGRESS);
		}

		// Open the presentation
		mPPTControl.startPresentation(filename, w, h, x, y);
		
		// Notify the server that we are ready to start the presentation
		mClientSession.sendMessage(SPOTMessage.START_PRESENTATION, mPresentationInfo);
				
		// Read the server's response
		message = mClientSession.receiveMessage();
		if (message.mType != SPOTMessage.ACK)
		{
			throw new ClientException(Client.SERVER_ERROR);
		}		
		
		// Start the control thread
		try
		{
			synchronized(mSema)
			{
				mAlive = true;
				mStart = true;
				mStop = false;
				mSema.notify();
			}
		}
		catch (Exception e)
		{
			stopPresentation();
			throw new ClientException(e.getMessage());
		}
	}
	
	/**
	 * Stops the presentation.
	 * 
	 * @throws <code>ClientSessionException</code> on error
	 */
	public void stopPresentation() throws ClientSessionException
	{
		SPOTMessage message;
		
		// Have we started?
		if (!mStart)
		{
			return;
		}

		// Have we already stopped?
		if (mStop)
		{
			return;
		}
		
		// Let the thread know that it is SUPPOSED to shut down
		mStopRequest = true;
		
		
		// Notify the server that we want to stop the presentation
		try
		{
			mClientSession.sendMessage(SPOTMessage.STOP_PRESENTATION, null);
		}
		catch (ClientSessionException e)
		{
			this.stop();	
				
			// Stop the prentation
			mPPTControl.stopPresentation();
			
			// Reset flags
			mStopRequest = false;
			mStart = false;				
			
			throw e;
		}
		
		// Tell the control thread to stop
		try
		{
			synchronized(mSema)
			{
				mAlive = false;
				mStop = true;
				mSema.notify();
			}
		}
		catch (Exception e)
		{
		}
		
		// Wait for the thread to join
		try
		{
			this.join();
		}
		catch (InterruptedException e)
		{
		}
		
		// Reset flags
		mStopRequest = false;
		mStart = false;		
		
		// Stop the prentation
		mPPTControl.stopPresentation();
	}	

	/**
	 * Synchronizes the PPT presentation and audio with the first 
	 * presentation slide.
	 * 
	 * @throws <code>ClientSessionException</code> on error.
	 */
	public void gotoFirstPresentationSlide() throws ClientSessionException
	{		
		// Have we started yet?
		if (!mStart)
		{
			return;
		}

		// Send a slide change request
		mClientSession.sendMessage(SPOTMessage.REQUEST_PRESENTATION_SLIDE_CHANGE, new Integer(0));

		if (DEBUG)
		{
			System.out.println("<ClientSPOTControl> :: asked for the first slide");
		}
	}
	
	/**
	 * Synchronizes the PPT presentation and audio with the last 
	 * presentation slide.
	 * 
	 * @throws <code>ClientSessionException</code> on error.
	 */
	public void gotoLastPresentationSlide() throws ClientSessionException
	{
		// Have we started yet?
		if (!mStart)
		{
			return;
		}

		// Send a slide change request
		mClientSession.sendMessage(SPOTMessage.REQUEST_PRESENTATION_SLIDE_CHANGE, new Integer(mNumPresentationSlides - 1));

		if (DEBUG)
		{
			System.out.println("<ClientSPOTControl> :: asked for the last slide");
		}
	}	

	/**
	 * Synchronizes the PPT presentation and audio with the next 
	 * presentation slide.
	 * 
	 * @throws <code>ClientSessionException</code> on error.
	 */
	public void gotoNextPresentationSlide() throws ClientSessionException
	{
		Integer newPresentationSlideNum = new Integer(getNextSlideNum());

		// Have we started yet?
		if (!mStart)
		{
			return;
		}

		// Send a slide change request
		mClientSession.sendMessage(SPOTMessage.REQUEST_PRESENTATION_SLIDE_CHANGE, newPresentationSlideNum);

		if (DEBUG)
		{
			System.out.println("<ClientSPOTControl> :: asked for slide " + newPresentationSlideNum);
		}
	}	
	
	/**
	 * Synchronizes the PPT presentation and audio with the previous 
	 * presentation slide.
	 * 
	 * @throws <code>ClientSessionException</code> on error.
	 */
	public void gotoPreviousPresentationSlide() throws ClientSessionException
	{
		Integer newPresentationSlideNum = new Integer(getPreviousSlideNum());

		// Have we started yet?
		if (!mStart)
		{
			return;
		}

		// Send a slide change request
		mClientSession.sendMessage(SPOTMessage.REQUEST_PRESENTATION_SLIDE_CHANGE, newPresentationSlideNum);

		if (DEBUG)
		{
			System.out.println("<ClientSPOTControl> :: asked for slide " + newPresentationSlideNum);
		}
	}
	
	/**
	 * Synchronizes the PPT presentation and audio with a specified
	 * presentation topic.
	 * 
	 * @param topic the desired topic with which to synchronize.
	 * @throws <code>ClientSessionException</code> on error.
	 */	
	public void gotoPresentationTopic(String topic) throws ClientSessionException
	{
		// Have we started yet?
		if (!mStart)
		{
			return;
		}

		// Send a slide change request
		mClientSession.sendMessage(SPOTMessage.REQUEST_PRESENTATION_TOPIC_SLIDE, topic);

		if (DEBUG)
		{
			System.out.println("<ClientSPOTControl> :: asked for slide with topic " + topic);
		}	
	}	

	/**
	 * Pauses the slide presentation.
	 * 
	 * @throws <code>ClientSessionException</code> on error.
	 */
	public void pausePresentation() throws ClientSessionException
	{
		// Have we started yet?
		if (!mStart)
		{
			return;
		}

		// Send a slide change request
		mClientSession.sendMessage(SPOTMessage.PAUSE_PRESENTATION, null);

		if (DEBUG)
		{
			System.out.println("<ClientSPOTControl> :: requested pause");
		}	
	}

	/**
	 * Resumes a paused slide presentation.
	 * 
	 * @throws <code>ClientSessionException</code> on error.
	 */
	public void resumePresentation() throws ClientSessionException
	{
		// Have we started yet?
		if (!mStart)
		{
			return;
		}

		// Send a slide change request
		mClientSession.sendMessage(SPOTMessage.RESUME_PRESENTATION, null);

		if (DEBUG)
		{
			System.out.println("<ClientSPOTControl> :: requested resume");
		}	
	}

	/**
	 * Thread function.
	 */
	public void run()
	{
		SPOTMessage message;
		int nextPPTSlideNum;
		int nextPresentationSideNum;
		PAMDescriptorEntry nextSlide;

		// Wait until we are either started or stopped
		try
		{
			synchronized(mSema)
			{
				if (!mStart)
				{
					mSema.wait();
					if (mStop)
					{
						shutdown();
						return;
					}
				}
			}
		}
		catch (Exception e)
		{
			// Simply shutdown on an exception
			shutdown();
			return;
		}

		// Main thread loop
		while (mAlive)
		{
			// Get the next message
			try
			{
				message = mClientSession.receiveMessage();
			}
			catch (ClientSessionException e)
			{
				mStart = false;
				mStop = true;
		
				// Stop the prentation
				mPPTControl.stopPresentation();
				
				// Unexpected server disconnection
				mClient.onServerDisconnect();
				
				break;
			}
			
			// Are we done?
			if (mStop)
			{
				break;
			}			
			
			// Has the session been ended?
			if (message.mType == SPOTMessage.END_SESSION)
			{
				// Was this message unexpected?
				if (!mStopRequest)
				{	
					mStart = false;
					mStop = false;
		
					// Stop the prentation
					mPPTControl.stopPresentation();
					
					// Alert the client of the disconnection
					mClient.onServerDisconnect();
				}
				
				break;
			}

			// Check the message type
			if (message.mType != SPOTMessage.PRESENTATION_SLIDE_CHANGE)
			{
				mStart = false;
				mStop = false;
		
				// Stop the prentation
				mPPTControl.stopPresentation();
					
				// Alert the client of the disconnection
				mClient.onServerHangup();
				
				break;
			}

			// Get the next PPT and Presentation slide numbers
			nextSlide = (PAMDescriptorEntry)message.mObject;
			nextPPTSlideNum = nextSlide.mPPTSlideNum;
			nextPresentationSideNum = nextSlide.mPresentationSlideNum;

			// Goto the specified PPT slide
			try
			{
				mPPTControl.gotoSlide(nextPPTSlideNum);
			}
			catch (PowerPointControlException e)
			{
				break;
			}

			if (DEBUG)
			{
				System.out.println("<ClientSPOTControl> :: changed PPT slide to " + nextPPTSlideNum);
			}

			// Set the new "current" slide number
			setSlideNum(nextPresentationSideNum);
		}

		// Shutdown the thread
		shutdown();
	}
	
	/**
	 * ClientPAMControl thread startup tasks.
	 */
	private void startup()
	{
		this.start();
		
		if (DEBUG)
		{
			System.out.println("<ClientSPOTControl> :: control thread started");
		}
	}

	/**
	 * ClientPAMControl thread shutdown tasks.
	 */
	private void shutdown()
	{
		if (DEBUG)
		{
			System.out.println("<ClientSPOTControl> :: control thread shutdown");
		}
	}
	
	/**
	 * Gets the current value of  mCurrentPresentationSlideNum (thread safe).
	 * 
	 * @return the current value of mCurrentPresentationSlideNum.
	 */		
	private synchronized int getCurrentSlideNum()
	{
		return mCurrentPresentationSlideNum;
	}
	
	/**
	 * Gets the "next" value of  mCurrentPresentationSlideNum (thread safe).
	 * 
	 * @return the "next" value of mCurrentPresentationSlideNum.
	 */		
	private synchronized int getNextSlideNum()
	{
		return mCurrentPresentationSlideNum + 1;
	}
	
	/**
	 * Gets the "previous" value of  mCurrentPresentationSlideNum (thread safe).
	 * 
	 * @return the "previous" value of mCurrentPresentationSlideNum.
	 */		
	private synchronized int getPreviousSlideNum()
	{
		if (mCurrentPresentationSlideNum == 0)
		{
			return 0;
		}
		return mCurrentPresentationSlideNum - 1;
	}	

	/**
	 * Sets mCurrentPresentationSlideNum (thread safe).
	 * 
	 * @param newSlideNum new value of mCurrentPresentationSlideNum
	 * @return true if mCurrentPresentationSlideNum changes, false otherwise.
	 */	
	private synchronized boolean setSlideNum(int newSlideNum)
	{
		if (mCurrentPresentationSlideNum != newSlideNum)
		{
			mCurrentPresentationSlideNum = newSlideNum;
			return true;
		}

		return false;
	}
}