/*
 * Copyright (c) 2005-2008 Substance 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 Substance 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.substance.api;

import java.awt.Component;
import java.util.EnumMap;
import java.util.Map;

import org.jvnet.lafwidget.animation.FadeKind;
import org.jvnet.substance.colorscheme.BlendBiColorScheme;
import org.jvnet.substance.painter.decoration.DecorationAreaType;
import org.jvnet.substance.utils.SubstanceCoreUtilities;

/**
 * Color scheme bundle. Defines the visual appearance of a single decoration
 * area of a skin.
 * 
 * @author Kirill Grouchnikov
 * @see DecorationAreaType
 * @see SubstanceSkin
 */
@SubstanceApi
public class SubstanceColorSchemeBundle implements Cloneable {
	/**
	 * The active color scheme of this bundle.
	 */
	protected SubstanceColorScheme activeColorScheme;

	/**
	 * The default color scheme of this bundle.
	 */
	protected SubstanceColorScheme defaultColorScheme;

	/**
	 * The disabled color scheme of this bundle.
	 */
	protected SubstanceColorScheme disabledColorScheme;

	/**
	 * Maps from component state to the matching color schemes. This map doesn't
	 * have to contain entries for all {@link ComponentState} instances.
	 */
	protected Map<ComponentState, SubstanceColorScheme> stateColorSchemeMap;

	/**
	 * Maps from component state to the alpha channel applied on color scheme.
	 * This map doesn't have to contain entries for all {@link ComponentState}
	 * instances.
	 */
	protected Map<ComponentState, Float> stateAlphaMap;

	/**
	 * Maps from component state to the matching color schemes for highlight
	 * color schemes. This map doesn't have to contain entries for all {@link
	 * ComponentState} instances.
	 */
	protected Map<ComponentState, SubstanceColorScheme> stateHighlightColorSchemeMap;

	/**
	 * Maps from component state to the alpha channel applied on highlight color
	 * scheme. This map doesn't have to contain entries for all {@link
	 * ComponentState} instances.
	 */
	protected Map<ComponentState, Float> stateHighlightSchemeAlphaMap;

	/**
	 * Maps from component state to the matching color schemes for border color
	 * schemes. This map doesn't have to contain entries for all {@link
	 * ComponentState} instances.
	 */
	protected Map<ComponentState, SubstanceColorScheme> stateBorderColorSchemeMap;

	/**
	 * If there is no explicitly registered color scheme for pressed component
	 * states, this field will contain a synthesized color scheme for a pressed
	 * state.
	 */
	protected SubstanceColorScheme pressedScheme;

	/**
	 * If there is no explicitly registered color scheme for the disabled
	 * selected component states, this field will contain a synthesized color
	 * scheme for the disabled selected state.
	 */
	protected SubstanceColorScheme disabledSelectedScheme;

	/**
	 * Creates a new color scheme bundle.
	 * 
	 * @param activeColorScheme
	 * 		The active color scheme of this bundle.
	 * @param defaultColorScheme
	 * 		The default color scheme of this bundle.
	 * @param disabledColorScheme
	 * 		The disabled color scheme of this bundle.
	 */
	public SubstanceColorSchemeBundle(SubstanceColorScheme activeColorScheme,
			SubstanceColorScheme defaultColorScheme,
			SubstanceColorScheme disabledColorScheme) {
		this.activeColorScheme = activeColorScheme;
		this.defaultColorScheme = defaultColorScheme;
		this.disabledColorScheme = disabledColorScheme;
		this.stateColorSchemeMap = new EnumMap<ComponentState, SubstanceColorScheme>(
				ComponentState.class);
		this.stateAlphaMap = new EnumMap<ComponentState, Float>(
				ComponentState.class);
		this.stateHighlightColorSchemeMap = new EnumMap<ComponentState, SubstanceColorScheme>(
				ComponentState.class);
		this.stateHighlightSchemeAlphaMap = new EnumMap<ComponentState, Float>(
				ComponentState.class);
		this.stateBorderColorSchemeMap = new EnumMap<ComponentState, SubstanceColorScheme>(
				ComponentState.class);
	}

	/**
	 * Registers a color scheme for the specific component state.
	 * 
	 * @param stateColorScheme
	 * 		Color scheme for the specified component state.
	 * @param alpha
	 * 		Alpha channel for the color scheme.
	 * @param states
	 * 		Component states.
	 */
	public void registerColorScheme(SubstanceColorScheme stateColorScheme,
			float alpha, ComponentState... states) {
		if (states != null) {
			for (ComponentState state : states) {
				this.stateColorSchemeMap.put(state, stateColorScheme);
				this.stateAlphaMap.put(state, alpha);
			}
		}
	}

	/**
	 * Registers a color scheme for the specific component state.
	 * 
	 * @param stateColorScheme
	 * 		Color scheme for the specified component state.
	 * @param states
	 * 		Component states.
	 */
	public void registerColorScheme(SubstanceColorScheme stateColorScheme,
			ComponentState... states) {
		this.registerColorScheme(stateColorScheme, 1.0f, states);
	}

	/**
	 * Registers a highlight color scheme for the specific component state if
	 * the component state is not <code>null</code>, or a global highlight color
	 * scheme otherwise.
	 * 
	 * @param stateHighlightScheme
	 * 		Highlight color scheme for the specified component state.
	 * @param states
	 * 		Component states. If <code>null</code>, the specified color scheme
	 * 		will be applied for all states left unspecified.
	 */
	public void registerHighlightColorScheme(
			SubstanceColorScheme stateHighlightScheme, ComponentState... states) {
		if ((states == null) || (states.length == 0)) {
			for (ComponentState state : ComponentState.values()) {
				if (this.stateHighlightColorSchemeMap.containsKey(state))
					continue;
				if (!state.isKindActive(FadeKind.ENABLE))
					continue;
				if (state == ComponentState.DEFAULT)
					continue;
				this.stateHighlightColorSchemeMap.put(state,
						stateHighlightScheme);
			}
		} else {
			for (ComponentState state : states) {
				this.stateHighlightColorSchemeMap.put(state,
						stateHighlightScheme);
			}
		}
	}

	/**
	 * Registers a highlight color scheme for the specific component state if
	 * the component state is not <code>null</code>, or a global highlight color
	 * scheme otherwise.
	 * 
	 * @param stateHighlightScheme
	 * 		Highlight color scheme for the specified component state.
	 * @param alpha
	 * 		Alpha channel for the highlight color scheme.
	 * @param states
	 * 		Component states. If <code>null</code>, the specified color scheme
	 * 		will be applied for all states left unspecified.
	 */
	public void registerHighlightColorScheme(
			SubstanceColorScheme stateHighlightScheme, float alpha,
			ComponentState... states) {
		if ((states == null) || (states.length == 0)) {
			for (ComponentState state : ComponentState.values()) {
				if (!state.isKindActive(FadeKind.ENABLE))
					continue;
				if (state == ComponentState.DEFAULT)
					continue;
				if (!this.stateHighlightColorSchemeMap.containsKey(state))
					this.stateHighlightColorSchemeMap.put(state,
							stateHighlightScheme);
				if (!this.stateHighlightSchemeAlphaMap.containsKey(state))
					this.stateHighlightSchemeAlphaMap.put(state, alpha);
			}
		} else {
			for (ComponentState state : states) {
				this.stateHighlightColorSchemeMap.put(state,
						stateHighlightScheme);
				this.stateHighlightSchemeAlphaMap.put(state, alpha);
			}
		}
	}

	/**
	 * Returns the color scheme of the specified component in the specified
	 * component state.
	 * 
	 * @param comp
	 * 		Component.
	 * @param componentState
	 * 		Component state.
	 * @param toIgnoreHighlights
	 * 		If <code>true</code>, the highlight scheme map will not be
	 * 		consulted.
	 * @return The color scheme of the component in the specified component
	 * 	state.
	 */
	public SubstanceColorScheme getColorScheme(Component comp,
			ComponentState componentState, boolean toIgnoreHighlights) {
		if (!toIgnoreHighlights
				&& SubstanceCoreUtilities.toUseHighlightColorScheme(comp)) {
			SubstanceColorScheme registered = this.stateHighlightColorSchemeMap
					.get(componentState);
			if (registered != null)
				return registered;
		} else {
			SubstanceColorScheme registered = this.stateColorSchemeMap
					.get(componentState);
			if (registered != null)
				return registered;

			if (componentState.isKindActive(FadeKind.PRESS)) {
				if (this.pressedScheme == null) {
					this.pressedScheme = this.activeColorScheme.shade(0.2)
							.saturate(0.1);
				}
				return this.pressedScheme;
			}
			if (componentState == ComponentState.DISABLED_SELECTED) {
				if (this.disabledSelectedScheme == null) {
					this.disabledSelectedScheme = new BlendBiColorScheme(
							this.activeColorScheme, this.disabledColorScheme,
							0.25);
				}
				return this.disabledSelectedScheme;
			}

			if (componentState == ComponentState.DEFAULT)
				return this.defaultColorScheme;
			if (!componentState.isKindActive(FadeKind.ENABLE))
				return this.disabledColorScheme;
			return this.activeColorScheme;
		}
		return null;
	}

	/**
	 * Returns the highlight color scheme of the component.
	 * 
	 * @param comp
	 * 		Component.
	 * @param componentState
	 * 		Component state.
	 * @return Component highlight color scheme.
	 */
	public SubstanceColorScheme getHighlightColorScheme(Component comp,
			ComponentState componentState) {
		SubstanceColorScheme registered = this.stateHighlightColorSchemeMap
				.get(componentState);
		if (registered != null)
			return registered;

		return null;
	}

	/**
	 * Returns the alpha channel of the highlight color scheme of the component.
	 * 
	 * @param comp
	 * 		Component.
	 * @param componentState
	 * 		Component state.
	 * @return Highlight color scheme alpha channel.
	 */
	public float getHighlightAlpha(Component comp, ComponentState componentState) {
		Float registered = this.stateHighlightSchemeAlphaMap
				.get(componentState);
		if (registered != null)
			return registered.floatValue();

		return -1.0f;
	}

	/**
	 * Returns the alpha channel of the color scheme of the component.
	 * 
	 * @param comp
	 * 		Component.
	 * @param componentState
	 * 		Component state.
	 * @return Color scheme alpha channel.
	 */
	public float getAlpha(Component comp, ComponentState componentState) {
		Float registered = this.stateAlphaMap.get(componentState);
		if (registered != null)
			return registered.floatValue();

		return -1.0f;
	}

	/**
	 * Returns the active color scheme of this bundle.
	 * 
	 * @return The active color scheme of this bundle.
	 */
	public SubstanceColorScheme getActiveColorScheme() {
		return activeColorScheme;
	}

	/**
	 * Returns the default color scheme of this bundle.
	 * 
	 * @return The default color scheme of this bundle.
	 */
	public SubstanceColorScheme getDefaultColorScheme() {
		return defaultColorScheme;
	}

	/**
	 * Returns the disabled color scheme of this bundle.
	 * 
	 * @return The disabled color scheme of this bundle.
	 */
	public SubstanceColorScheme getDisabledColorScheme() {
		return disabledColorScheme;
	}

	/**
	 * Returns the color scheme for painting component border in the specified
	 * state.
	 * 
	 * @param comp
	 * 		Component.
	 * @param componentState
	 * 		Component state.
	 * @return Color scheme for painting component border in the specified
	 * 	state.
	 */
	public SubstanceColorScheme getBorderColorScheme(Component comp,
			ComponentState componentState) {
		SubstanceColorScheme registered = this.stateBorderColorSchemeMap
				.get(componentState);
		if (registered != null)
			return registered;

		return this.getColorScheme(comp, componentState, true);
	}

	/**
	 * Registers a border color scheme for the specific component state if the
	 * component state is not <code>null</code>, or a global border color scheme
	 * otherwise.
	 * 
	 * @param stateBorderScheme
	 * 		Border color scheme for the specified component states.
	 * @param states
	 * 		Component states. If <code>null</code>, the specified color scheme
	 * 		will be applied for all states left unspecified.
	 */
	public void registerBorderColorScheme(
			SubstanceColorScheme stateBorderScheme, ComponentState... states) {
		if ((states == null) || (states.length == 0)) {
			for (ComponentState state : ComponentState.values()) {
				if (this.stateBorderColorSchemeMap.containsKey(state))
					continue;
				this.stateBorderColorSchemeMap.put(state, stateBorderScheme);
			}
		} else {
			for (ComponentState state : states) {
				this.stateBorderColorSchemeMap.put(state, stateBorderScheme);
			}
		}
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		SubstanceColorSchemeBundle clone = new SubstanceColorSchemeBundle(
				this.activeColorScheme, this.defaultColorScheme,
				this.disabledColorScheme);

		if (this.stateColorSchemeMap != null) {
			clone.stateColorSchemeMap = new EnumMap<ComponentState, SubstanceColorScheme>(
					this.stateColorSchemeMap);
		}
		if (this.stateAlphaMap != null) {
			clone.stateAlphaMap = new EnumMap<ComponentState, Float>(
					this.stateAlphaMap);
		}
		if (this.stateBorderColorSchemeMap != null) {
			clone.stateBorderColorSchemeMap = new EnumMap<ComponentState, SubstanceColorScheme>(
					this.stateBorderColorSchemeMap);
		}
		if (this.stateHighlightColorSchemeMap != null) {
			clone.stateHighlightColorSchemeMap = new EnumMap<ComponentState, SubstanceColorScheme>(
					this.stateHighlightColorSchemeMap);
		}
		if (this.stateHighlightSchemeAlphaMap != null) {
			clone.stateHighlightSchemeAlphaMap = new EnumMap<ComponentState, Float>(
					this.stateHighlightSchemeAlphaMap);
		}
		return clone;
	}

	/**
	 * Creates a new color scheme bundle that has the same settings as this
	 * color scheme bundle with the addition of applying the specified color
	 * scheme transformation on all the relevant color schemes
	 * 
	 * @param transform
	 * 		Color scheme transformation.
	 * @return The new color scheme bundle.
	 */
	public SubstanceColorSchemeBundle transform(ColorSchemeTransform transform) {
		// transform the basic schemes
		SubstanceColorSchemeBundle result = new SubstanceColorSchemeBundle(
				transform.transform(this.activeColorScheme), transform
						.transform(this.defaultColorScheme), transform
						.transform(this.disabledColorScheme));

		// transform the state-specific schemes
		if (this.stateColorSchemeMap != null) {
			result.stateColorSchemeMap = new EnumMap<ComponentState, SubstanceColorScheme>(
					ComponentState.class);
			for (Map.Entry<ComponentState, SubstanceColorScheme> entry : this.stateColorSchemeMap
					.entrySet()) {
				result.stateColorSchemeMap.put(entry.getKey(), transform
						.transform(entry.getValue()));
			}
		}

		// alphas are the same
		if (this.stateAlphaMap != null) {
			result.stateAlphaMap = new EnumMap<ComponentState, Float>(
					this.stateAlphaMap);
		}

		// transform the state-specific border schemes
		if (this.stateBorderColorSchemeMap != null) {
			result.stateBorderColorSchemeMap = new EnumMap<ComponentState, SubstanceColorScheme>(
					ComponentState.class);
			for (Map.Entry<ComponentState, SubstanceColorScheme> entry : this.stateBorderColorSchemeMap
					.entrySet()) {
				result.stateBorderColorSchemeMap.put(entry.getKey(), transform
						.transform(entry.getValue()));
			}
		}

		// transform the state-specific highlight schemes
		if (this.stateHighlightColorSchemeMap != null) {
			result.stateHighlightColorSchemeMap = new EnumMap<ComponentState, SubstanceColorScheme>(
					ComponentState.class);
			for (Map.Entry<ComponentState, SubstanceColorScheme> entry : this.stateHighlightColorSchemeMap
					.entrySet()) {
				result.stateHighlightColorSchemeMap.put(entry.getKey(),
						transform.transform(entry.getValue()));
			}
		}

		// highlight alphas are the same
		if (this.stateHighlightSchemeAlphaMap != null) {
			result.stateHighlightSchemeAlphaMap = new EnumMap<ComponentState, Float>(
					this.stateHighlightSchemeAlphaMap);
		}
		return result;
	}
}
