package hirondelle.stocks.quotes;

import hirondelle.stocks.util.Args;
import hirondelle.stocks.util.Consts;
import hirondelle.stocks.util.Util;

import java.awt.Color;
import java.awt.EventQueue;
import java.util.logging.Logger;

import javax.swing.JComponent;

/**
* Calls user's attention to an aspect of the GUI (much like a 
* <tt>ToolTip</tt>) by changing the background color of a 
* component (typically a <tt>JLabel</tt>) for a few seconds; 
* the component will always revert to its original background color 
* after a short time has passed. This is done once, without repeating.
*
* <p>Example use case:
<pre>
 //no initial delay, and show the new color for 2 seconds only
 ColorTip tip = new ColorTip(0, 2, someLabel, temporaryColor);
 tip.start();
</pre>
* 
* Uses a daemon thread, so this class will not prevent a program from 
* terminating. Will not lock the GUI.
*/
final class ColorTip {
  
  /**
  * Constructor. 
  *  
  * @param aInitialDelay number of seconds to wait before changing the 
  * background color of <tt>aComponent</tt>, and must be in range 0..60 (inclusive).
  * @param aActivationInterval number of seconds to display <tt>aTempColor</tt>, 
  * and must be in range 1..60 (inclusive).
  * @param aComponent GUI item whose background color will be changed.
  * @param aTempColor background color which <tt>aComponent</tt> will take for 
  * <tt>aActivationInterval</tt> seconds.
  */
  ColorTip (
    int aInitialDelay, int aActivationInterval, JComponent aComponent, Color aTempColor
   ) {
    Args.checkForRange(aInitialDelay, 0, Consts.SECONDS_PER_MINUTE);
    Args.checkForRange(aActivationInterval, 1, Consts.SECONDS_PER_MINUTE);
    Args.checkForNull(aTempColor);
    fInitialDelay = aInitialDelay;
    fActivationInterval = aActivationInterval;
    fComponent = aComponent;
    fTemporaryColor = aTempColor;
    fOriginalColor = aComponent.getBackground();
    fOriginalOpacity = aComponent.isOpaque();
  }

  /**
  * Temporarily change the background color of the component, without interfering with 
  * the user's control of the gui, and without preventing program termination.
  *
  * <P>If the target temporary color is the same as the current background color, then 
  * do nothing. (This condition occurs when two <tt>ColorTip</tt> objects are 
  * altering the same item at nearly the same time, such that they "overlap".)
  */
  void start(){
    if (isSameColor()) return;
    /*
     * The use of a low-level Thread, instead of a more modern class, is unusual here.
     * It's acceptable since other tools aren't a great match for this task, which is to 
     * go back and forth TWICE (wait, color-on, wait, color-off) between a worker thread
     * and the Event Dispatch Thread; that's not a good match for either SwingWorker 
     * or javax.swing.Timer.
     */
    Thread thread = new Thread(new Worker());
    thread.setDaemon(true);
    thread.start();
  }
  
  // PRIVATE 
  private final int fInitialDelay;
  private final int fActivationInterval;
  private final JComponent fComponent;
  private final Color fTemporaryColor;
  private final Color fOriginalColor;
  private final int fCONVERSION_FACTOR = Consts.MILLISECONDS_PER_SECOND;
  
  /**
  * Stores the original value of the opaque property of fComponent.
  *  
  * Changes to the background color of a component 
  * take effect only if the component is in charge of drawing its background.
  * This is defined by the opaque property, which needs to be true for these
  * changes to take effect.
  *
  * <P>If fComponent is not opaque, then this property is temporarily
  * changed by this class in order to change the background color.
  */
  private final boolean fOriginalOpacity;
  
  private static final Logger fLogger = Util.getLogger(ColorTip.class);

  /**
  * Return true only if fTemporaryColor is the same as the fOriginalColor. 
  */
  private boolean isSameColor(){
    return fTemporaryColor.equals(fOriginalColor);
  }
  
  /** 
   The sleeping done by this class is NOT done on the Event Dispatch Thread; 
   that would lock the GUI. 
  */
  private final class Worker implements Runnable {
    @Override public void run(){
      try {
        fLogger.fine("Initial Sleeping...");
        Thread.sleep(fCONVERSION_FACTOR * fInitialDelay);
        EventQueue.invokeLater(new ChangeColor());
        fLogger.fine("Activation Sleeping...");
        Thread.sleep(fCONVERSION_FACTOR * fActivationInterval);
        EventQueue.invokeLater(new RevertColor());
      }
      catch (InterruptedException ex) {
        fLogger.severe("Cannot sleep.");
      }
      fLogger.fine("Color worker done.");
    }
  }
  
  private final class ChangeColor implements Runnable {
    @Override public void run(){
      if (! fOriginalOpacity) {
        fComponent.setOpaque(true);
      }
      fComponent.setBackground(fTemporaryColor);
    }
  }
  
  private final class RevertColor implements Runnable {
    @Override public void run(){
      fComponent.setBackground(fOriginalColor);
      fComponent.setOpaque(fOriginalOpacity);
    }
  }
}