/* ***************************************************************************+
 * 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 javax.swing.*;
import javax.swing.filechooser.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;

/**
 * The <code>ClientGUI</code> class implements the main Client graphical user
 * interface.  It "wraps" around the Client class and provides an interface for 
 * controlling a presentation, much like the Windows CD player application.
 * 
 * @version 1.0, 4/14/1999
 * @author Jason Howes
 */
public class ClientGUI extends JFrame implements WindowListener, ActionListener, ItemListener
{
	/**
	 *  'The' client
	 */
	private Client mClient;
	
	/**
	 * Presentation state variables
	 */
	private boolean mPresentationPaused;
	
	/**
	 * SPOT home directory
	 */
	private String mSPOTDirectory;

	/** 
	 * Application defaults
	 */
	private final static String APP_NAME			= new String("SPOT Client");

	/** 
	 * Application labels
	 */
	private final static JLabel TITLE_LABEL			= new JLabel("Title:");
	private final static JLabel SLIDE_LABEL			= new JLabel("Slide:");
	private final static JLabel TOPIC_LABEL			= new JLabel("Topic:");
	
	/** 
	 * Component commands
	 */
	private final static String OPEN_COMMAND		= "Open";
	private final static String CLOSE_COMMAND		= "Close";
	private final static String EXIT_COMMAND		= "Exit";
	private final static String PREFERENCES_COMMAND	= "Preferences";
	private final static String PLAY_COMMAND		= "Play";
	private final static String PAUSE_COMMAND		= "Pause";
	private final static String STOP_COMMAND		= "Stop";
	private final static String REWIND_COMMAND		= "Rewind";
	private final static String PREVIOUS_COMMAND	= "Previous";
	private final static String NEXT_COMMAND		= "Next";
	private final static String FF_COMMAND			= "FF";
	private final static String SLIDE_COMMAND		= "Slide";
	private final static String TOPIC_COMMAND		= "Topic";

	/**
	 * GUI controls and other components
	 */ 
	private JMenuBar menuBar;
	private JMenu menuFile;
	private JMenuItem menuFileOpen;
	private JMenuItem menuFileClose;
	private JMenuItem menuFileExit;
	
	private JPanel buttonPanel;
	private JPanel controlPanel;
	private JTextField textTitle;
	private JComboBox choiceSlide;
	private JComboBox choiceTopic;

	private JButton buttonPlay;
	private JButton buttonPause;
	private JButton buttonStop;
	private JButton buttonRewind;
	private JButton buttonPrevious;
	private JButton buttonNext;
	private JButton buttonFF;

	/**
	 * Class constructor.
	 * 
	 * @param newClient the client to be controlled
	 */
	ClientGUI(Client newClient)
	{
		super(APP_NAME);
		
		// Retrieve SPOT preferences
		mSPOTDirectory = (new SPOTPreferences()).getSPOTDirectory();
		
		// Initialize client related data
		mClient = newClient;
		mPresentationPaused = false;
	}

	/**
	 * Initializes all GUI components and controls, lays them out 
	 * in the main frame, and then shows the main frame.  Also
	 * starts the client.
	 */
	public void start()
	{	
		// Start a session with the SPOT server, if necessary...
		try
		{
			if (mClient.getClientMode() == ClientPresentationControl.SPOT_CONTROL)
			{
				mClient.startSession(0);
			}
		}
		catch (ClientSessionException e)
		{
			showErrorDialog("Error connecting to the Server: " + e.getMessage());
			try
			{
				mClient.reset();
			}
			catch (Exception exp)
			{
			}
		}
		catch (ClientException e)
		{
			showErrorDialog("Internal error: " + e.getMessage());
			try
			{
				mClient.reset();
			}
			catch (Exception exp)
			{
			}
		}
		
		// Create the menu
		menuBar = new JMenuBar();
		menuFile = new JMenu("File");
		menuFile.setMnemonic('F');
		menuBar.add(menuFile);
		setJMenuBar(menuBar);
		
		menuFileOpen = new JMenuItem("Open", KeyEvent.VK_O);
		menuFileOpen.setActionCommand(OPEN_COMMAND);
		menuFileOpen.addActionListener(this);
		menuFileOpen.setEnabled(!mClient.isInitialized());
		menuFile.add(menuFileOpen);

		menuFileClose = new JMenuItem("Close", KeyEvent.VK_C);
		menuFileClose.setActionCommand(CLOSE_COMMAND);
		menuFileClose.addActionListener(this);
		menuFileClose.setEnabled(mClient.isInitialized());
		menuFile.add(menuFileClose);
		
		menuFile.addSeparator();
		
		menuFileExit = new JMenuItem("Exit", KeyEvent.VK_X);
		menuFileExit.setActionCommand(EXIT_COMMAND);
		menuFileExit.addActionListener(this);
		menuFile.add(menuFileExit);
		
		// Create and layout the button panel
		buttonPanel = new JPanel();
		BoxLayout buttonLayout = new BoxLayout(buttonPanel, BoxLayout.X_AXIS);
		buttonPanel.setLayout(buttonLayout);
		buttonPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
		
		buttonPlay = new JButton(">");
		buttonPlay.setActionCommand(PLAY_COMMAND);
		buttonPlay.addActionListener(this);
		buttonPlay.setEnabled(mClient.isInitialized());
		buttonPause = new JButton("\"");
		buttonPause.setActionCommand(PAUSE_COMMAND);
		buttonPause.addActionListener(this);
		buttonPause.setEnabled(false);
		buttonStop= new JButton("[]");
		buttonStop.setActionCommand(STOP_COMMAND);
		buttonStop.addActionListener(this);
		buttonStop.setEnabled(false);
		buttonRewind = new JButton("|<<");
		buttonRewind.setActionCommand(REWIND_COMMAND);
		buttonRewind.addActionListener(this);
		buttonRewind.setEnabled(false);
		buttonPrevious = new JButton("<<");
		buttonPrevious.setActionCommand(PREVIOUS_COMMAND);
		buttonPrevious.addActionListener(this);
		buttonPrevious.setEnabled(false);
		buttonNext = new JButton(">>");
		buttonNext.setActionCommand(NEXT_COMMAND);
		buttonNext.addActionListener(this);
		buttonNext.setEnabled(false);
		buttonFF = new JButton(">>|");
		buttonFF.setActionCommand(FF_COMMAND);
		buttonFF.addActionListener(this);
		buttonFF.setEnabled(false);
		
		// Add all panel components
		buttonPanel.add(buttonPlay);
		buttonPanel.add(buttonPause);
		buttonPanel.add(buttonStop);
		buttonPanel.add(buttonRewind);
		buttonPanel.add(buttonPrevious);
		buttonPanel.add(buttonNext);
		buttonPanel.add(buttonFF);
		
		// Layout the control panel
		controlPanel = new JPanel();	
		GridBagLayout controlLayout = new GridBagLayout();
		GridBagConstraints controlConstraints = new GridBagConstraints();
		controlPanel.setLayout(controlLayout);
		controlPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
		
		textTitle = new JTextField(15);
		textTitle.setEditable(false);
		choiceSlide = new JComboBox();
		choiceSlide.setActionCommand(SLIDE_COMMAND);
		choiceSlide.addItemListener(this);
		choiceSlide.setLightWeightPopupEnabled(false);
		choiceSlide.setEnabled(false);
		choiceTopic = new JComboBox();
		choiceTopic.setActionCommand(TOPIC_COMMAND);
		choiceTopic.setLightWeightPopupEnabled(false);
		choiceTopic.setEnabled(false);
		
		// Set component values
		if (mClient.isInitialized())
		{
			textTitle.setText(mClient.getPresentationPath());
			getPresentationTopics();
		}
		
		// Set constraints for all the labels
		controlConstraints.gridwidth = GridBagConstraints.RELATIVE;
		controlConstraints.anchor = GridBagConstraints.WEST;
		controlConstraints.fill = GridBagConstraints.NONE;
		controlConstraints.insets = new Insets(5, 0, 0, 0);
		controlConstraints.weightx = 0.0;
		
		controlLayout.setConstraints(TITLE_LABEL, controlConstraints);
		controlLayout.setConstraints(SLIDE_LABEL, controlConstraints);
		controlLayout.setConstraints(TOPIC_LABEL, controlConstraints);
		
		// Set constraints for the labeled components
		controlConstraints.gridwidth = GridBagConstraints.REMAINDER;
		controlConstraints.anchor = GridBagConstraints.EAST;
		controlConstraints.fill = GridBagConstraints.HORIZONTAL;
		controlConstraints.weightx = 1.0;
		
		controlLayout.setConstraints(textTitle, controlConstraints);
		controlLayout.setConstraints(choiceSlide, controlConstraints);
		controlLayout.setConstraints(choiceTopic, controlConstraints);
		
		// Add all panel components
		controlPanel.add(TITLE_LABEL);
		controlPanel.add(textTitle);
		controlPanel.add(SLIDE_LABEL);
		controlPanel.add(choiceSlide);
		controlPanel.add(TOPIC_LABEL);
		controlPanel.add(choiceTopic);
		
		// Layout the main panel
		JPanel contentPane = (JPanel)getContentPane();
		BoxLayout contentLayout = new BoxLayout(contentPane, BoxLayout.Y_AXIS);
		contentPane.setLayout(contentLayout);
		contentPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
		
		// Add all panel components
		contentPane.add(buttonPanel);
		contentPane.add(controlPanel);
		
		// Add ourself as a window listener
		addWindowListener(this);
		
		// Set ourself as non-resizable, show the dialog, and center ourself
		setResizable(false);
		pack();
		Dimension paneSize = getSize();
        Dimension screenSize = getToolkit().getScreenSize();
        setLocation((screenSize.width - paneSize.width) / 2,
					(screenSize.height - paneSize.height) / 2);
		show();
	}
	
    public void windowClosing(WindowEvent e) 
	{
		exitApplication();
    }

    public void windowClosed(WindowEvent e) 
	{
    }

    public void windowOpened(WindowEvent e) 
	{
    }

    public void windowIconified(WindowEvent e) 
	{
    }

    public void windowDeiconified(WindowEvent e) 
	{
    }

    public void windowActivated(WindowEvent e) 
	{
    }

    public void windowDeactivated(WindowEvent e) 
	{
    }
	
	/**
	 * <code>ActionEvent</code> handler.
	 * 
	 * @param e event to handle
	 */
    public void actionPerformed(ActionEvent event) 
	{
		String actionCommand = event.getActionCommand();
		
        if (actionCommand.equals(OPEN_COMMAND)) 
		{
			// Allow the user to select a SPOT file
			JFileChooser fileChooser = new JFileChooser();
			fileChooser.setMultiSelectionEnabled(false);
			fileChooser.setCurrentDirectory(new File(mSPOTDirectory));
			fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
			fileChooser.setFileFilter(new SPOTFileFilter());
			fileChooser.showDialog(null, "Open");
			
			// Initialize the Client using the selected filename
			File file;
			if ((file = fileChooser.getSelectedFile()) != null)
			{
				// Initialize the client
				try
				{				
					// Reset the Client if it is initialized
					if (mClient.isInitialized())
					{
						mClient.reset();
					}
					mClient.initialize(file.getAbsolutePath());
				}
				catch (Exception e)
				{
					showErrorDialog("Error initializing the Client: " + e.getMessage());
					return;
				}
				
				try
				{
					// Connect to the server, if necessary
					if (mClient.getClientMode() == ClientPresentationControl.SPOT_CONTROL)
					{
						mClient.startSession(0);
					}
				}
				catch (Exception e)
				{
					showErrorDialog("Error connecting to the Server: " + e.getMessage());
					
					// Remeber to reset the client
					try
					{
						mClient.reset();
					}
					catch (Exception exp)
					{
					}
					return;
				}
				
				// Update GUI
				menuFileOpen.setEnabled(false);
				menuFileClose.setEnabled(true);
				buttonPlay.setEnabled(true);
				buttonPause.setEnabled(false);
				buttonStop.setEnabled(false);
				buttonRewind.setEnabled(false);
				buttonPrevious.setEnabled(false);
				buttonNext.setEnabled(false);
				buttonFF.setEnabled(false);
				choiceTopic.setEnabled(false);
				getPresentationTopics();
				choiceTopic.addItemListener(this);
				textTitle.setText(mClient.getPresentationPath());
			}
        }
		else if (actionCommand.equals(CLOSE_COMMAND))
		{
			// Close the client
			try
			{				
				// Reset the Client if it is initialized
				if (mClient.isInitialized())
				{
					// Update GUI
					menuFileOpen.setEnabled(true);
					menuFileClose.setEnabled(false);					
					buttonPlay.setEnabled(false);
					buttonPause.setEnabled(false);
					buttonStop.setEnabled(false);
					buttonRewind.setEnabled(false);
					buttonPrevious.setEnabled(false);
					buttonNext.setEnabled(false);
					buttonFF.setEnabled(false);
					choiceTopic.removeItemListener(this);
					choiceTopic.setEnabled(false);
					choiceTopic.removeAllItems();
					textTitle.setText("");
					
					// Reset client
					mClient.reset();
				}
			}
			catch (Exception e)
			{
				showErrorDialog("Error reseting the Client: " + e.getMessage());
			}			
		}
		else if (actionCommand.equals(EXIT_COMMAND)) 
		{
			// Cleanup...
			exitApplication();
        }
		else if (actionCommand.equals(PLAY_COMMAND))
		{	
			try
			{
				// If the presentation is paused, resume
				if (mPresentationPaused)
				{
					mClient.resumePresentation();
				
					mPresentationPaused = false;
					buttonPlay.setEnabled(false);
					
					return;
				}				
				
				// Download the presentation, if necessary
				if ((mClient.getClientMode() == ClientPresentationControl.SPOT_CONTROL) &&
					!mClient.searchLocalArchive(mSPOTDirectory))
				{
					mClient.getPresentation(mSPOTDirectory);
				}
			}
			catch (ClientSessionException e)
			{
				showErrorDialog("Error communicating with the Server: " + e.getMessage());
				return;
			}
			catch (ClientException e)
			{
				showErrorDialog("Client error: " + e.getMessage());
				return;
			}
			
			// Start the presentation
			try
			{
				mClient.startPresentation(mSPOTDirectory, 500, 667, 179, 0);
			}
			catch (Exception e)
			{
				showErrorDialog("Error starting presentation: " + e.getMessage());
				return;
			}
			
			// Update GUI
			buttonPlay.setEnabled(false);
			buttonPause.setEnabled(true);
			buttonStop.setEnabled(true);
			buttonRewind.setEnabled(true);
			buttonPrevious.setEnabled(true);
			buttonNext.setEnabled(true);
			buttonFF.setEnabled(true);
			choiceTopic.setEnabled(true);
		}
		else if (actionCommand.equals(PAUSE_COMMAND))
		{
			// Pause / resume presentation
			try
			{
				if (!mPresentationPaused)
				{
					mClient.pausePresentation();
				
					mPresentationPaused = true;
					buttonPlay.setEnabled(true);
				}
				else
				{
					mClient.resumePresentation();
				
					mPresentationPaused = false;
					buttonPlay.setEnabled(false);
				}
			}
			catch (ClientSessionException e)
			{
				showErrorDialog("Error communicating with the Server: " + e.getMessage());
				return;
			}
			catch (ClientException e)
			{
				showErrorDialog("Client error: " + e.getMessage());
				return;
			}
		}
		else if (actionCommand.equals(STOP_COMMAND))
		{
			// Stop the presentation
			try
			{
				mClient.stopPresentation();
			}
			catch (ClientSessionException e)
			{
				showErrorDialog("Error communicating with the Server: " + e.getMessage());
			}
			
			// Update GUI
			buttonPlay.setEnabled(true);
			buttonPause.setEnabled(false);
			buttonStop.setEnabled(false);
			buttonRewind.setEnabled(false);
			buttonPrevious.setEnabled(false);
			buttonNext.setEnabled(false);
			buttonFF.setEnabled(false);
			choiceTopic.setEnabled(false);
		}
		else if (actionCommand.equals(REWIND_COMMAND))
		{
			// Rewind presentation
			try
			{
				mClient.gotoFirstPresentationSlide();
			}
			catch (ClientSessionException e)
			{
				showErrorDialog("Error communicating with the Server: " + e.getMessage());
				return;
			}
			catch (ClientException e)
			{
				showErrorDialog("Client error: " + e.getMessage());
				return;
			}
		}
		else if (actionCommand.equals(PREVIOUS_COMMAND))
		{
			// Goto previous slide
			try
			{
				mClient.gotoPreviousPresentationSlide();
			}
			catch (ClientSessionException e)
			{
				showErrorDialog("Error communicating with the Server: " + e.getMessage());
				return;
			}
			catch (ClientException e)
			{
				showErrorDialog("Client error: " + e.getMessage());
				return;
			}		
		}
		else if (actionCommand.equals(NEXT_COMMAND))
		{
			// Goto next slide
			try
			{
				mClient.gotoNextPresentationSlide();
			}
			catch (ClientSessionException e)
			{
				showErrorDialog("Error communicating with the Server: " + e.getMessage());
				return;
			}
			catch (ClientException e)
			{
				showErrorDialog("Client error: " + e.getMessage());
				return;
			}		
		}
		else if (actionCommand.equals(FF_COMMAND))
		{
			// Fast forward
			try
			{
				mClient.gotoLastPresentationSlide();
			}
			catch (ClientSessionException e)
			{
				showErrorDialog("Error communicating with the Server: " + e.getMessage());
				return;
			}
			catch (ClientException e)
			{
				showErrorDialog("Client error: " + e.getMessage());
				return;
			}				
		}
    }
	
	/**
	 * Handles SLIDE_COMMAND or TOPIC_COMMANDS events (fired when
	 * the user selects a different topic or slide).
	 * 
	 * @param e event to handle
	 */
	public void itemStateChanged(ItemEvent event)
	{	
		// Only respond to item selected events
		if (event.getStateChange() == ItemEvent.SELECTED)
		{	
			// Get the action command associated with the source
			JComboBox comboBox = (JComboBox)event.getSource();
			String actionCommand = comboBox.getActionCommand();
			
			if (actionCommand.equals(TOPIC_COMMAND))
			{
				try
				{
					mClient.gotoPresentationTopic((String)comboBox.getSelectedItem());
				}
				catch (ClientSessionException e)
				{
					return;
				}
				catch (ClientException e)
				{
					return;
				}
			}
			else if (actionCommand.equals(SLIDE_COMMAND))
			{
			}
		}
	}
	
	/**
	 * Retrieves the presentation topics from the Client and adjusts GUI appropriately.
	 */
	private void getPresentationTopics()
	{		
		try
		{
			// Clear the topic combo box
			choiceTopic.removeAllItems();
			
			// Get the topics
			Vector topics = mClient.getPresentationTopics();
			
			for (int i = 0; i < topics.size(); i++)
			{
				choiceTopic.addItem(topics.elementAt(i));
			}
		}
		catch (ArrayIndexOutOfBoundsException e)
		{
		}
		catch (ClientSessionException e)
		{
		}
		catch (ClientException e)
		{
		}
	}
	
	/**
	 * Performs all application exit duties.
	 */
	private void exitApplication()
	{	
		// Remeber to reset the client...
		try
		{
			mClient.reset();
		}
		catch (Exception e)
		{
		}		
		
		dispose();
		System.exit(0);
	}
	
	/**
	 * Show an error dialog.
	 */
	public static void showErrorDialog(String error)
	{
		JOptionPane.showMessageDialog(null, error, "SPOT Error", JOptionPane.ERROR_MESSAGE); 
	}
	
	/**
	 * Show a message dialog
	 */
	public static void showMessageDialog(String message)
	{
		JOptionPane.showMessageDialog(null,message, "SPOT Message", JOptionPane.INFORMATION_MESSAGE);
	}
}

/**
 * SPOT file filter: filters out all files except those with suffix
 * SPOTDefinitions.SPT_FILE_SUFFIX or SPOTDefinitions.PAM_FILE_SUFFIX.
 */
class SPOTFileFilter extends FileFilter
{	
	/**
	 * Whether the given file is accepted by this filter.
	 */
	public boolean accept(File f)
	{	
		// Only test files
		if (f.isDirectory())
		{
			return true;
		}
		
		String filename = f.getName();		
		
		return (SPOTDefinitions.isPAMFile(filename) || 
				SPOTDefinitions.isSPTFile(filename));
	}
	
	/**
	 * The description of this filter.
	 */
	public String getDescription()
	{
		return "SPOT files";
	}
}