package industries.doublehelix.furrynotemanager.gui;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.Timer;

/**
 * based on the source of ProgressMonitor
 * sadly, nearly all values are private, not protected, so we have to redo the whole thing
 * this is pretty much a copy paste of the whole thing, only changes are:
 * - indeterminate mode
 * - support for listeners
 * - it is modal
 * - it doesn't auto-close on 100%
 * - it starts up immediately without waiting
 * @author klaue
 *
 */
public class ImprovedProgressMonitor {
	/* original members from ProgressMonitor */
	/** parentComponent */
	Component component;
	/** note */
	String note;
	/** message */
	Object message;
	/** millisToDecideToPopup */
	int millisToDecideToPopup = 0;
	int min, max, progress;
	JProgressBar progressBar;
	JLabel noteLabel;
	JDialog progressDialog;
	Timer timer;
	boolean canceled;
	
	/** own members **/
	private ArrayList<ActionListener> listeners = new ArrayList<>();
	private boolean indeterminate = false;
	
	
	/**
	 * Creates a new <code>ImprovedProgressMonitor</code> instance.  This is used to 
	 * monitor a task and pops up a dialog if the task is taking a long time to 
	 * run.
	 * 
	 * @param component The parent component of the progress dialog or 
	 *                  <code>null</code>.
	 * @param message A constant message object which works in the way it does 
	 *                in {@link JOptionPane}.
	 * @param note A string message which can be changed while the operation goes
	 *             on.
	 * @param minimum The minimum value for the operation (start value).
	 * @param maximum The maximum value for the operation (end value).
	 */
	public ImprovedProgressMonitor(Component parentComponent, Object message, String note, int min, int max) {
		// Set data.
		this.component = parentComponent;
		this.message = message;
		this.note = note;
		this.min = min;
		this.max = max;
	}

	public void setIndeterminate(boolean on) {
		System.out.println("progress: indeterminate to " + on);
		if (progressBar != null && progressBar.isIndeterminate() == on) return;
		indeterminate = on;
		
		addTimer();
	}
	
	public boolean isIndeterminate() {
		return (progressBar == null) ? false : progressBar.isIndeterminate();
	}
	
	/**
	 * <p>Updates the progress value.</p>
	 *
	 * <p>When called for the first time this initializes a timer
	 * which decides after <code>millisToDecideToPopup</code> time
	 * whether to show a progress dialog or not.</p>
	 *
	 * <p>If the progress value equals or exceeds the maximum
	 * value the progress dialog is closed automatically.</p>
	 *
	 * @param progress New progress value.
	 */
	public void setProgress(int progress) {
		System.out.println("progress: value to " + progress);
		this.progress = progress;
		this.indeterminate = false;

		addTimer();
		
	}
	
	private void addTimer() {
		// Initializes and starts a timer with a task
		// which measures the duration and displays
		// a progress dialog if neccessary.
		if (timer == null && progressDialog == null) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					timer = new Timer(25, null);
					timer.addActionListener(new TimerListener());
					timer.start();
				}
			}).start();
		}
	}
	
	public void addCancelListener(ActionListener l) {
		this.listeners.add(l);
	}
	
	public void removeCancelListener(ActionListener l) {
		this.listeners.remove(l);
	}

	/**
	 * Internal method that creates the progress dialog.
	 */
	void createDialog() {
		// If there is no note we suppress the generation of the
		// label.
		Object[] tmp = (note == null) ? new Object[] { message, progressBar = new JProgressBar(min, max) }
				: new Object[] { message, noteLabel = new JLabel(note), progressBar = new JProgressBar(min, max) };

		progressBar.setIndeterminate(indeterminate);
		
		JOptionPane pane = new JOptionPane(tmp, JOptionPane.INFORMATION_MESSAGE);

		// FIXME: Internationalize the button
		JButton cancelButton = new JButton("Cancel");
		cancelButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				canceled = true;
				ActionEvent evt = new ActionEvent(ImprovedProgressMonitor.this, ActionEvent.ACTION_PERFORMED, "CANCEL");
				for (ActionListener l : listeners) {
					l.actionPerformed(evt);
				}
			}
		});

		pane.setOptions(new Object[] { cancelButton });

		// FIXME: Internationalize the title
		progressDialog = pane.createDialog(component, "Progress ...");
		progressDialog.setModal(true);
		progressDialog.setResizable(true);

		progressDialog.setSize(450, 150);
		//progressDialog.pack();
		
		// TODO: this is kinda ugly, GUI should not be accessed outside the EVT - but there seems to be no way to make a
		// modal progress window without it, as setVisible() blocks on modal dialogs
		// maybe make a classpane over every window of the app that swallows all input?
		Thread t = new Thread(new Runnable() {
			public void run() {
				progressDialog.setVisible(true);
			}
		});
		t.start();

	}
	
	


	/**
	 * An ActionListener implementation which does the measurements and
	 * estimations of the ProgressMonitor.
	 */
	class TimerListener implements ActionListener {
		long timestamp;

		int lastProgress;

		boolean first = true;

		TimerListener() {
			timestamp = System.currentTimeMillis();
		}

		public void actionPerformed(ActionEvent ae) {
			long now = System.currentTimeMillis();

			if (first) {
				if ((now - timestamp) > millisToDecideToPopup) {
					first = false;

					createDialog(); // if intermediate is set first, progress is always 0
//					long expected = (progress - min == 0) ? (now - timestamp) * (max - min)
//							: (now - timestamp) * (max - min) / (progress - min);
//
//					if (expected > millisToPopup) {
//						createDialog();
//					}
				} else {
					// We have not waited long enough to make a decision,
					// so return and try again when the timer is invoked.
					return;
				}
			} else if (progressDialog != null) {
				if (indeterminate ) {
					progressBar.setIndeterminate(true);
				} else {
					progressBar.setIndeterminate(false);
					// The progress dialog is being displayed. We now calculate
					// whether setting the progress bar to the current progress
					// value would result in a visual difference.
					int delta = progress - progressBar.getValue();
	
					if ((delta * progressBar.getWidth() / (max - min)) > 0) {
						// At least one pixel would change.
						progressBar.setValue(progress);
					}
				}
			} else {
				// No dialog necessary
				timer.stop();
				timer = null;
			}

			timestamp = now;
		}
	}
	
	/*
	 * 
	 * 
	 *      ALL CODE BELOW THIS POINT IS UNCHANGED FROM PROGRESSMONITOR
	 * 
	 * 
	 */
	
	
	

	/**
	 * <p>
	 * Hides the dialog and stops any measurements.
	 * </p>
	 *
	 * <p>
	 * Has no effect when <code>setProgress</code> is not at least called once.
	 * </p>
	 */
	public void close() {
		if (progressDialog != null) {
			progressDialog.setVisible(false);
		}

		if (timer != null) {
			timer.stop();
			timer = null;
		}
	}

	/**
	 * Returns the minimum or start value of the operation.
	 *
	 * @return Minimum or start value of the operation.
	 */
	public int getMinimum() {
		return min;
	}

	/**
	 * <p>
	 * Use this method to set the minimum or start value of your operation.
	 * </p>
	 *
	 * <p>
	 * For typical application like copy operation this will be zero.
	 * </p>
	 *
	 * <p>
	 * Keep in mind that changing this value after the progress dialog is made
	 * visible has no effect upon the progress bar.
	 * </p>
	 *
	 * @param minimum
	 *            The new minimum value.
	 */
	public void setMinimum(int minimum) {
		min = minimum;
	}

	/**
	 * Return the maximum or end value of your operation.
	 *
	 * @return Maximum or end value.
	 */
	public int getMaximum() {
		return max;
	}

	/**
	 * <p>
	 * Sets the maximum or end value of the operation to the given integer.
	 * </p>
	 *
	 * @param maximum
	 */
	public void setMaximum(int maximum) {
		max = maximum;
	}

	/**
	 * Returns whether the user canceled the operation.
	 *
	 * @return Whether the operation was canceled.
	 */
	public boolean isCanceled() {
		// The value is predefined to false
		// and changes only when the user clicks
		// the cancel button in the progress dialog.
		return canceled;
	}

	/**
	 * Returns the amount of milliseconds to wait until the ProgressMonitor
	 * should decide whether a progress dialog is to be shown or not.
	 *
	 * @return The duration in milliseconds.
	 */
	public int getMillisToDecideToPopup() {
		return millisToDecideToPopup;
	}

	/**
	 * Sets the amount of milliseconds to wait until the ProgressMonitor should
	 * decide whether a progress dialog is to be shown or not.
	 *
	 * <p>
	 * This method has no effect when the progress dialog is already visible.
	 * </p>
	 *
	 * @param time
	 *            The duration in milliseconds.
	 */
	public void setMillisToDecideToPopup(int time) {
		millisToDecideToPopup = time;
	}
	/**
	 * Returns a message which is shown in the progress dialog.
	 *
	 * @return The changeable message visible in the progress dialog.
	 */
	public String getNote() {
		return note;
	}

	/**
	 * <p>
	 * Set the message shown in the progess dialog.
	 * </p>
	 *
	 * <p>
	 * Changing the note while the progress dialog is visible is possible.
	 * </p>
	 *
	 * @param note
	 *            A message shown in the progress dialog.
	 */
	public void setNote(String note) {
		if (noteLabel != null) {
			noteLabel.setText(note);
		} else {
			this.note = note;
		}
	}
}
