/*
 * Copyright (c) 2005-2009 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  o Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  o Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 *  o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.jvnet.lafwidget.animation;

import java.awt.Component;
import java.awt.Container;
import java.util.*;

import javax.swing.SwingUtilities;

import org.jvnet.lafwidget.LafWidgetUtilities;
import org.jvnet.lafwidget.utils.TrackableThread;
import org.jvnet.lafwidget.utils.LafConstants.AnimationKind;

/**
 * Tracker class for fade animations.
 * 
 * @author Kirill Grouchnikov
 */
public class FadeTracker {
	/**
	 * Max value for fade cycle.
	 */
	public static final int END_VALUE = 1;

	/**
	 * Debug mode indicator. Set to <code>true</code> to have trace messages on
	 * console.
	 */
	public static boolean DEBUG_MODE = false;

	/**
	 * Single instance of <code>this</code> class.
	 */
	private static FadeTracker instance;

	/**
	 * All currently tracked components. Only components that are being animated
	 * (fade-in or fade-out) are in <code>this</code> map. Key is
	 * {@link FadeKind}, value is map from {@link ComponentId} to
	 * {@link FadeState}.
	 */
	private Map<FadeKind, Map<ComponentId, FadeState>> trackedComponents;

	/**
	 * All currently executing fade instances. Key is {@link Long} (corresponds
	 * to the {@link FadeState#id}, value is {@link FadeKind} - which is a key
	 * in {@link #trackedComponents}.
	 */
	private Map<Long, FadeKind> fadeInstances;

	/**
	 * ID of the current loop iteration.
	 */
	private long currLoopId;

	/**
	 * List of global fade tracker callbacks.
	 */
	private List<GlobalFadeTrackerCallback> globalCallbackList;

	private boolean nothingTracked;

	/**
	 * Information on a single component under fade.
	 * 
	 * @author Kirill Grouchnikov
	 */
	protected static class ComponentId {
		/**
		 * UI component itself.
		 */
		public Component component;

		/**
		 * ID to distinguish between different sub-components of the UI
		 * component. For example, the tabbed pane uses this field to make
		 * tab-specific animations.
		 */
		@SuppressWarnings("unchecked")
		public Comparable subID;

		/**
		 * Creates a new component ID.
		 * 
		 * @param component
		 *            UI component itself.
		 * @param subID
		 *            ID to distinguish between different sub-components of the
		 *            UI component.
		 */
		@SuppressWarnings("unchecked")
		public ComponentId(Component component, Comparable subID) {
			this.component = component;
			this.subID = subID;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#hashCode()
		 */
		@Override
		public int hashCode() {
			int result = this.component.hashCode();
			if (this.subID != null)
				result &= (this.subID.hashCode());
			return result;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#equals(java.lang.Object)
		 */
		@Override
		@SuppressWarnings("unchecked")
		public boolean equals(Object obj) {
			if (obj instanceof ComponentId) {
				ComponentId cid = (ComponentId) obj;
				try {
					boolean result = (this.component == cid.component);
					if (this.subID == null) {
						result = result && (cid.subID == null);
					} else {
						result = result
								&& (this.subID.compareTo(cid.subID) == 0);
					}
					return result;
				} catch (Exception exc) {
					return false;
				}
			}
			return false;
		}
	}

	/**
	 * The fade thread.
	 */
	private FadeTrackerThread trackerThread;

	/**
	 * The fade thread class.
	 * 
	 * @author Kirill Grouchnikov
	 */
	private class FadeTrackerThread extends TrackableThread {
		/**
		 * Fade thread delay.
		 */
		public static final int DELAY = 40;

		/**
		 * Indication whether a stop request has been issued on
		 * <code>this</code> thread.
		 */
		private boolean stopRequested;

		/**
		 * Simple constructor. Defined private for singleton.
		 * 
		 * @see #getInstance()
		 */
		public FadeTrackerThread() {
			super();
			this.setName("Laf-Widget fade tracker");
			this.stopRequested = false;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Thread#run()
		 */
		@Override
		public void run() {
			while (!this.stopRequested) {
				try {
					Thread.sleep(FadeTrackerThread.DELAY);
				} catch (InterruptedException ie) {
					ie.printStackTrace();
				}
				// SwingUtilities.invokeLater(new Runnable() {
				// public void run() {
				FadeTracker.this.updateComponents();
				currLoopId++;
				// }
				// });
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.jvnet.substance.utils.SubstanceThread#requestStop()
		 */
		@Override
		protected void requestStop() {
			this.stopRequested = true;
			FadeTracker.this.trackerThread = null;
		}
	}

	/**
	 * Simple constructor. Defined private for singleton.
	 * 
	 * @see #getInstance()
	 */
	private FadeTracker() {
		this.trackerThread = this.getThread();
		this.trackedComponents = new HashMap<FadeKind, Map<ComponentId, FadeState>>();
		this.fadeInstances = new HashMap<Long, FadeKind>();
		this.currLoopId = 0;

		this.globalCallbackList = new ArrayList<GlobalFadeTrackerCallback>();
	}

	/**
	 * Gets singleton instance.
	 * 
	 * @return Singleton instance.
	 */
	public synchronized static FadeTracker getInstance() {
		if (FadeTracker.instance == null) {
			FadeTracker.instance = new FadeTracker();
		}
		return FadeTracker.instance;
	}

	/**
	 * Updates all components that are currently registered with
	 * <code>this</code> tracker. The update include repaint (based on the
	 * {@link FadeState#toRepaintParent}) and optional call to the callback.
	 */
	private synchronized void updateComponents() {
		if (this.trackedComponents.size() == 0)
			return;

		// if (this.comp2int.size() > 0)
		// System.out.println("Periodic update");
		Set<Container> parents = null;
		for (Iterator<Map.Entry<FadeKind, Map<ComponentId, FadeState>>> itComp = this.trackedComponents
				.entrySet().iterator(); itComp.hasNext();) {
			Map.Entry<FadeKind, Map<ComponentId, FadeState>> entryComp = itComp
					.next();
			// FadeKind fadeKind = entryComp.getKey();
			Map<ComponentId, FadeState> mapComp = entryComp.getValue();
			for (Iterator<Map.Entry<ComponentId, FadeState>> itKind = mapComp
					.entrySet().iterator(); itKind.hasNext();) {
				Map.Entry<ComponentId, FadeState> entryKind = itKind.next();
				ComponentId compId = entryKind.getKey();
				FadeState state = entryKind.getValue();
				boolean hasEnded = false;
				if (DEBUG_MODE) {
					System.out
							.println("Processing "
									+ state.id
									+ "["
									+ compId.component.getClass()
											.getSimpleName()
									+ "] from "
									+ state.getFadePosition()
									+ ". Callback - "
									+ (state.getCallback() == null ? "no"
											: "yes")
									+ ". Displayable "
									+ (compId.component.isDisplayable() ? "yes"
											: "no"));
				}
				// Component comp = entry.getKey();
				if (state.isFadingIn()) {
					state.setFadePosition(state.getFadePosition()
							+ state.fadeStep.getNextStep(state.fadeKind, state
									.getFadePosition(), state.isFadingIn(),
									state.isLooping()));
					if (DEBUG_MODE) {
						System.out.println("New cycle position is "
								+ state.getFadePosition());
					}
					if (state.getFadePosition() > FadeTracker.END_VALUE) {
						state.setFadePosition(FadeTracker.END_VALUE);
						if (state.isLooping()) {
							if (state.isLoopingReverse()) {
								state.setFadingIn(false);
							} else {
								if (state.isToStopAtCycleBreak()) {
									this.fadeInstances
											.remove(new Long(state.id));
									hasEnded = true;
									if (state.getCallback() != null)
										callbackCallFadeEnded(state
												.getCallback(), state.fadeKind);
									this.globalCallbackCallFadeEnded(
											compId.component, compId.subID,
											state.fadeKind);
									itKind.remove();
								} else {
									state.setFadePosition(0.0f);
								}
							}
						} else {
							this.fadeInstances.remove(new Long(state.id));
							hasEnded = true;
							itKind.remove();
						}
					}
				} else {
					state.setFadePosition(state.getFadePosition()
							- state.fadeStep.getNextStep(state.fadeKind, state
									.getFadePosition(), state.isFadingIn(),
									state.isLooping()));
					if (DEBUG_MODE) {
						System.out.println("New cycle position is "
								+ state.getFadePosition());
					}
					if (state.getFadePosition() < 0) {
						state.setFadePosition(0.0f);
						if (state.isLooping()) {
							boolean stopLoopingAnimation = state
									.isToStopAtCycleBreak();
							int loopsToLive = state.getLoopCount();
							if (loopsToLive > 0) {
								loopsToLive--;
								stopLoopingAnimation = stopLoopingAnimation
										|| (loopsToLive == 0);
								state.setLoopCount(loopsToLive);
							}
							if (stopLoopingAnimation) {
								// end looping animation
								this.fadeInstances.remove(new Long(state.id));
								hasEnded = true;
								itKind.remove();
							} else {
								state.setFadingIn(true);
							}
						} else {
							this.fadeInstances.remove(new Long(state.id));
							hasEnded = true;
							itKind.remove();
						}
					}
				}
				if (compId.component.isDisplayable()) {
					if (state.getCallback() != null) {
						if (hasEnded) {
							if (DEBUG_MODE) {
								System.out.println("Ending " + state.id
										+ " on " + state.fadeKind.toString());
							}
							this.callbackCallFadeEnded(state.getCallback(),
									state.fadeKind);
						} else {
							if (DEBUG_MODE) {
								System.out.println("Calling " + state.id
										+ " on " + state.fadeKind.toString()
										+ " at " + state.getFadePosition());
							}
							this.callbackCallFadePerformed(state.getCallback(),
									state.fadeKind, state.getFadePosition());
						}
						// continue;
					}
					if (hasEnded) {
						this.globalCallbackCallFadeEnded(compId.component,
								compId.subID, state.fadeKind);
					} else {
						this.globalCallbackCallFadePerformed(compId.component,
								compId.subID, state.fadeKind, state
										.getFadePosition());
					}
					// if we have an internal callback, go to the next component
					if (state.getCallback() != null) {
						continue;
					}

					if (state.isToRepaintParent()) {
						// Lazy creation for performance
						if (parents == null)
							parents = new HashSet<Container>();
						parents.add(compId.component.getParent());
					} else {
						compId.component.repaint();
					}
				} else {
					if (state.fadeKind == FadeKind.GHOSTING_BUTTON_PRESS) {
						this.callbackCallFadeEnded(state.getCallback(),
								state.fadeKind);
						this.globalCallbackCallFadeEnded(compId.component,
								compId.subID, state.fadeKind);
					}
				}
			}
			// if (mapComp.size() == 0)
			// itComp.remove();
			// System.out.println("\tState of " + comp.hashCode() + " is "
			// + state.fadePosition + " fading "
			// + (state.isFadingIn ? "in" : "out"));
			// long time0 = System.nanoTime();

			// long time1 = System.nanoTime();
			// if (entry.getKey() instanceof JButton) {
			// System.out.println(((JButton) entry.getKey()).getText() + " [" +
			// state.isFadingIn + "]: "
			// + (time1 - time0));
			// }
		}
		if ((parents != null) && (parents.size() > 0)) {
			for (Iterator<Container> itParent = parents.iterator(); itParent
					.hasNext();) {
				Container parent = itParent.next();
				parent.repaint();
			}
		}

		this.nothingTracked = (this.fadeInstances.size() == 0);
	}

	private void callbackCallFadeEnded(final FadeTrackerCallback fadeCallback,
			final FadeKind fadeKind) {
		boolean shouldRunOnEDT = false;
		Class<?> clazz = fadeCallback.getClass();
		while ((clazz != null) && !shouldRunOnEDT) {
			shouldRunOnEDT = clazz.isAnnotationPresent(RunOnUIThread.class);
			clazz = clazz.getSuperclass();
		}
		if (shouldRunOnEDT) {
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					fadeCallback.fadeEnded(fadeKind);
				}
			});
		} else {
			fadeCallback.fadeEnded(fadeKind);
		}
	}

	private void callbackCallFadeReversed(
			final FadeTrackerCallback fadeCallback, final FadeKind fadeKind,
			final boolean isFadingIn, final float fadeCycle10) {
		boolean shouldRunOnEDT = false;
		Class<?> clazz = fadeCallback.getClass();
		while ((clazz != null) && !shouldRunOnEDT) {
			shouldRunOnEDT = clazz.isAnnotationPresent(RunOnUIThread.class);
			clazz = clazz.getSuperclass();
		}
		if (shouldRunOnEDT) {
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					fadeCallback
							.fadeReversed(fadeKind, isFadingIn, fadeCycle10);
				}
			});
		} else {
			fadeCallback.fadeReversed(fadeKind, isFadingIn, fadeCycle10);
		}
	}

	private void callbackCallFadePerformed(
			final FadeTrackerCallback fadeCallback, final FadeKind fadeKind,
			final float fadePosition) {
		boolean shouldRunOnEDT = false;
		Class<?> clazz = fadeCallback.getClass();
		while ((clazz != null) && !shouldRunOnEDT) {
			shouldRunOnEDT = clazz.isAnnotationPresent(RunOnUIThread.class);
			clazz = clazz.getSuperclass();
		}
		if (shouldRunOnEDT) {
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					fadeCallback.fadePerformed(fadeKind, fadePosition);
				}
			});
		} else {
			fadeCallback.fadePerformed(fadeKind, fadePosition);
		}
	}

	private void globalCallbackCallFadeEnded(final Component comp,
			final Comparable subID, final FadeKind fadeKind) {
		if (this.globalCallbackList.size() == 0)
			return;
		if (!fadeKind.isCore)
			return;

		for (GlobalFadeTrackerCallback globalCallback : this.globalCallbackList) {
			globalCallback.fadeEnded(comp, subID, fadeKind);
		}
	}

	private void globalCallbackCallFadeStarted(final Component comp,
			final Comparable subID, final FadeKind fadeKind,
			float initialFadeCycle10) {
		if (this.globalCallbackList.size() == 0)
			return;
		if (!fadeKind.isCore)
			return;

		for (GlobalFadeTrackerCallback globalCallback : this.globalCallbackList) {
			globalCallback.fadeStarted(comp, subID, fadeKind,
					initialFadeCycle10);
		}
	}

	private void globalCallbackCallFadeReversed(final Component comp,
			final Comparable subID, final FadeKind fadeKind,
			final boolean isFadingIn, final float fadeCycle10) {
		if (this.globalCallbackList.size() == 0)
			return;
		if (!fadeKind.isCore)
			return;

		for (GlobalFadeTrackerCallback globalCallback : this.globalCallbackList) {
			globalCallback.fadeReversed(comp, subID, fadeKind, isFadingIn,
					fadeCycle10);
		}
	}

	private void globalCallbackCallFadePerformed(final Component comp,
			final Comparable subID, final FadeKind fadeKind,
			final float fadePosition) {
		if (this.globalCallbackList.size() == 0)
			return;
		if (!fadeKind.isCore)
			return;

		for (GlobalFadeTrackerCallback globalCallback : this.globalCallbackList) {
			globalCallback.fadePerformed(comp, subID, fadeKind, fadePosition);
		}
	}

	/**
	 * Returns the state of the component under the specified fade kind.
	 * 
	 * @param fadeKind
	 *            Fade kind.
	 * @param comp
	 *            Component.
	 * @param componentId
	 *            Component id. Relevant for such components as tabbed panes
	 *            (where fade is performed on component "sub" parts).
	 * @return Component state under the specified fade kind.
	 */
	private synchronized FadeState getState(FadeKind fadeKind, Component comp,
			Comparable<?> componentId) {
		ComponentId cid = new ComponentId(comp, componentId);
		Map<ComponentId, FadeState> map = this.trackedComponents.get(fadeKind);
		if (map == null) {
			map = new HashMap<ComponentId, FadeState>();
			this.trackedComponents.put(fadeKind, map);
		}
		return map.get(cid);
	}

	/**
	 * Adds new fade state for the specified component.
	 * 
	 * @param comp
	 *            Component.
	 * @param componentId
	 *            Component id. Relevant for such components as tabbed panes
	 *            (where fade is performed on component "sub" parts).
	 * @param fadeKind
	 *            Fade kind.
	 * @param fadeState
	 *            Fade state object.
	 */
	private synchronized void addState(Component comp,
			Comparable<?> componentId, FadeKind fadeKind, FadeState fadeState) {
		ComponentId cid = new ComponentId(comp, componentId);
		Map<ComponentId, FadeState> map = this.trackedComponents.get(fadeKind);
		if (map == null) {
			map = new HashMap<ComponentId, FadeState>();
			this.trackedComponents.put(fadeKind, map);
		}
		map.put(cid, fadeState);
		this.fadeInstances.put(fadeState.id, fadeKind);
		this.nothingTracked = false;
		if (DEBUG_MODE) {
			System.out.println("Added (" + fadeState.id + ") on "
					+ comp.getClass().getSimpleName() + "[" + componentId
					+ "]. Fade " + fadeKind.toString() + " and fading "
					+ (fadeState.isFadingIn() ? "in" : "out") + ". Callback - "
					+ (fadeState.getCallback() == null ? "no" : "yes"));
		}
	}

	/**
	 * Requests start of fade-in tracking on the specified component.
	 * 
	 * @param fadeKind
	 *            Fade kind.
	 * @param comp
	 *            The component to track.
	 * @param toRepaintParent
	 *            Indication whether the component parent should be repainted.
	 * @param callback
	 *            Optional callback to be called on each fade cycle.
	 * @return ID of the fade instance. Guaranteed to be unique. Value
	 *         <code>-1</code> signifies that there was no fade instance created
	 *         (this may happen when the specified component is not visible).
	 */
	public synchronized long trackFadeIn(FadeKind fadeKind, Component comp,
			boolean toRepaintParent, FadeTrackerCallback callback) {
		return this
				.trackFadeIn(fadeKind, comp, null, toRepaintParent, callback);
	}

	/**
	 * Requests start of fade-in tracking on the specified component.
	 * 
	 * @param fadeKind
	 *            Fade kind.
	 * @param comp
	 *            The component to track.
	 * @param componentId
	 *            Component id. Relevant for such components as tabbed panes
	 *            (where fade is performed on component "sub" parts).
	 * @param toRepaintParent
	 *            Indication whether the component parent should be repainted.
	 * @param callback
	 *            Optional callback to be called on each fade cycle.
	 * @return ID of the fade instance. Guaranteed to be unique. Value
	 *         <code>-1</code> signifies that there was no fade instance created
	 *         (this may happen when the specified component is not visible).
	 */
	public synchronized long trackFadeIn(FadeKind fadeKind, Component comp,
			int componentId, boolean toRepaintParent,
			FadeTrackerCallback callback) {
		return this.trackFadeIn(fadeKind, comp, new Integer(componentId),
				toRepaintParent, callback);
	}

	/**
	 * Requests start of fade-in tracking on the specified component.
	 * 
	 * @param fadeKind
	 *            Fade kind.
	 * @param comp
	 *            The component to track.
	 * @param componentId
	 *            Component id. Relevant for such components as tabbed panes,
	 *            lists, tables, trees and others (where fade is performed on
	 *            component "sub" parts). May be <code>null</code>.
	 * @param toRepaintParent
	 *            Indication whether the component parent should be repainted.
	 * @param callback
	 *            Optional callback to be called on each fade cycle.
	 * @return ID of the fade instance. Guaranteed to be unique. Value
	 *         <code>-1</code> signifies that there was no fade instance created
	 *         (this may happen when the specified component is not visible).
	 */
	public synchronized long trackFadeIn(FadeKind fadeKind, Component comp,
			Comparable<?> componentId, boolean toRepaintParent,
			FadeTrackerCallback callback) {
		return this.trackFadeIn(fadeKind, comp, componentId, toRepaintParent,
				callback, LafWidgetUtilities.getAnimationKind(comp));
	}

	/**
	 * Requests start of fade-in tracking on the specified component.
	 * 
	 * @param fadeKind
	 *            Fade kind.
	 * @param comp
	 *            The component to track.
	 * @param componentId
	 *            Component id. Relevant for such components as tabbed panes,
	 *            lists, tables, trees and others (where fade is performed on
	 *            component "sub" parts). May be <code>null</code>.
	 * @param toRepaintParent
	 *            Indication whether the component parent should be repainted.
	 * @param callback
	 *            Optional callback to be called on each fade cycle.
	 * @param aKind
	 *            Animation kind. Should not be <code>null</code>.
	 * @return ID of the fade instance. Guaranteed to be unique. Value
	 *         <code>-1</code> signifies that there was no fade instance created
	 *         (this may happen when the specified component is not visible).
	 */
	public synchronized long trackFadeIn(FadeKind fadeKind, Component comp,
			Comparable<?> componentId, boolean toRepaintParent,
			FadeTrackerCallback callback, AnimationKind aKind) {
		// System.out.println(fadeKind.toString() + " on "
		// + comp.getClass().getName());
		if (fadeKind == null) {
			throw new IllegalArgumentException("Can't pass null fadeKind");
		}

		if ((comp == null) || (!comp.isDisplayable())) {
			if (callback != null) {
				this.callbackCallFadeEnded(callback, fadeKind);
			}
			this.globalCallbackCallFadeEnded(comp, componentId, fadeKind);
			return -1;
		}

		if (LafWidgetUtilities.hasNoFades(comp, fadeKind)) {
			if (callback != null) {
				this.callbackCallFadeEnded(callback, fadeKind);
			} else {
				if (toRepaintParent && comp.getParent() != null)
					comp.getParent().repaint();
				else
					comp.repaint();
			}
			this.globalCallbackCallFadeEnded(comp, componentId, fadeKind);
			return -1;
		}

		this.getThread();

		// see if it's already tracked
		FadeState fadeState = this.getState(fadeKind, comp, componentId);
		if (fadeState == null) {
			fadeState = new FadeState(fadeKind, 0, true, toRepaintParent);
			fadeState.fadeStep = aKind.getStep();
			if (fadeKind == FadeKind.SELECTION) {
				fadeState.fadeStep = new FadeAccelerationStep(
						fadeState.fadeStep, 2.0f);
			}
			fadeState.setCallback(callback);
			this.addState(comp, componentId, fadeKind, fadeState);
			this.globalCallbackCallFadeStarted(comp, componentId, fadeKind,
					fadeState.getFadePosition());
		} else {
			if (!fadeState.isFadingIn()) {
				if (fadeState.getCallback() != null) {
					this.callbackCallFadeReversed(fadeState.getCallback(),
							fadeKind, true, fadeState.getFadePosition());
					// fadeState.getCallback().fadeReversed(fadeKind, true,
					// fadeState.getFadePosition());
				}
				this.globalCallbackCallFadeReversed(comp, componentId,
						fadeKind, true, fadeState.getFadePosition());
			}
			fadeState.setFadingIn(true);
			fadeState.setCallback(callback);
		}
		return fadeState.id;
		// System.out.println(sdf.format(new Date())
		// + " : starting tracking fade in on "
		// + comp.getClass().getName() + " [" + comp.hashCode()
		// + "] from " + fadeState.fadePosition);
	}

	/**
	 * Requests start of fade-out tracking on the specified component.
	 * 
	 * @param fadeKind
	 *            Fade kind.
	 * @param comp
	 *            The component to track.
	 * @param toRepaintParent
	 *            Indication whether the component parent should be repainted.
	 * @param callback
	 *            Optional callback to be called on each fade cycle.
	 * @return ID of the fade instance. Guaranteed to be unique. Value
	 *         <code>-1</code> signifies that there was no fade instance created
	 *         (this may happen when the specified component is not visible).
	 */
	public synchronized long trackFadeOut(FadeKind fadeKind, Component comp,
			boolean toRepaintParent, FadeTrackerCallback callback) {
		return this.trackFadeOut(fadeKind, comp, null, toRepaintParent,
				callback);
	}

	/**
	 * Requests start of fade-out tracking on the specified component.
	 * 
	 * @param fadeKind
	 *            Fade kind.
	 * @param comp
	 *            The component to track.
	 * @param componentId
	 *            Component id. Relevant for such components as tabbed panes
	 *            (where fade is performed on component "sub" parts).
	 * @param toRepaintParent
	 *            Indication whether the component parent should be repainted.
	 * @param callback
	 *            Optional callback to be called on each fade cycle.
	 * @return ID of the fade instance. Guaranteed to be unique. Value
	 *         <code>-1</code> signifies that there was no fade instance created
	 *         (this may happen when the specified component is not visible).
	 */
	public synchronized long trackFadeOut(FadeKind fadeKind, Component comp,
			int componentId, boolean toRepaintParent,
			FadeTrackerCallback callback) {
		return this.trackFadeOut(fadeKind, comp, new Integer(componentId),
				toRepaintParent, callback);
	}

	/**
	 * Requests start of fade-out tracking on the specified component.
	 * 
	 * @param fadeKind
	 *            Fade kind.
	 * @param comp
	 *            The component to track.
	 * @param componentId
	 *            Component id. Relevant for such components as tabbed panes,
	 *            lists, tables, trees and others (where fade is performed on
	 *            component "sub" parts). May be <code>null</code>.
	 * @param toRepaintParent
	 *            Indication whether the component parent should be repainted.
	 * @param callback
	 *            Optional callback to be called on each fade cycle.
	 * @return ID of the fade instance. Guaranteed to be unique. Value
	 *         <code>-1</code> signifies that there was no fade instance created
	 *         (this may happen when the specified component is not visible).
	 */
	public synchronized long trackFadeOut(FadeKind fadeKind, Component comp,
			Comparable<?> componentId, boolean toRepaintParent,
			FadeTrackerCallback callback) {
		return this.trackFadeOut(fadeKind, comp, componentId, toRepaintParent,
				callback, LafWidgetUtilities.getAnimationKind(comp));
	}

	/**
	 * Requests start of fade-out tracking on the specified component.
	 * 
	 * @param fadeKind
	 *            Fade kind.
	 * @param comp
	 *            The component to track.
	 * @param componentId
	 *            Component id. Relevant for such components as tabbed panes,
	 *            lists, tables, trees and others (where fade is performed on
	 *            component "sub" parts). May be <code>null</code>.
	 * @param toRepaintParent
	 *            Indication whether the component parent should be repainted.
	 * @param callback
	 *            Optional callback to be called on each fade cycle.
	 * @param aKind
	 *            Animation kind. Should not be <code>null</code>.
	 * @return ID of the fade instance. Guaranteed to be unique. Value
	 *         <code>-1</code> signifies that there was no fade instance created
	 *         (this may happen when the specified component is not visible).
	 */
	public synchronized long trackFadeOut(FadeKind fadeKind, Component comp,
			Comparable<?> componentId, boolean toRepaintParent,
			FadeTrackerCallback callback, AnimationKind aKind) {
		if (fadeKind == null) {
			throw new IllegalArgumentException("Can't pass null fadeKind");
		}

		if ((comp == null) || (!comp.isDisplayable())) {
			if (callback != null) {
				callbackCallFadeEnded(callback, fadeKind);
			}
			this.globalCallbackCallFadeEnded(comp, componentId, fadeKind);
			return -1;
		}

		if (LafWidgetUtilities.hasNoFades(comp, fadeKind)) {
			if (callback != null) {
				callbackCallFadeEnded(callback, fadeKind);
			} else {
				if (toRepaintParent && comp.getParent() != null)
					comp.getParent().repaint();
				else
					comp.repaint();
			}
			this.globalCallbackCallFadeEnded(comp, componentId, fadeKind);
			return -1;
		}

		this.getThread();

		// see if it's already tracked
		FadeState fadeState = this.getState(fadeKind, comp, componentId);
		if (fadeState == null) {
			fadeState = new FadeState(fadeKind, FadeTracker.END_VALUE, false,
					toRepaintParent);
			fadeState.fadeStep = aKind.getStep();
			if (fadeKind == FadeKind.SELECTION)
				fadeState.fadeStep = new FadeAccelerationStep(
						fadeState.fadeStep, 2.0f);
			fadeState.setCallback(callback);
			this.addState(comp, componentId, fadeKind, fadeState);
			this.globalCallbackCallFadeStarted(comp, componentId, fadeKind,
					fadeState.getFadePosition());
		} else {
			if (fadeState.isFadingIn()) {
				if (fadeState.getCallback() != null) {
					callbackCallFadeReversed(fadeState.getCallback(), fadeKind,
							false, fadeState.getFadePosition());
					// fadeState.getCallback().fadeReversed(fadeKind, false,
					// fadeState.getFadePosition());
				}
				this.globalCallbackCallFadeReversed(comp, componentId,
						fadeKind, false, fadeState.getFadePosition());
			}
			fadeState.setFadingIn(false);
			fadeState.setCallback(callback);
		}
		return fadeState.id;
		// System.out.println(sdf.format(new Date())
		// + " : starting tracking fade out on "
		// + comp.getClass().getName() + " [" + comp.hashCode()
		// + "] from " + fadeState.fadePosition);
	}

	/**
	 * Requests start of fade tracking on the specified component.
	 * 
	 * @param comp
	 *            The component to track.
	 * @param fadeKind
	 *            Fade kind.
	 * @param isFadeIn
	 *            Indication whether fade-in or fade-out should be commenced.
	 * @param toRepaintParent
	 *            Indication whether the component parent should be repainted.
	 * @return ID of the fade instance. Guaranteed to be unique. Value
	 *         <code>-1</code> signifies that there was no fade instance created
	 *         (this may happen when the specified component is not visible).
	 */
	public synchronized long trackFade(Component comp, FadeKind fadeKind,
			boolean isFadeIn, boolean toRepaintParent) {
		return this.trackFade(comp, fadeKind, isFadeIn, toRepaintParent, null);
	}

	/**
	 * Requests start of fade tracking on the specified component.
	 * 
	 * @param comp
	 *            The component to track.
	 * @param fadeKind
	 *            Fade kind.
	 * @param isFadeIn
	 *            Indication whether fade-in or fade-out should be commenced.
	 * @param toRepaintParent
	 *            Indication whether the component parent should be repainted.
	 * @param callback
	 *            Optional callback to be called on each fade cycle.
	 * @return ID of the fade instance. Guaranteed to be unique. Value
	 *         <code>-1</code> signifies that there was no fade instance created
	 *         (this may happen when the specified component is not visible).
	 */
	public synchronized long trackFade(Component comp, FadeKind fadeKind,
			boolean isFadeIn, boolean toRepaintParent,
			FadeTrackerCallback callback) {
		if (isFadeIn)
			return this.trackFadeIn(fadeKind, comp, toRepaintParent, callback);
		else
			return this.trackFadeOut(fadeKind, comp, toRepaintParent, callback);
	}

	/**
	 * Requests start of looping fade tracking on the specified component.
	 * 
	 * @param fadeKind
	 *            Fade kind.
	 * @param animationKind
	 *            Animation kind.
	 * @param comp
	 *            The component to track.
	 * @param componentId
	 *            Component id. Relevant for such components as tabbed panes,
	 *            lists, tables, trees and others (where fade is performed on
	 *            component "sub" parts). May be <code>null</code>.
	 * @param toRepaintParent
	 *            Indication whether the component parent should be repainted.
	 * @param callback
	 *            Optional callback to be called on each fade cycle.
	 * @param loopCount
	 *            Loop count.
	 * @param isLoopingReverse
	 *            If <code>true</code>, when the fade value gets to the maximal
	 *            value, the fade cycle will begin fade-out. Otherwise the fade
	 *            cycle will begin from 0, continuing to fade-in.
	 * @return ID of the fade instance. Guaranteed to be unique. Value
	 *         <code>-1</code> signifies that there was no fade instance created
	 *         (this may happen when the specified component is not visible).
	 */
	public synchronized long trackFadeLooping(FadeKind fadeKind,
			AnimationKind animationKind, Component comp,
			Comparable<?> componentId, boolean toRepaintParent,
			FadeTrackerCallback callback, int loopCount,
			boolean isLoopingReverse) {
		if (fadeKind == null) {
			throw new IllegalArgumentException("Can't pass null fadeKind");
		}

		this.getThread();

		if (LafWidgetUtilities.hasNoFades(comp, fadeKind)) {
			if (callback != null) {
				callbackCallFadeEnded(callback, fadeKind);
			} else {
				if (comp != null) {
					if (toRepaintParent && comp.getParent() != null)
						comp.getParent().repaint();
					else
						comp.repaint();
				}
			}
			this.globalCallbackCallFadeEnded(comp, componentId, fadeKind);
			return -1;
		}

		// see if it's already tracked
		FadeState fadeState = this.getState(fadeKind, comp, componentId);
		if (fadeState == null) {
			fadeState = new FadeState(fadeKind, 0, true, toRepaintParent);
			fadeState.fadeStep = animationKind.getStep();
			fadeState.setLooping(true);
			fadeState.setLoopingReverse(isLoopingReverse);
			fadeState.setCallback(callback);
			fadeState.setLoopCount(loopCount);
			this.addState(comp, componentId, fadeKind, fadeState);
			this.globalCallbackCallFadeStarted(comp, componentId, fadeKind,
					fadeState.getFadePosition());
		} else {
			fadeState.setLooping(true);
			fadeState.setLoopingReverse(isLoopingReverse);
			fadeState.setCallback(callback);
			fadeState.setToStopAtCycleBreak(false);
			fadeState.setLoopCount(loopCount);
		}
		return fadeState.id;
	}

	/**
	 * Checks whether the specified component is being tracked by
	 * <code>this</code> tracker. Effectively returns indication whether the
	 * specified component is in fade-in or fade-out animation of the specified
	 * kind.
	 * 
	 * @param comp
	 *            Component.
	 * @param fadeKind
	 *            Fade kind.
	 * @return <code>true</code> if the specified component is being tracked by
	 *         <code>this</code> tracker, <code>false</code> otherwise.
	 */
	public boolean isTracked(Component comp, FadeKind fadeKind) {
		return this.isTracked(comp, null, fadeKind);
	}

	/**
	 * Checks whether the specified component is being tracked by
	 * <code>this</code> tracker. Effectively returns indication whether the
	 * specified component is in fade-in or fade-out animation of the specified
	 * kind.
	 * 
	 * @param comp
	 *            Component.
	 * @param componentId
	 *            Component id. Relevant for such components as tabbed panes
	 *            (where fade is performed on component "sub" parts).
	 * @param fadeKind
	 *            Fade kind.
	 * @return <code>true</code> if the specified component is being tracked by
	 *         <code>this</code> tracker, <code>false</code> otherwise.
	 */
	public boolean isTracked(Component comp, int componentId, FadeKind fadeKind) {
		return this.isTracked(comp, new Integer(componentId), fadeKind);
	}

	/**
	 * Checks whether the specified component is being tracked by
	 * <code>this</code> tracker. Effectively returns indication whether the
	 * specified component is in fade-in or fade-out animation of the specified
	 * kind.
	 * 
	 * @param comp
	 *            Component.
	 * @param componentId
	 *            Component id. Relevant for such components as tabbed panes,
	 *            lists, tables, trees and others (where fade is performed on
	 *            component "sub" parts). May be <code>null</code>.
	 * @param fadeKind
	 *            Fade kind.
	 * @return <code>true</code> if the specified component is being tracked by
	 *         <code>this</code> tracker, <code>false</code> otherwise.
	 */
	public boolean isTracked(Component comp, Comparable<?> componentId,
			FadeKind fadeKind) {
		if (this.nothingTracked)
			return false;

		Map<ComponentId, FadeState> map = this.trackedComponents.get(fadeKind);
		if (map == null)
			return false;
		ComponentId cid = new ComponentId(comp, componentId);
		return map.containsKey(cid);
	}

	/**
	 * Checks whether the specified component is being tracked by
	 * <code>this</code> tracker. Effectively returns indication whether the
	 * specified component is in the specified animation (fade-in / fade-out) of
	 * the specified kind.
	 * 
	 * @param comp
	 *            Component.
	 * @param componentId
	 *            Component id. Relevant for such components as tabbed panes,
	 *            lists, tables, trees and others (where fade is performed on
	 *            component "sub" parts). May be <code>null</code>.
	 * @param fadeKind
	 *            Fade kind. May be <code>null</code> - not recommended.
	 * @param checkFadeIn
	 *            Specifies whether the check should be done for fade-in state.
	 *            If <code>true</code>, the returned value will be
	 *            <code>true</code> only if the component is tracked <b>and</b>
	 *            it is in the fade-in state.
	 * @return <code>true</code> if the specified component is being tracked by
	 *         <code>this</code> tracker, <code>false</code> otherwise.
	 */
	public boolean isTracked(Component comp, Comparable<?> componentId,
			FadeKind fadeKind, boolean checkFadeIn) {
		if (fadeKind == null) {
			throw new IllegalArgumentException("Can't pass null fadeKind");
		}

		if (this.nothingTracked)
			return false;

		Map<ComponentId, FadeState> map = this.trackedComponents.get(fadeKind);
		if (map == null)
			return false;

		ComponentId cid = new ComponentId(comp, componentId);
		FadeState fState = map.get(cid);
		if (fState == null)
			return false;

		return (fState.isFadingIn() == checkFadeIn);
	}

	/**
	 * Returns the fade cycle for the specified component. The result will be in
	 * 0.0-1.0 range.
	 * 
	 * @param comp
	 *            Component.
	 * @param fadeKind
	 *            Fade kind.
	 * @return The fade cycle for the specified component. For components that
	 *         are not tracked (when
	 *         {@link #isTracked(Component, org.jvnet.lafwidget.animation.FadeTracker.FadeKind)}
	 *         returns <code>false</code>), value 0 (zero) is returned.
	 */
	public synchronized float getFade(Component comp, FadeKind fadeKind) {
		return this.getFade(comp, null, fadeKind);
	}

	/**
	 * Returns the fade cycle for the specified component. The result will be in
	 * 0.0-1.0 range.
	 * 
	 * @param comp
	 *            Component.
	 * @param componentId
	 *            Component id. Relevant for such components as tabbed panes
	 *            (where fade is performed on component "sub" parts).
	 * @param fadeKind
	 *            Fade kind.
	 * @return The fade cycle for the specified component. For components that
	 *         are not tracked (when
	 *         {@link #isTracked(Component, org.jvnet.lafwidget.animation.FadeTracker.FadeKind)}
	 *         returns <code>false</code>), value 0 (zero) is returned.
	 */
	public synchronized float getFade(Component comp, int componentId,
			FadeKind fadeKind) {
		return this.getFade(comp, new Integer(componentId), fadeKind);
	}

	/**
	 * Returns the fade cycle for the specified component. The result will be in
	 * 0.0-1.0 range.
	 * 
	 * @param comp
	 *            Component.
	 * @param componentId
	 *            Component id. Relevant for such components as tabbed panes,
	 *            lists, tables, trees and others (where fade is performed on
	 *            component "sub" parts). May be <code>null</code>.
	 * @param fadeKind
	 *            Fade kind.
	 * @return The fade cycle for the specified component. For components that
	 *         are not tracked (when
	 *         {@link #isTracked(Component, org.jvnet.lafwidget.animation.FadeTracker.FadeKind)}
	 *         returns <code>false</code>), value 0 (zero) is returned.
	 */
	public synchronized float getFade(Component comp,
			Comparable<?> componentId, FadeKind fadeKind) {
		if (fadeKind == null) {
			throw new IllegalArgumentException("Can't pass null fadeKind");
		}

		if (this.nothingTracked)
			return 0;

		Map<ComponentId, FadeState> map = this.trackedComponents.get(fadeKind);
		if ((map == null) || (map.size() == 0))
			return 0;

		ComponentId cid = new ComponentId(comp, componentId);
		FadeState state = map.get(cid);
		if (state == null)
			return 0;
		return state.getFadePosition();
	}

	// public synchronized boolean isFadeIn(Component comp, FadeKind fadeKind) {
	// return this.isFadeIn(comp, null, fadeKind);
	// }
	//
	// public synchronized boolean isFadeIn(Component comp, int componentId,
	// FadeKind fadeKind) {
	// return this.isFadeIn(comp, new Integer(componentId), fadeKind);
	// }
	//
	// public synchronized boolean isFadeIn(Component comp,
	// Comparable<?> componentId, FadeKind fadeKind) {
	// if (fadeKind == null) {
	// throw new IllegalArgumentException("Can't pass null fadeKind");
	// }
	//
	// ComponentId cid = new ComponentId(comp, componentId);
	// Map<ComponentId, FadeState> map = this.trackedComponents.get(fadeKind);
	// if ((map == null) || (map.size() == 0))
	// return false;
	//
	// FadeState state = map.get(cid);
	// if (state == null)
	// return false;
	// return state.isFadingIn();
	// }
	//

	/**
	 * Returns the fade state of specified component if it's being tracked by
	 * <code>this</code> tracker or <code>null</code>.
	 * 
	 * @param comp
	 *            Component.
	 * @param fadeKind
	 *            Fade kind.
	 * @return Fade state of specified component if it's being tracked by
	 *         <code>this</code> tracker or <code>null</code>.
	 */
	public FadeState getFadeState(Component comp, FadeKind fadeKind) {
		return this.getFadeState(comp, null, fadeKind);
	}

	/**
	 * Returns the fade state of specified component if it's being tracked by
	 * <code>this</code> tracker or <code>null</code>.
	 * 
	 * @param comp
	 *            Component.
	 * @param componentId
	 *            Component id. Relevant for such components as tabbed panes
	 *            (where fade is performed on component "sub" parts).
	 * @param fadeKind
	 *            Fade kind.
	 * @return Fade state of specified component if it's being tracked by
	 *         <code>this</code> tracker or <code>null</code>.
	 */
	public FadeState getFadeState(Component comp, int componentId,
			FadeKind fadeKind) {
		return this.getFadeState(comp, new Integer(componentId), fadeKind);
	}

	/**
	 * Returns the fade state of specified component if it's being tracked by
	 * <code>this</code> tracker or <code>null</code>.
	 * 
	 * @param comp
	 *            Component.
	 * @param componentId
	 *            Component id. Relevant for such components as tabbed panes,
	 *            lists, tables, trees and others (where fade is performed on
	 *            component "sub" parts). May be <code>null</code>.
	 * @param fadeKind
	 *            Fade kind.
	 * @return Fade state of specified component if it's being tracked by
	 *         <code>this</code> tracker or <code>null</code>.
	 */
	public synchronized FadeState getFadeState(Component comp,
			Comparable<?> componentId, FadeKind fadeKind) {
		if (fadeKind == null) {
			throw new IllegalArgumentException("Can't pass null fadeKind");
		}

		if (this.nothingTracked)
			return null;

		Map<ComponentId, FadeState> map = this.trackedComponents.get(fadeKind);
		if ((map == null) || (map.size() == 0))
			return null;

		ComponentId cid = new ComponentId(comp, componentId);
		FadeState state = map.get(cid);
		if (state == null)
			return null;
		return state.getCopy();
	}

	/**
	 * Stops tracking of all components. Note that this function <b>does not</b>
	 * stop the fade thread ({@link #trackerThread}).
	 */
	public synchronized void stopAllTimers() {
		for (Iterator<Map.Entry<FadeKind, Map<ComponentId, FadeState>>> itComp = this.trackedComponents
				.entrySet().iterator(); itComp.hasNext();) {
			Map.Entry<FadeKind, Map<ComponentId, FadeState>> entryComp = itComp
					.next();
			Map<ComponentId, FadeState> mapComp = entryComp.getValue();
			for (Iterator<Map.Entry<ComponentId, FadeState>> itKind = mapComp
					.entrySet().iterator(); itKind.hasNext();) {
				Map.Entry<ComponentId, FadeState> entryKind = itKind.next();
				ComponentId compId = entryKind.getKey();
				FadeState state = entryKind.getValue();
				if (state.getCallback() != null)
					this.callbackCallFadeEnded(state.getCallback(),
							state.fadeKind);
				this.globalCallbackCallFadeEnded(compId.component,
						compId.subID, state.fadeKind);
			}
		}
		this.trackedComponents.clear();
		this.fadeInstances.clear();
	}

	/**
	 * Returns an instance of the tracker thread.
	 * 
	 * @return The tracker thread.
	 */
	private synchronized FadeTrackerThread getThread() {
		if (this.trackerThread == null) {
			this.trackerThread = new FadeTrackerThread();
			this.trackerThread.start();
		}
		return this.trackerThread;
	}

	/**
	 * Cancels the specified fade instance.
	 * 
	 * @param fadeInstanceId
	 *            Fade instance ID.
	 */
	public synchronized void cancelFadeInstance(long fadeInstanceId) {
		FadeKind fadeKind = this.fadeInstances.get(new Long(fadeInstanceId));
		if (fadeKind != null) {
			Map<ComponentId, FadeState> compFades = this.trackedComponents
					.get(fadeKind);
			if (compFades != null) {
				for (Iterator<Map.Entry<ComponentId, FadeState>> itKind = compFades
						.entrySet().iterator(); itKind.hasNext();) {
					Map.Entry<ComponentId, FadeState> entryKind = itKind.next();
					ComponentId compId = entryKind.getKey();
					FadeState state = entryKind.getValue();
					if (fadeInstanceId == state.id) {
						itKind.remove();
						if (state.getCallback() != null) {
							this.callbackCallFadeEnded(state.getCallback(),
									state.fadeKind);
						} else {
							if (state.isToRepaintParent()) {
								entryKind.getKey().component.getParent()
										.repaint();
							} else {
								entryKind.getKey().component.repaint();
							}
						}
						this.globalCallbackCallFadeEnded(compId.component,
								compId.subID, state.fadeKind);
					}
				}
			}
			// if (compFades.size() == 0) {
			// this.trackedComponents.remove(fadeKind);
			// }

			this.fadeInstances.remove(new Long(fadeInstanceId));
		}
	}

	/**
	 * Requests that the specified fade instance should stop at the end of the
	 * fade-out. This method should be called only on looping fades.
	 * 
	 * @param fadeInstanceId
	 *            Fade instance ID.
	 */
	public synchronized void requestStopOnCycleBreak(long fadeInstanceId) {
		FadeKind fadeKind = this.fadeInstances.get(fadeInstanceId);
		if (fadeKind != null) {
			Map<ComponentId, FadeState> compFades = this.trackedComponents
					.get(fadeKind);
			for (FadeState fadeState : compFades.values()) {
				if (fadeInstanceId == fadeState.id) {
					fadeState.toStopAtCycleBreak();
				}
			}
		}
	}

	/**
	 * Returns all components that are currently under the specified fade kind.
	 * 
	 * @param fadeKind
	 *            Fade kind.
	 * @return Non-<code>null</code> set of all components that are currently
	 *         under the specified fade kind.
	 */
	public synchronized Set<Component> getAllComponentsByFadeKind(
			FadeKind fadeKind) {
		Set<Component> result = new HashSet<Component>();
		Map<ComponentId, FadeState> comps = this.trackedComponents
				.get(fadeKind);
		if (comps != null) {
			for (ComponentId cid : comps.keySet()) {
				result.add(cid.component);
			}
		}
		return result;
	}

	/**
	 * Returns the ID of the current loop iteration.
	 * 
	 * @return ID of the current loop iteration.
	 */
	public long getCurrLoopId() {
		return currLoopId;
	}

	/**
	 * Registers the specified application callback for tracking core fade
	 * events.
	 * 
	 * @param callback
	 *            Application callback for tracking core fade events.
	 * @see FadeConfigurationManager#addGlobalFadeTrackerCallback(GlobalFadeTrackerCallback)
	 * @see #removeGlobalCallback(GlobalFadeTrackerCallback)
	 */
	public void addGlobalCallback(GlobalFadeTrackerCallback callback) {
		this.globalCallbackList.add(callback);
	}

	/**
	 * Removes the specified application callback for tracking core fade events.
	 * 
	 * @param callback
	 *            Application callback to remove.
	 * @see #addGlobalFadeTrackerCallback(GlobalFadeTrackerCallback)
	 */
	public void removeGlobalCallback(GlobalFadeTrackerCallback callback) {
		this.globalCallbackList.remove(callback);
	}
}
