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);
	}
}
