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

import java.awt.Color;
import java.awt.Component;

import javax.swing.AbstractButton;
import javax.swing.JTabbedPane;
import javax.swing.plaf.UIResource;

import org.jvnet.lafwidget.animation.FadeKind;
import org.jvnet.substance.api.*;
import org.jvnet.substance.colorscheme.*;
import org.jvnet.substance.painter.decoration.DecorationAreaType;
import org.jvnet.substance.utils.combo.SubstanceComboBoxButton;
import org.jvnet.substance.utils.scroll.SubstanceScrollButton;

/**
 * Utilities related to color schemes. This class is for internal use only.
 * 
 * @author Kirill Grouchnikov
 */
public class SubstanceColorSchemeUtilities {
	/**
	 * Metallic skin.
	 */
	public static final SubstanceSkin METALLIC_SKIN = getMetallicSkin();

	/**
	 * Returns a metallic skin.
	 * 
	 * @return Metallic skin.
	 */
	private static SubstanceSkin getMetallicSkin() {
		SubstanceSkin res = new SubstanceSkin() {
			@Override
			public String getDisplayName() {
				return "Metallic Skin";
			}
		};
		res.registerDecorationAreaSchemeBundle(new SubstanceColorSchemeBundle(
				new MetallicColorScheme(), new MetallicColorScheme(),
				new LightGrayColorScheme()), DecorationAreaType.NONE);
		return res;
	}

	/**
	 * Base interface for colorization support.
	 * 
	 * @author Kirill Grouchnikov
	 */
	private static interface ColorizationSupport {
		/**
		 * Returns the background color of the specified component.
		 * 
		 * @param component
		 *            Component.
		 * @return The background color of the specified component.
		 */
		public Color getBackground(Component component);

		/**
		 * Returns the foreground color of the specified component.
		 * 
		 * @param component
		 *            Component.
		 * @return The foreground color of the specified component.
		 */
		public Color getForeground(Component component);
	}

	/**
	 * Returns a colorized version of the specified color scheme.
	 * 
	 * @param component
	 *            Component.
	 * @param scheme
	 *            Color scheme.
	 * @param isEnabled
	 *            Indicates whether the component is enabled.
	 * @return Colorized version of the specified color scheme.
	 */
	private static SubstanceColorScheme getColorizedScheme(Component component,
			SubstanceColorScheme scheme, boolean isEnabled) {
		ColorizationSupport support = new ColorizationSupport() {
			private boolean toTakeFromParent(Component component) {
				return (component.getParent() != null)
						&& ((component instanceof SubstanceScrollButton)
								|| (component instanceof SubstanceSpinnerButton)
								|| (component instanceof SubstanceComboBoxButton) || (component instanceof SubstanceTitleButton));
			}

			public Color getBackground(Component component) {
				return toTakeFromParent(component) ? component.getParent()
						.getBackground() : component.getBackground();
			}

			public Color getForeground(Component component) {
				return toTakeFromParent(component) ? component.getParent()
						.getForeground() : component.getForeground();
			}
		};
		return getColorizedScheme(component, scheme, support, isEnabled);
	}

	/**
	 * Returns a colorized version of the specified color scheme.
	 * 
	 * @param component
	 *            Component.
	 * @param scheme
	 *            Color scheme.
	 * @param support
	 *            Used to compute the colorized scheme.
	 * @param isEnabled
	 *            Indicates whether the component is enabled.
	 * @return Colorized version of the specified color scheme.
	 */
	private static SubstanceColorScheme getColorizedScheme(Component component,
			SubstanceColorScheme scheme, ColorizationSupport support,
			boolean isEnabled) {
		if (component != null) {
			// Support for enhancement 256 - colorizing
			// controls.
			Color bk = support.getBackground(component);
			Color fg = support.getForeground(component);
			// if (component instanceof SubstanceTitleButton) {
			// if ((fg != null) && (bk != null)) {
			// // guard for issue 322 - these are null when JavaHelp
			// // window is printed.
			// fg = SubstanceColorUtilities.getInterpolatedColor(fg, bk,
			// 0.5);
			// }
			// }
			if (bk instanceof UIResource)
				bk = null;
			if (fg instanceof UIResource) {
				fg = null;
			}
			if ((bk != null) || (fg != null)) {
				double colorization = SubstanceCoreUtilities
						.getColorizationFactor(component);
				if (!isEnabled)
					colorization /= 2.0;
				if (colorization > 0.0) {
					return ShiftColorScheme.getShiftedScheme(scheme, bk,
							colorization, fg, colorization);
				}
			}
		}
		return scheme;
	}

	/**
	 * Returns the color scheme of the specified tabbed pane tab.
	 * 
	 * @param jtp
	 *            Tabbed pane.
	 * @param tabIndex
	 *            Tab index.
	 * @param componentState
	 *            Tab component state.
	 * @return The color scheme of the specified tabbed pane tab.
	 */
	public static SubstanceColorScheme getColorScheme(final JTabbedPane jtp,
			final int tabIndex, ComponentState componentState) {
		SubstanceSkin skin = SubstanceCoreUtilities.getSkin(jtp);
		if (skin == null) {
			throw new IllegalStateException(
					"Substance delegate used when Substance is not the current LAF");
		}
		SubstanceColorScheme nonColorized = skin.getColorScheme(jtp,
				componentState);
		if (tabIndex >= 0) {
			Component component = jtp.getComponentAt(tabIndex);
			SubstanceColorScheme colorized = getColorizedScheme(component,
					nonColorized, new ColorizationSupport() {
						public Color getBackground(Component component) {
							return jtp.getBackgroundAt(tabIndex);
						}

						public Color getForeground(Component component) {
							return jtp.getForegroundAt(tabIndex);
						}
					}, componentState.isKindActive(FadeKind.ENABLE));
			return colorized;
		} else {
			return getColorizedScheme(jtp, nonColorized, componentState
					.isKindActive(FadeKind.ENABLE));
		}
	}

	/**
	 * Returns the border color scheme of the specified tabbed pane tab.
	 * 
	 * @param jtp
	 *            Tabbed pane.
	 * @param tabIndex
	 *            Tab index.
	 * @param componentState
	 *            Tab component state.
	 * @return The border color scheme of the specified tabbed pane tab.
	 */
	public static SubstanceColorScheme getBorderColorScheme(
			final JTabbedPane jtp, final int tabIndex,
			ComponentState componentState) {

		SubstanceColorScheme nonColorized = SubstanceCoreUtilities.getSkin(jtp)
				.getBorderColorScheme(jtp, componentState);
		if (tabIndex >= 0) {
			Component component = jtp.getComponentAt(tabIndex);
			SubstanceColorScheme colorized = getColorizedScheme(component,
					nonColorized, new ColorizationSupport() {
						public Color getBackground(Component component) {
							return jtp.getBackgroundAt(tabIndex);
						}

						public Color getForeground(Component component) {
							return jtp.getForegroundAt(tabIndex);
						}
					}, componentState.isKindActive(FadeKind.ENABLE));
			return colorized;
		} else {
			return getColorizedScheme(jtp, nonColorized, componentState
					.isKindActive(FadeKind.ENABLE));
		}
	}

	/**
	 * Returns the color scheme of the specified component.
	 * 
	 * @param component
	 *            Component.
	 * @param componentState
	 *            Component state.
	 * @return Component color scheme.
	 */
	public static SubstanceColorScheme getColorScheme(Component component,
			ComponentState componentState) {
		return getColorScheme(component, componentState, false);
	}

	/**
	 * Returns the color scheme of the specified component.
	 * 
	 * @param component
	 *            Component.
	 * @param componentState
	 *            Component state.
	 * @param toIgnoreHighlights
	 *            If <code>true</code>, the highlight scheme map will not be
	 *            consulted.
	 * @return Component color scheme.
	 */
	public static SubstanceColorScheme getColorScheme(Component component,
			ComponentState componentState, boolean toIgnoreHighlights) {
		Component orig = component;
		// special case - if the component is marked as flat and
		// it is in the default state, or it is a button
		// that is never painting its background - get the theme of the parent
		boolean isButtonThatIsNeverPainted = ((component instanceof AbstractButton) && SubstanceCoreUtilities
				.isButtonNeverPainted((AbstractButton) component));
		if (isButtonThatIsNeverPainted
				|| (SubstanceCoreUtilities.hasFlatAppearance(component, false) && (componentState == ComponentState.DEFAULT))) {
			component = component.getParent();
		}

		SubstanceSkin skin = SubstanceCoreUtilities.getSkin(component);
		if (skin == null) {
			throw new IllegalStateException(
					"Substance delegate used when Substance is not the current LAF");
		}
		SubstanceColorScheme nonColorized = skin.getColorScheme(component,
				componentState, toIgnoreHighlights);
		// ColorizationSupport support = new ColorizationSupport() {
		// private boolean toTakeFromParent(Component component) {
		// return (component.getParent() != null)
		// && ((component instanceof SubstanceScrollButton)
		// || (component instanceof SubstanceSpinnerButton)
		// || (component instanceof SubstanceComboBoxButton) || (component
		// instanceof SubstanceTitleButton));
		// }
		//
		// public Color getBackground(Component component) {
		// return toTakeFromParent(component) ? component.getParent()
		// .getBackground() : component.getBackground();
		// }
		//
		// public Color getForeground(Component component) {
		// return toTakeFromParent(component) ? component.getParent()
		// .getForeground() : component.getForeground();
		// }
		// };

		return getColorizedScheme(orig, nonColorized, componentState
				.isKindActive(FadeKind.ENABLE));
	}

	/**
	 * Returns the border color scheme of the component.
	 * 
	 * @param component
	 *            Component.
	 * @param componentState
	 *            Component state.
	 * @return Component border color scheme.
	 */
	public static SubstanceColorScheme getBorderColorScheme(
			Component component, ComponentState componentState) {
		// special case - if the component is marked as flat and
		// it is in the default state, get the theme of the parent
		if (SubstanceCoreUtilities.hasFlatAppearance(component, false)
				&& (componentState == ComponentState.DEFAULT)) {
			component = component.getParent();
		}

		SubstanceColorScheme nonColorized = SubstanceCoreUtilities.getSkin(
				component).getBorderColorScheme(component, componentState);
		return getColorizedScheme(component, nonColorized, componentState
				.isKindActive(FadeKind.ENABLE));
	}

	/**
	 * Returns the highlight color scheme of the component.
	 * 
	 * @param component
	 *            Component.
	 * @param componentState
	 *            Component state.
	 * @return Component highlight color scheme.
	 */
	public static SubstanceColorScheme getHighlightColorScheme(
			Component component, ComponentState componentState) {
		return SubstanceCoreUtilities.getSkin(component)
				.getHighlightColorScheme(component, componentState);
	}

	/**
	 * Returns the alpha channel of the highlight color scheme of the component.
	 * 
	 * @param component
	 *            Component.
	 * @param componentState
	 *            Component state.
	 * @return Highlight color scheme alpha channel.
	 */
	public static float getHighlightAlpha(Component component,
			ComponentState componentState) {
		return SubstanceCoreUtilities.getSkin(component).getHighlightAlpha(
				component, componentState);
	}

	/**
	 * Returns the alpha channel of the color scheme of the component.
	 * 
	 * @param component
	 *            Component.
	 * @param componentState
	 *            Component state.
	 * @return Color scheme alpha channel.
	 */
	public static float getAlpha(Component component,
			ComponentState componentState) {
		return SubstanceCoreUtilities.getSkin(component).getAlpha(component,
				componentState);
	}

	/**
	 * Used as reference in attention-drawing animations. This field is <b>for
	 * internal use only</b>.
	 */
	public final static SubstanceColorScheme YELLOW = new SunGlareColorScheme();

	/**
	 * Used as reference in attention-drawing animations. This field is <b>for
	 * internal use only</b>.
	 */
	public final static SubstanceColorScheme ORANGE = new SunfireRedColorScheme();

	/**
	 * Used as reference to the green color scheme. This field is <b>for
	 * internal use only</b>.
	 */
	public final static SubstanceColorScheme GREEN = new BottleGreenColorScheme();
}
