/*
 * Copyright (c) 2005-2010 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.pushingpixels.substance.internal.utils;

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.*;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicGraphicsUtils;
import javax.swing.text.JTextComponent;

import org.pushingpixels.lafwidget.LafWidgetUtilities;
import org.pushingpixels.lafwidget.text.LockBorder;
import org.pushingpixels.lafwidget.utils.RenderingUtils;
import org.pushingpixels.substance.api.*;
import org.pushingpixels.substance.api.watermark.SubstanceWatermark;
import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
import org.pushingpixels.substance.internal.utils.border.SubstanceTextComponentBorder;

/**
 * Text-related utilities. This class if for internal use only.
 * 
 * @author Kirill Grouchnikov
 */
public class SubstanceTextUtilities {
	public static final String ENFORCE_FG_COLOR = "substancelaf.internal.textUtilities.enforceFgColor";

	/**
	 * Paints text with drop shadow.
	 * 
	 * @param c
	 *            Component.
	 * @param g
	 *            Graphics context.
	 * @param foregroundColor
	 *            Foreground color.
	 * @param text
	 *            Text to paint.
	 * @param width
	 *            Text rectangle width.
	 * @param height
	 *            Text rectangle height.
	 * @param xOffset
	 *            Text rectangle X offset.
	 * @param yOffset
	 *            Text rectangle Y offset.
	 */
	public static void paintTextWithDropShadow(JComponent c, Graphics g,
			Color foregroundColor, String text, int width, int height,
			int xOffset, int yOffset) {
		Graphics2D graphics = (Graphics2D) g.create();
		RenderingUtils.installDesktopHints(graphics, c);

		// blur the text shadow
		BufferedImage blurred = SubstanceCoreUtilities.getBlankImage(width,
				height);
		Graphics2D gBlurred = (Graphics2D) blurred.getGraphics();
		gBlurred.setFont(graphics.getFont());
		gBlurred.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
				RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
		// Color neg =
		// SubstanceColorUtilities.getNegativeColor(foregroundColor);
		float luminFactor = SubstanceColorUtilities
				.getColorStrength(foregroundColor);
		gBlurred.setColor(SubstanceColorUtilities
				.getNegativeColor(foregroundColor));
		ConvolveOp convolve = new ConvolveOp(new Kernel(3, 3, new float[] {
				.02f, .05f, .02f, .05f, .02f, .05f, .02f, .05f, .02f }),
				ConvolveOp.EDGE_NO_OP, null);
		gBlurred.drawString(text, xOffset, yOffset - 1);
		blurred = convolve.filter(blurred, null);

		graphics.setComposite(LafWidgetUtilities.getAlphaComposite(c,
				luminFactor, g));
		graphics.drawImage(blurred, 0, 0, null);
		graphics.setComposite(LafWidgetUtilities.getAlphaComposite(c, g));

		FontMetrics fm = graphics.getFontMetrics();
		SubstanceTextUtilities.paintText(graphics, c, new Rectangle(xOffset,
				yOffset - fm.getAscent(), width - xOffset, fm.getHeight()),
				text, -1, graphics.getFont(), foregroundColor, graphics
						.getClipBounds());

		graphics.dispose();
	}

	/**
	 * Paints the specified text.
	 * 
	 * @param g
	 *            Graphics context.
	 * @param comp
	 *            Component.
	 * @param textRect
	 *            Text rectangle.
	 * @param text
	 *            Text to paint.
	 * @param mnemonicIndex
	 *            Mnemonic index.
	 * @param font
	 *            Font to use.
	 * @param color
	 *            Color to use.
	 * @param clip
	 *            Optional clip. Can be <code>null</code>.
	 * @param transform
	 *            Optional transform to apply. Can be <code>null</code>.
	 */
	private static void paintText(Graphics g, JComponent comp,
			Rectangle textRect, String text, int mnemonicIndex,
			java.awt.Font font, java.awt.Color color, java.awt.Rectangle clip,
			java.awt.geom.AffineTransform transform) {
		if ((text == null) || (text.length() == 0))
			return;

		Graphics2D g2d = (Graphics2D) g.create();
		// workaroundBug6576507(g2d);
		// RenderingUtils.installDesktopHints(g2d);

		g2d.setFont(font);
		g2d.setColor(color);
		// fix for issue 420 - call clip() instead of setClip() to
		// respect the currently set clip shape
		if (clip != null)
			g2d.clip(clip);
		if (transform != null)
			g2d.transform(transform);
		BasicGraphicsUtils.drawStringUnderlineCharAt(g2d, text, mnemonicIndex,
				textRect.x, textRect.y + g2d.getFontMetrics().getAscent());
		g2d.dispose();
	}

	/**
	 * Paints the specified text.
	 * 
	 * @param g
	 *            Graphics context.
	 * @param comp
	 *            Component.
	 * @param textRect
	 *            Text rectangle.
	 * @param text
	 *            Text to paint.
	 * @param mnemonicIndex
	 *            Mnemonic index.
	 * @param font
	 *            Font to use.
	 * @param color
	 *            Color to use.
	 * @param clip
	 *            Optional clip. Can be <code>null</code>.
	 */
	public static void paintText(Graphics g, JComponent comp,
			Rectangle textRect, String text, int mnemonicIndex,
			java.awt.Font font, java.awt.Color color, java.awt.Rectangle clip) {
		SubstanceTextUtilities.paintText(g, comp, textRect, text,
				mnemonicIndex, font, color, clip, null);
	}

	/**
	 * Paints the specified vertical text.
	 * 
	 * @param g
	 *            Graphics context.
	 * @param comp
	 *            Component.
	 * @param textRect
	 *            Text rectangle.
	 * @param text
	 *            Text to paint.
	 * @param mnemonicIndex
	 *            Mnemonic index.
	 * @param font
	 *            Font to use.
	 * @param color
	 *            Color to use.
	 * @param clip
	 *            Optional clip. Can be <code>null</code>.
	 * @param isFromBottomToTop
	 *            If <code>true</code>, the text will be painted from bottom to
	 *            top, otherwise the text will be painted from top to bottom.
	 */
	public static void paintVerticalText(Graphics g, JComponent comp,
			Rectangle textRect, String text, int mnemonicIndex,
			java.awt.Font font, java.awt.Color color, java.awt.Rectangle clip,
			boolean isFromBottomToTop) {
		if ((text == null) || (text.length() == 0))
			return;

		AffineTransform at;

		if (!isFromBottomToTop) {
			at = AffineTransform.getTranslateInstance(textRect.x
					+ textRect.width, textRect.y);
			at.rotate(Math.PI / 2);
		} else {
			at = AffineTransform.getTranslateInstance(textRect.x, textRect.y
					+ textRect.height);
			at.rotate(-Math.PI / 2);
		}
		Rectangle newRect = new Rectangle(0, 0, textRect.width, textRect.height);

		SubstanceTextUtilities.paintText(g, comp, newRect, text, mnemonicIndex,
				font, color, clip, at);
	}

	/**
	 * Paints the text of the specified button.
	 * 
	 * @param g
	 *            Graphic context.
	 * @param button
	 *            Button
	 * @param textRect
	 *            Text rectangle
	 * @param text
	 *            Text to paint
	 * @param mnemonicIndex
	 *            Mnemonic index.
	 */
	public static void paintText(Graphics g, AbstractButton button,
			Rectangle textRect, String text, int mnemonicIndex) {
		paintText(g, button, button.getModel(), textRect, text, mnemonicIndex);
	}

	/**
	 * Paints the text of the specified button.
	 * 
	 * @param g
	 *            Graphic context.
	 * @param button
	 *            Button
	 * @param model
	 *            Button model.
	 * @param textRect
	 *            Text rectangle
	 * @param text
	 *            Text to paint
	 * @param mnemonicIndex
	 *            Mnemonic index.
	 */
	public static void paintText(Graphics g, AbstractButton button,
			ButtonModel model, Rectangle textRect, String text,
			int mnemonicIndex) {
		TransitionAwareUI transitionAwareUI = (TransitionAwareUI) button
				.getUI();
		StateTransitionTracker stateTransitionTracker = transitionAwareUI
				.getTransitionTracker();

		float buttonAlpha = SubstanceColorSchemeUtilities.getAlpha(button,
				ComponentState.getState(button));

		if (button instanceof JMenuItem) {
			paintMenuItemText(g, (JMenuItem) button, textRect, text,
					mnemonicIndex, stateTransitionTracker.getModelStateInfo(),
					buttonAlpha);
		} else {
			paintText(g, button, textRect, text, mnemonicIndex,
					stateTransitionTracker.getModelStateInfo(), buttonAlpha);
		}
	}

	/**
	 * Paints the specified text.
	 * 
	 * @param g
	 *            Graphics context.
	 * @param component
	 *            Component.
	 * @param textRect
	 *            Text rectangle.
	 * @param text
	 *            Text to paint.
	 * @param mnemonicIndex
	 *            Mnemonic index.
	 * @param state
	 *            Component state.
	 * @param textAlpha
	 *            Alpha channel for painting the text.
	 */
	public static void paintText(Graphics g, JComponent component,
			Rectangle textRect, String text, int mnemonicIndex,
			ComponentState state, float textAlpha) {
		Color fgColor = getForegroundColor(component, text, state, textAlpha);

		SubstanceTextUtilities.paintText(g, component, textRect, text,
				mnemonicIndex, component.getFont(), fgColor, null);
	}

	public static void paintText(Graphics g, JComponent component,
			Rectangle textRect, String text, int mnemonicIndex,
			StateTransitionTracker.ModelStateInfo modelStateInfo,
			float textAlpha) {
		Color fgColor = getForegroundColor(component, text, modelStateInfo,
				textAlpha);

		SubstanceTextUtilities.paintText(g, component, textRect, text,
				mnemonicIndex, component.getFont(), fgColor, null);
	}

	public static void paintMenuItemText(Graphics g, JMenuItem menuItem,
			Rectangle textRect, String text, int mnemonicIndex,
			StateTransitionTracker.ModelStateInfo modelStateInfo,
			float textAlpha) {
		Color fgColor = getMenuComponentForegroundColor(menuItem, text,
				modelStateInfo, textAlpha);

		SubstanceTextUtilities.paintText(g, menuItem, textRect, text,
				mnemonicIndex, menuItem.getFont(), fgColor, null);
	}

	/**
	 * Returns the foreground color for the specified component.
	 * 
	 * @param component
	 *            Component.
	 * @param text
	 *            Text. If empty or <code>null</code>, the result is
	 *            <code>null</code>.
	 * @param state
	 *            Component state.
	 * @param textAlpha
	 *            Alpha channel for painting the text. If value is less than
	 *            1.0, the result is an opaque color which is an interpolation
	 *            between the "real" foreground color and the background color
	 *            of the component. This is done to ensure that native text
	 *            rasterization will be performed on 6u10+ on Windows.
	 * @return The foreground color for the specified component.
	 */
	public static Color getForegroundColor(JComponent component, String text,
			ComponentState state, float textAlpha) {
		if ((text == null) || (text.length() == 0))
			return null;

		boolean toEnforceFgColor = (SwingUtilities.getAncestorOfClass(
				CellRendererPane.class, component) != null)
				|| Boolean.TRUE.equals(component
						.getClientProperty(ENFORCE_FG_COLOR));

		Color fgColor = toEnforceFgColor ? component.getForeground()
				: SubstanceColorSchemeUtilities
						.getColorScheme(component, state).getForegroundColor();

		// System.out.println(text + ":" + prevState.name() + "->" +
		// state.name() + ":" + fgColor);
		if (textAlpha < 1.0f) {
			Color bgFillColor = SubstanceColorUtilities
					.getBackgroundFillColor(component);
            if (bgFillColor != null) {
			    fgColor = SubstanceColorUtilities.getInterpolatedColor(fgColor,
				    	bgFillColor, textAlpha);
            }
		}
		return fgColor;
	}

	/**
	 * Returns the foreground color for the specified component.
	 * 
	 * @param component
	 *            Component.
	 * @param text
	 *            Text. If empty or <code>null</code>, the result is
	 *            <code>null</code>.
	 * @param textAlpha
	 *            Alpha channel for painting the text. If value is less than
	 *            1.0, the result is an opaque color which is an interpolation
	 *            between the "real" foreground color and the background color
	 *            of the component. This is done to ensure that native text
	 *            rasterization will be performed on 6u10 on Windows.
	 * @return The foreground color for the specified component.
	 */
	public static Color getForegroundColor(JComponent component, String text,
			StateTransitionTracker.ModelStateInfo modelStateInfo,
			float textAlpha) {
		if ((text == null) || (text.length() == 0))
			return null;

		boolean toEnforceFgColor = (SwingUtilities.getAncestorOfClass(
				CellRendererPane.class, component) != null)
				|| Boolean.TRUE.equals(component
						.getClientProperty(ENFORCE_FG_COLOR));

		Color fgColor;
		if (toEnforceFgColor) {
			fgColor = component.getForeground();
		} else {
			fgColor = SubstanceColorUtilities.getForegroundColor(component,
					modelStateInfo);
		}

		// System.out.println(text + ":" + prevState.name() + "->" +
		// state.name() + ":" + fgColor);
		if (textAlpha < 1.0f) {
			Color bgFillColor = SubstanceColorUtilities
					.getBackgroundFillColor(component);
			fgColor = SubstanceColorUtilities.getInterpolatedColor(fgColor,
					bgFillColor, textAlpha);
		}
		return fgColor;
	}

	/**
	 * Returns the foreground color for the specified menu component.
	 * 
	 * @param menuComponent
	 *            Menu component.
	 * @param text
	 *            Text. If empty or <code>null</code>, the result is
	 *            <code>null</code>.
	 * @param modelStateInfo
	 *            Model state info for the specified component.
	 * @param textAlpha
	 *            Alpha channel for painting the text. If value is less than
	 *            1.0, the result is an opaque color which is an interpolation
	 *            between the "real" foreground color and the background color
	 *            of the component. This is done to ensure that native text
	 *            rasterization will be performed on 6u10 on Windows.
	 * @return The foreground color for the specified component.
	 */
	public static Color getMenuComponentForegroundColor(Component menuComponent,
			String text, StateTransitionTracker.ModelStateInfo modelStateInfo,
			float textAlpha) {
		if ((text == null) || (text.length() == 0))
			return null;

		Color fgColor = SubstanceColorUtilities
				.getMenuComponentForegroundColor(menuComponent, modelStateInfo);

		if (textAlpha < 1.0f) {
			Color bgFillColor = SubstanceColorUtilities
					.getBackgroundFillColor(menuComponent);
			fgColor = SubstanceColorUtilities.getInterpolatedColor(fgColor,
					bgFillColor, textAlpha);
		}
		return fgColor;
	}

	/**
	 * Paints background of the specified text component.
	 * 
	 * @param g
	 *            Graphics context.
	 * @param comp
	 *            Component.
	 */
	public static void paintTextCompBackground(Graphics g, JComponent comp) {
		Color backgroundFillColor = getTextBackgroundFillColor(comp);

		boolean toPaintWatermark = (SubstanceLookAndFeel.getCurrentSkin(comp)
				.getWatermark() != null)
				&& (SubstanceCoreUtilities.toDrawWatermark(comp) || !comp
						.isOpaque());
		paintTextCompBackground(g, comp, backgroundFillColor, toPaintWatermark);
	}

	public static Color getTextBackgroundFillColor(JComponent comp) {
		Color backgroundFillColor = SubstanceColorUtilities
				.getBackgroundFillColor(comp);
		JTextComponent componentForTransitions = SubstanceCoreUtilities
				.getTextComponentForTransitions(comp);

		if (componentForTransitions != null) {
			ComponentUI ui = componentForTransitions.getUI();
			if (ui instanceof TransitionAwareUI) {
				TransitionAwareUI trackable = (TransitionAwareUI) ui;
				StateTransitionTracker stateTransitionTracker = trackable
						.getTransitionTracker();

				Color outerTextComponentBorderColor = SubstanceColorUtilities
						.getOuterTextComponentBorderColor(backgroundFillColor);
				outerTextComponentBorderColor = SubstanceColorUtilities
						.getInterpolatedColor(outerTextComponentBorderColor,
								backgroundFillColor, 0.6);

				float selectionStrength = stateTransitionTracker
						.getFacetStrength(ComponentStateFacet.SELECTION);
				float rolloverStrength = stateTransitionTracker
						.getFacetStrength(ComponentStateFacet.ROLLOVER);
				backgroundFillColor = SubstanceColorUtilities
						.getInterpolatedColor(outerTextComponentBorderColor,
								backgroundFillColor, Math.max(
										selectionStrength, rolloverStrength));
			}
		}
		return backgroundFillColor;
	}

	/**
	 * Paints background of the specified text component.
	 * 
	 * @param g
	 *            Graphics context.
	 * @param comp
	 *            Component.
	 * @param backgr
	 *            Background color.
	 * @param toOverlayWatermark
	 *            If <code>true</code>, this method will paint the watermark
	 *            overlay on top of the background fill.
	 */
	private static void paintTextCompBackground(Graphics g, JComponent comp,
			Color backgr, boolean toOverlayWatermark) {
		Graphics2D g2d = (Graphics2D) g.create();

		int componentFontSize = SubstanceSizeUtils.getComponentFontSize(comp);
		int borderDelta = (int) Math.floor(SubstanceSizeUtils
				.getBorderStrokeWidth(componentFontSize));
		Border compBorder = comp.getBorder();

		if (compBorder instanceof LockBorder) {
			compBorder = ((LockBorder) compBorder).getOriginalBorder();
		}
		boolean isSubstanceBorder = compBorder instanceof SubstanceTextComponentBorder;

		if (!isSubstanceBorder) {
			Border border = compBorder;
			while (border instanceof CompoundBorder) {
				Border outer = ((CompoundBorder) border).getOutsideBorder();
				if (outer instanceof SubstanceTextComponentBorder) {
					isSubstanceBorder = true;
					break;
				}
				Border inner = ((CompoundBorder) border).getInsideBorder();
				if (inner instanceof SubstanceTextComponentBorder) {
					isSubstanceBorder = true;
					break;
				}
				border = inner;
			}
		}

		Shape contour = isSubstanceBorder ? SubstanceOutlineUtilities
				.getBaseOutline(
						comp.getWidth(),
						comp.getHeight(),
						Math
								.max(
										0,
										2.0f
												* SubstanceSizeUtils
														.getClassicButtonCornerRadius(componentFontSize)
												- borderDelta), null,
						borderDelta)
				: new Rectangle(0, 0, comp.getWidth(), comp.getHeight());

		BackgroundPaintingUtils.update(g, comp, false);
		SubstanceWatermark watermark = SubstanceCoreUtilities.getSkin(comp)
				.getWatermark();
		if (watermark != null) {
			watermark.drawWatermarkImage(g2d, comp, 0, 0, comp.getWidth(), comp
					.getHeight());
		}
		g2d.setColor(backgr);
		g2d.fill(contour);

		if (toOverlayWatermark) {
			if (watermark != null) {
				g2d.clip(contour);
				watermark.drawWatermarkImage(g2d, comp, 0, 0, comp.getWidth(),
						comp.getHeight());
			}
		}

		g2d.dispose();
	}
}
