package gui;

import java.awt.*;
import java.beans.*;
import java.util.*;
import javax.swing.*;

/**
 * A modal JDialog that can be used to cancel and show the status
 * of a background task (i.e., a task being executed using a
 * SwingWorker).
 *
 * @param <V> The type returned by the SwingWorker
 * @param <T> The type of intermediate values generated by the SwingWorker
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class BackgroundTaskDialog<T, V> extends AbstractModalDialog
    implements PropertyChangeListener
{
  private static final long serialVersionUID = 1L;
  private long timeout;
  private SwingWorker<T, V> task;
  private java.util.Timer countdown;


  /**
   * Explicit Value Constructor.
   *
   * @param owner   The parent Dialog
   * @param title   The title of this JDialog
   * @param task    The background task
   */
  public BackgroundTaskDialog(final Dialog owner, final String title, final SwingWorker<T, V> task)
  {
    super(owner, title);
    performCommonConstructionTasks(task);
  }

  /**
   * Explicit Value Constructor.
   *
   * @param owner   The parent Frame
   * @param title   The title of this JDialog
   * @param task    The background task
   */
  public BackgroundTaskDialog(final Frame owner, final String title, final SwingWorker<T,V> task)
  {
    super(owner, title);       
    performCommonConstructionTasks(task);
  }


  /**
   * Cancel the countdown timer (if there is one).
   */
  private void cancelCountdownTimer()
  {
    if (countdown != null) 
    {
      countdown.cancel();       
      countdown = null;          
    }
  }

  /**
   * Create the main pane in this JDialog.
   */
  @Override
  protected JComponent createMainPane()
  {
    JPanel result = new JPanel();
    result.setLayout(new BorderLayout());

    JProgressBar progressBar = new JProgressBar();
    progressBar.setIndeterminate(true);
    progressBar.setOrientation(SwingConstants.HORIZONTAL);
    progressBar.setBorderPainted(true);       

    result.add(progressBar, BorderLayout.SOUTH);

    return result;
  }


  /**
   * The code to execute in the event dispatch thread.
   */
  public void execute()
  {
    int status;

    status = OK_OPTION;

    // Execute the background task (in a worker thread)
    task.execute();

    // If the task should timeout, start the countdown timer
    if (timeout > 0) startCountdownTimer();       

    // Show the dialog (unless the task has already completed somehow)
    if (!task.isDone()) status = showDialog();

    // Cancel the countdown timer (if there is one)
    cancelCountdownTimer();

    // If the dialog was canceled (i.e., the task didn't complete)
    // then cancel the task
    if (status == CANCEL_OPTION)
    {
      if (task != null) task.cancel(true);
    }
  }


  /**
   * Perform common construction tasks.
   * 
   * @param swtask The SwingWorker
   */
  private void performCommonConstructionTasks(final SwingWorker<T,V> swtask)
  {

    setIncludeCancel(true);
    setIncludeOK(false);       
    this.task = swtask;       
    task.addPropertyChangeListener(this);       

    timeout = 0;       
  }



  /**
   * Handle propertyChange messages.
   *
   * Note: This method should only be called by the SwingWorker, and
   * the SwingWorker always calls this method on the event dispatch
   * thread.
   *
   * @param evt   The PropertyChangeEvent associated with the message
   */
  @Override
  public void propertyChange(final PropertyChangeEvent evt)
  {
    SwingWorker.StateValue   state;

    state = task.getState();

    if (state.equals(SwingWorker.StateValue.DONE)) 
    {
      // Cancel the countdown timer
      cancelCountdownTimer();

      // Hide this dialog
      setVisible(false);
    }
  }


  /**
   * Set the timeout for the task.

   * The timeout must be positive. A value of 0 or less
   * is treated as an infinite timeout.
   *
   * @param taskTimeout  The timeout (in milliseconds)
   */
  public void setTaskTimeout(final long taskTimeout)
  {
    this.timeout = taskTimeout;       
  }


  /**
   * Start the countdown timer.
   */
  private void startCountdownTimer()
  {
    countdown = new java.util.Timer();

    countdown.schedule(new TimeoutTask(), timeout);
  }


  /**
   * A TimerTask for handling time-outs.
   */
  private class TimeoutTask extends TimerTask
  {
    /**
     * The action to be performed by this TimerTask.
     */
    public void run()
    {
      // Too much time has elapsed so cancel the task
      task.cancel(true);
    }
  }

}
