content: package tim.prune.function.autoplay;
  
  import java.awt.Component;
  import java.awt.FlowLayout;
  import java.awt.event.WindowAdapter;
  import java.awt.event.WindowEvent;
  import java.util.TreeSet;
  
  import javax.swing.BoxLayout;
  import javax.swing.JButton;
  import javax.swing.JCheckBox;
  import javax.swing.JDialog;
  import javax.swing.JLabel;
  import javax.swing.JPanel;
  
  import tim.prune.App;
  import tim.prune.GenericFunction;
  import tim.prune.I18nManager;
  import tim.prune.data.Field;
  import tim.prune.data.Timestamp;
  import tim.prune.data.Track;
  import tim.prune.gui.GuiGridLayout;
  import tim.prune.gui.IconManager;
  import tim.prune.gui.WholeNumberField;
  
  /**
   * Function to handle the autoplay of a track
   */
  public class AutoplayFunction extends GenericFunction
  {
  	/** Dialog */
  	private JDialog _dialog = null;
  	/** Entry field for number of seconds to autoplay for */
  	private WholeNumberField _durationField = null;
  	/** Checkbox for using point timestamps */
  	private JCheckBox _useTimestampsCheckbox = null;
  	/** Buttons for controlling autoplay */
  	private JButton _rewindButton = null, _pauseButton = null, _playButton = null;
  	/** Flag for recalculating all the times */
  	private boolean _needToRecalculate = true;
  	/** Point list */
  	private PointList _pointList = null;
  	/** Flag to see if we're still running or not */
  	private boolean _running = false;
  	/** Remember the time we started playing */
  	private long _startTime = 0L;
  
  
  	/**
  	 * Constructor
  	 * @param inApp App object
  	 */
  	public AutoplayFunction(App inApp) {
  		super(inApp);
  	}
  
  	@Override
  	public String getNameKey() {
  		return "function.autoplay";
  	}
  
  	/**
  	 * Begin the function
  	 */
  	public void begin()
  	{
  		if (_dialog == null)
  		{
  			_dialog = new JDialog(_parentFrame, getName(), true);
  			_dialog.setLocationRelativeTo(_parentFrame);
  			_dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
  			_dialog.getContentPane().add(makeDialogComponents());
  			_dialog.pack();
  			_dialog.setResizable(false);
  			_dialog.addWindowListener(new WindowAdapter() {
  				public void windowClosing(WindowEvent e) {
  					_running = false;
  					super.windowClosing(e);
  				}
  			});
  		}
  		// Don't select any point
  		_app.getTrackInfo().selectPoint(-1);
  		// enable buttons
  		enableButtons(false, true); // can't pause, can play
  		// MAYBE: reset duration if it's too long
  		// Disable point checkbox if there aren't any times
  		final boolean hasTimes = _app.getTrackInfo().getTrack().hasData(Field.TIMESTAMP);
  		_useTimestampsCheckbox.setEnabled(hasTimes);
  		if (!hasTimes) {
  			_useTimestampsCheckbox.setSelected(false);
  		}
  
  		_needToRecalculate = true;
  		_dialog.setVisible(true);
  	}
  
  
  	/**
  	 * Create dialog components
  	 * @return Panel containing all gui elements in dialog
  	 */
  	private Component makeDialogComponents()
  	{
  		JPanel dialogPanel = new JPanel();
  		dialogPanel.setLayout(new BoxLayout(dialogPanel, BoxLayout.Y_AXIS));
  		// Duration panel
  		JPanel durationPanel = new JPanel();
  		GuiGridLayout grid = new GuiGridLayout(durationPanel);
  		grid.add(new JLabel(I18nManager.getText("dialog.autoplay.duration") + " :"));
  		_durationField = new WholeNumberField(3);
  		_durationField.setValue(60); // default is one minute
  		_durationField.addActionListener(e -> onParamsChanged());
  		grid.add(_durationField);
  		durationPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
  		dialogPanel.add(durationPanel);
  		// Checkbox
  		_useTimestampsCheckbox = new JCheckBox(I18nManager.getText("dialog.autoplay.usetimestamps"));
  		_useTimestampsCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
  		dialogPanel.add(_useTimestampsCheckbox);
  		_useTimestampsCheckbox.addActionListener(e -> onParamsChanged());
  		// Button panel
  		JPanel buttonPanel = new JPanel();
  		buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
  		buttonPanel.add(_rewindButton = new JButton(getIconManager().getImageIcon(IconManager.CONTROL_REWIND)));
  		_rewindButton.setToolTipText(I18nManager.getText("dialog.autoplay.rewind"));
  		_rewindButton.addActionListener(e -> onRewindPressed());
  		buttonPanel.add(_pauseButton = new JButton(getIconManager().getImageIcon(IconManager.CONTROL_PAUSE)));
  		_pauseButton.setToolTipText(I18nManager.getText("dialog.autoplay.pause"));
  		_pauseButton.addActionListener(e -> onPausePressed());
  		buttonPanel.add(_playButton = new JButton(getIconManager().getImageIcon(IconManager.CONTROL_PLAY)));
  		_playButton.setToolTipText(I18nManager.getText("dialog.autoplay.play"));
  		_playButton.addActionListener(e -> onPlayPressed());
  		buttonPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
  		dialogPanel.add(buttonPanel);
  		return dialogPanel;
  	}
  
  	/**
  	 * React to a change to either the duration or the checkbox
  	 */
  	private void onParamsChanged()
  	{
  		onRewindPressed();
  		enableButtons(false, _durationField.getValue() > 0);
  		_needToRecalculate = true;
  	}
  
  	/**
  	 * React to rewind button pressed - stop and go back to the first point
  	 */
  	private void onRewindPressed()
  	{
  		_running = false;
  		if (_pointList != null)
  		{
  			_pointList.set(0);
  			_app.getTrackInfo().selectPoint(_pointList.getCurrentPointIndex());
  		}
  	}
  
  	/**
  	 * React to pause button pressed - stop scrolling but maintain position
  	 */
  	private void onPausePressed()
  	{
  		_running = false;
  		enableButtons(false, true);
  	}
  
  	/**
  	 * React to play button being pressed - either start or resume
  	 */
  	private void onPlayPressed()
  	{
  		if (_needToRecalculate) {
  			recalculateTimes();
  		}
  		enableButtons(true, false);
  		if (_pointList.isAtStart() || _pointList.isFinished())
  		{
  			_pointList.set(0);
  			_startTime = System.currentTimeMillis();
  		}
  		else
  		{
  			// Get current millis from pointList, reset _startTime
  			_startTime = System.currentTimeMillis() - _pointList.getCurrentMilliseconds();
  		}
  		new Thread(this::run).start();
  	}
  
  	/**
  	 * Recalculate the times using the dialog settings
  	 */
  	private void recalculateTimes()
  	{
  		if (_useTimestampsCheckbox.isSelected()) {
  			_pointList = generatePointListUsingTimes(_app.getTrackInfo().getTrack(), _durationField.getValue());
  		}
  		else {
  			_pointList = generatePointListUsingIndexes(_app.getTrackInfo().getTrack().getNumPoints(), _durationField.getValue());
  		}
  		_needToRecalculate = false;
  	}
  
  	/**
  	 * Enable and disable the pause and play buttons
  	 * @param inCanPause true to enable pause button
  	 * @param inCanPlay  true to enable play button
  	 */
  	private void enableButtons(boolean inCanPause, boolean inCanPlay)
  	{
  		_pauseButton.setEnabled(inCanPause);
  		_playButton.setEnabled(inCanPlay);
  	}
  
  	/**
  	 * Generate a points list based just on the point timestamps
  	 * (points without timestamps will be ignored)
  	 * @param inTrack track object
  	 * @param inDuration number of seconds to play
  	 * @return PointList object
  	 */
  	private static PointList generatePointListUsingTimes(Track inTrack, int inDuration)
  	{
  		// Make a Set of all the points with timestamps and sort them
  		TreeSet<PointInfo> set = new TreeSet<PointInfo>();
  		int numPoints = inTrack.getNumPoints();
  		for (int i=0; i<numPoints; i++)
  		{
  			PointInfo info = new PointInfo(inTrack.getPoint(i), i);
  			if (info.getTimestamp() != null) {
  				set.add(info);
  			}
  		}
  		// For each point, keep track of the time since the previous time
  		Timestamp previousTime = null;
  		long trackMillis = 0L;
  		// Copy info to point list
  		numPoints = set.size();
  		PointList list = new PointList(numPoints);
  		for (PointInfo info : set)
  		{
  			if (previousTime != null)
  			{
  				if (info.getSegmentFlag()) {
  					trackMillis += 1000; // just add a second if it's a new segment
  				}
  				else {
  					trackMillis += (info.getTimestamp().getMillisecondsSince(previousTime));
  				}
  			}
  			previousTime = info.getTimestamp();
  			list.setPoint(trackMillis, info.getIndex());
  		}
  		// Now normalize the list to the requested length
  		list.normalize(inDuration);
  		return list;
  	}
  
  
  	/**
  	 * Generate a points list based just on indexes, ignoring timestamps
  	 * @param inNumPoints number of points in track
  	 * @param inDuration number of seconds to play
  	 * @return PointList object
  	 */
  	private static PointList generatePointListUsingIndexes(int inNumPoints, int inDuration)
  	{
  		// simple case, just take all the points in the track
  		PointList list = new PointList(inNumPoints);
  		// Add each of the points in turn
  		for (int i=0; i<inNumPoints; i++) {
  			list.setPoint(i, i);
  		}
  		list.normalize(inDuration);
  		return list;
  	}
  
  	/**
  	 * Run method, for scrolling in separate thread
  	 */
  	public void run()
  	{
  		_running = true;
  		_app.getTrackInfo().selectPoint(_pointList.getCurrentPointIndex());
  		while (_running && !_pointList.isFinished())
  		{
  			_pointList.set(System.currentTimeMillis() - _startTime);
  			final int pointIndex = _pointList.getCurrentPointIndex();
  			//System.out.println("Set point index to " + pointIndex);
  			_app.getTrackInfo().selectPoint(pointIndex);
  			long waitInterval = _pointList.getMillisUntilNextPoint(System.currentTimeMillis() - _startTime);
  			if (waitInterval < 20) {waitInterval = 20;}
  			try {Thread.sleep(waitInterval);}
  			catch (InterruptedException ie) {}
  		}
  		_running = false;
  		enableButtons(false, true);
  	}
  }
  