/*
 * 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.util.*;

import org.jvnet.lafwidget.LafWidget;
import org.jvnet.lafwidget.utils.LafConstants.AnimationKind;

/**
 * Fade configuration manager.
 * 
 * @author Kirill Grouchnikov
 * @since 2.1
 */
public class FadeConfigurationManager {
	/**
	 * Singleton instance.
	 */
	private static FadeConfigurationManager instance;

	/**
	 * Contains {@link FadeKind} instances.
	 */
	private Set<FadeKind> globalAllowed;

	/**
	 * Key - {@link FadeKind}, value - set of {@link Class} instances.
	 */
	private Map<FadeKind, Set<Class<?>>> classAllowed;

	/**
	 * Key - {@link FadeKind}, value - set of {@link Class} instances.
	 */
	private Map<FadeKind, Set<Class<?>>> classDisallowed;

	/**
	 * Key - {@link FadeKind}, value - set of {@link Component} instances.
	 */
	private Map<FadeKind, Set<Component>> instanceAllowed;

	/**
	 * Key - {@link FadeKind}, value - set of {@link Component} instances.
	 */
	private Map<FadeKind, Set<Component>> instanceDisallowed;

	/**
	 * Returns the configuration manager instance.
	 * 
	 * @return Configuration manager instance.
	 */
	public static synchronized FadeConfigurationManager getInstance() {
		if (instance == null) {
			instance = new FadeConfigurationManager();
		}
		return instance;
	}

	/**
	 * Creates a new instance.
	 */
	private FadeConfigurationManager() {
		this.globalAllowed = new HashSet<FadeKind>();
		this.classAllowed = new HashMap<FadeKind, Set<Class<?>>>();
		this.classDisallowed = new HashMap<FadeKind, Set<Class<?>>>();
		this.instanceAllowed = new HashMap<FadeKind, Set<Component>>();
		this.instanceDisallowed = new HashMap<FadeKind, Set<Component>>();
	}

	/**
	 * Allows fade of the specified kind on all controls.
	 * 
	 * @param fadeKind
	 *            Fade kind to allow.
	 */
	public synchronized void allowFades(FadeKind fadeKind) {
		this.globalAllowed.add(fadeKind);
	}

	/**
	 * Allows fade of the specified kind on all controls of specified class.
	 * 
	 * @param fadeKind
	 *            Fade kind to allow.
	 * @param clazz
	 *            Control class for allowing the fade kind.
	 */
	public synchronized void allowFades(FadeKind fadeKind, Class<?> clazz) {
		Set<Class<?>> existingAllowed = this.classAllowed.get(fadeKind);
		if (existingAllowed == null) {
			existingAllowed = new HashSet<Class<?>>();
			this.classAllowed.put(fadeKind, existingAllowed);
		}
		existingAllowed.add(clazz);

		Set<Class<?>> existingDisallowed = this.classDisallowed.get(fadeKind);
		if (existingDisallowed != null) {
			existingDisallowed.remove(clazz);
		}
	}

	/**
	 * Allows fade of the specified kind on all controls of specified classes.
	 * 
	 * @param fadeKind
	 *            Fade kind to allow.
	 * @param clazz
	 *            Control classes for allowing the fade kind.
	 */
	public synchronized void allowFades(FadeKind fadeKind, Class<?>[] clazz) {
		for (int i = 0; i < clazz.length; i++) {
			allowFades(fadeKind, clazz[i]);
		}
	}

	/**
	 * Allows fade of the specified kind on the specified control.
	 * 
	 * @param fadeKind
	 *            Fade kind to allow.
	 * @param comp
	 *            Control for allowing the fade kind.
	 */
	public synchronized void allowFades(FadeKind fadeKind, Component comp) {
		Set<Component> existingAllowed = this.instanceAllowed.get(fadeKind);
		if (existingAllowed == null) {
			existingAllowed = new HashSet<Component>();
			this.instanceAllowed.put(fadeKind, existingAllowed);
		}
		existingAllowed.add(comp);

		Set<Component> existingDisallowed = this.instanceDisallowed
				.get(fadeKind);
		if (existingDisallowed != null) {
			existingDisallowed.remove(comp);
		}
	}

	/**
	 * Disallows fade of the specified kind on all controls.
	 * 
	 * @param fadeKind
	 *            Fade kind to disallow.
	 */
	public synchronized void disallowFades(FadeKind fadeKind) {
		this.globalAllowed.remove(fadeKind);
	}

	/**
	 * Disallows fade of the specified kind on all controls of specified class.
	 * 
	 * @param fadeKind
	 *            Fade kind to disallow.
	 * @param clazz
	 *            Control class for disallowing the fade kind.
	 */
	public synchronized void disallowFades(FadeKind fadeKind, Class<?> clazz) {
		Set<Class<?>> existingAllowed = this.classAllowed.get(fadeKind);
		if (existingAllowed != null) {
			existingAllowed.remove(clazz);
			if (existingAllowed.size() == 0)
				this.classAllowed.remove(fadeKind);
		}

		Set<Class<?>> existingDisallowed = this.classDisallowed.get(fadeKind);
		if (existingDisallowed == null) {
			existingDisallowed = new HashSet<Class<?>>();
			this.classDisallowed.put(fadeKind, existingDisallowed);
		}
		existingDisallowed.add(clazz);
	}

	/**
	 * Disallows fade of the specified kind on all controls of specified
	 * classes.
	 * 
	 * @param fadeKind
	 *            Fade kind to disallow.
	 * @param clazz
	 *            Control classes for disallowing the fade kind.
	 */
	public synchronized void disallowFades(FadeKind fadeKind, Class<?>[] clazz) {
		for (int i = 0; i < clazz.length; i++) {
			disallowFades(fadeKind, clazz[i]);
		}
	}

	/**
	 * Disallows fade of the specified kind on the specified control.
	 * 
	 * @param fadeKind
	 *            Fade kind to disallow.
	 * @param comp
	 *            Control for disallowing the fade kind.
	 */
	public synchronized void disallowFades(FadeKind fadeKind, Component comp) {
		Set<Component> existingAllowed = this.instanceAllowed.get(fadeKind);
		if (existingAllowed != null) {
			existingAllowed.remove(comp);
			if (existingAllowed.size() == 0)
				this.instanceAllowed.remove(fadeKind);
		}

		Set<Component> existingDisallowed = this.instanceDisallowed
				.get(fadeKind);
		if (existingDisallowed == null) {
			existingDisallowed = new HashSet<Component>();
			this.instanceDisallowed.put(fadeKind, existingDisallowed);
		}
		existingDisallowed.add(comp);
	}

	/**
	 * Checks whether the specified fade kind is allowed on the specified
	 * component.
	 * 
	 * @param fadeKind
	 *            Fade kind.
	 * @param comp
	 *            Component. Can be <code>null</code>.
	 * @return <code>true</code> if the specified fade kind is allowed on the
	 *         specified component, <code>false</code> otherwise.
	 */
	public synchronized boolean fadeAllowed(FadeKind fadeKind, Component comp) {
		Set<Component> instanceDisallowed = this.instanceDisallowed
				.get(fadeKind);
		if (instanceDisallowed != null) {
			if (instanceDisallowed.contains(comp))
				return false;
		}
		Set<Component> instanceAllowed = this.instanceAllowed.get(fadeKind);
		if (instanceAllowed != null) {
			if (instanceAllowed.contains(comp))
				return true;
		}

		if (comp != null) {
			Class<?> clazz = comp.getClass();
			Set<Class<?>> classAllowed = this.classAllowed.get(fadeKind);
			Set<Class<?>> classDisallowed = this.classDisallowed.get(fadeKind);
			if (classDisallowed != null) {
				for (Class<?> disallowed : classDisallowed) {
					if (disallowed.isAssignableFrom(clazz))
						return false;
				}
			}
			if (classAllowed != null) {
				for (Class<?> allowed : classAllowed) {
					if (allowed.isAssignableFrom(clazz))
						return true;
				}
			}
		}
		if (this.globalAllowed.contains(fadeKind))
			return true;
		return false;
	}

	/**
	 * Registers the specified application callback for tracking core fade
	 * events. This allows the applications to track different stages of
	 * animation sequences. For example, the action listener on button press is
	 * called immediately on mouse release. However, the sequence going from
	 * pressed state to rollover state takes some time (depending on the current
	 * animation speed setting controlled by {@link AnimationKind} and
	 * {@link LafWidget#ANIMATION_KIND}). An application that wishes to take a
	 * frame snapshot with the final (rollover) state of the button can register
	 * a global fade tracker callback that will take the snapshot at the end of
	 * the fade sequence.
	 * 
	 * @param callback
	 *            Application callback for tracking core fade events.
	 * @see #removeGlobalFadeTrackerCallback(GlobalFadeTrackerCallback)
	 */
	public static void addGlobalFadeTrackerCallback(
			GlobalFadeTrackerCallback callback) {
		FadeTracker.getInstance().addGlobalCallback(callback);
	}

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