/*
 * Copyright (c) 2009 Kathryn Huxtable and Kenneth Orr.
 *
 * This file is part of the SeaGlass Pluggable Look and Feel.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * $Id$
 */
package com.seaglasslookandfeel.util;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;

import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.plaf.basic.BasicGraphicsUtils;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.synth.ColorType;
import javax.swing.plaf.synth.SynthContext;
import javax.swing.plaf.synth.SynthGraphicsUtils;
import javax.swing.plaf.synth.SynthStyle;
import javax.swing.text.View;

import sun.swing.MenuItemLayoutHelper;
import sun.swing.SwingUtilities2;
import sun.swing.plaf.synth.SynthIcon;

import com.seaglasslookandfeel.SeaGlassContext;
import com.seaglasslookandfeel.SeaGlassLookAndFeel;
import com.seaglasslookandfeel.component.SeaGlassIcon;

/**
 * Customize the graphics utilities used by Synth.
 *
 * @author Kathryn Huxtable
 */
public class SeaGlassGraphicsUtils extends SynthGraphicsUtils {

    // These are used in the text painting code to avoid allocating a bunch of
    // garbage.
    private Rectangle paintIconR  = new Rectangle();
    private Rectangle paintTextR  = new Rectangle();
    private Rectangle paintViewR  = new Rectangle();
    private Insets    paintInsets = new Insets(0, 0, 0, 0);

    
    public static void applyInsets(Rectangle rect, Insets insets, boolean leftToRight) {
        if (insets != null) {
            rect.x += (leftToRight ? insets.left : insets.right);
            rect.y += insets.top;
            rect.width -= (leftToRight ? insets.right : insets.left) + rect.x;
            rect.height -= (insets.bottom + rect.y);
        }
    }

    
    public static void paint(SynthContext context, SynthContext accContext, Graphics g, Icon checkIcon, Icon arrowIcon,
        String acceleratorDelimiter, int defaultTextIconGap, String propertyPrefix) {
        JMenuItem mi = (JMenuItem) context.getComponent();
        SynthStyle style = context.getStyle();
        g.setFont(style.getFont(context));

        Rectangle viewRect = new Rectangle(0, 0, mi.getWidth(), mi.getHeight());
        boolean leftToRight = SeaGlassLookAndFeel.isLeftToRight(mi);
        applyInsets(viewRect, mi.getInsets(), leftToRight);

        SeaGlassMenuItemLayoutHelper lh = new SeaGlassMenuItemLayoutHelper(context, accContext, mi, checkIcon, arrowIcon, viewRect,
            defaultTextIconGap, acceleratorDelimiter, leftToRight, MenuItemLayoutHelper.useCheckAndArrow(mi), propertyPrefix);
        MenuItemLayoutHelper.LayoutResult lr = lh.layoutMenuItem();

        paintMenuItem(g, lh, lr);
    }

    
    public static void paintMenuItem(Graphics g, SeaGlassMenuItemLayoutHelper lh, MenuItemLayoutHelper.LayoutResult lr) {
        // Save original graphics font and color
        Font holdf = g.getFont();
        Color holdc = g.getColor();

        paintCheckIcon(g, lh, lr);
        paintIcon(g, lh, lr);
        paintText(g, lh, lr);
        paintAccText(g, lh, lr);
        paintArrowIcon(g, lh, lr);

        // Restore original graphics font and color
        g.setColor(holdc);
        g.setFont(holdf);
    }

    static void paintBackground(Graphics g, SeaGlassMenuItemLayoutHelper lh) {
        paintBackground(lh.getContext(), g, lh.getMenuItem());
    }

    static void paintBackground(SynthContext context, Graphics g, JComponent c) {
        ((SeaGlassContext)context).getPainter().paintMenuItemBackground(context, g, 0, 0,
                c.getWidth(), c.getHeight());
    }

    static void paintIcon(Graphics g, SeaGlassMenuItemLayoutHelper lh,
                          MenuItemLayoutHelper.LayoutResult lr) {
        if (lh.getIcon() != null) {
            Icon icon;
            JMenuItem mi = lh.getMenuItem();
            ButtonModel model = mi.getModel();
            if (!model.isEnabled()) {
                icon = mi.getDisabledIcon();
            } else if (model.isPressed() && model.isArmed()) {
                icon = mi.getPressedIcon();
                if (icon == null) {
                    // Use default icon
                    icon = mi.getIcon();
                }
            } else {
                icon = mi.getIcon();
            }

            if (icon != null) {
                Rectangle iconRect = lr.getIconRect();
                SynthIcon.paintIcon(icon, lh.getContext(), g, iconRect.x,
                        iconRect.y, iconRect.width, iconRect.height);
            }
        }
    }

    static void paintCheckIcon(Graphics g, SeaGlassMenuItemLayoutHelper lh,
                               MenuItemLayoutHelper.LayoutResult lr) {
        if (lh.getCheckIcon() != null) {
            Rectangle checkRect = lr.getCheckRect();
            SynthIcon.paintIcon(lh.getCheckIcon(), lh.getContext(), g,
                    checkRect.x, checkRect.y, checkRect.width, checkRect.height);
        }
    }

    static void paintAccText(Graphics g, SeaGlassMenuItemLayoutHelper lh,
                             MenuItemLayoutHelper.LayoutResult lr) {
        String accText = lh.getAccText();
        if (accText != null && !accText.equals("")) {
            g.setColor(lh.getAccStyle().getColor(lh.getAccContext(),
                    ColorType.TEXT_FOREGROUND));
            g.setFont(lh.getAccStyle().getFont(lh.getAccContext()));
            lh.getAccGraphicsUtils().paintText(lh.getAccContext(), g, accText,
                    lr.getAccRect().x, lr.getAccRect().y, -1);
        }
    }

    static void paintText(Graphics g, SeaGlassMenuItemLayoutHelper lh,
                          MenuItemLayoutHelper.LayoutResult lr) {
        if (!lh.getText().equals("")) {
            if (lh.getHtmlView() != null) {
                // Text is HTML
                lh.getHtmlView().paint(g, lr.getTextRect());
            } else {
                // Text isn't HTML
                g.setColor(lh.getStyle().getColor(
                        lh.getContext(), ColorType.TEXT_FOREGROUND));
                g.setFont(lh.getStyle().getFont(lh.getContext()));
                lh.getGraphicsUtils().paintText(lh.getContext(), g, lh.getText(),
                        lr.getTextRect().x, lr.getTextRect().y,
                        lh.getMenuItem().getDisplayedMnemonicIndex());
            }
        }
    }

    static void paintArrowIcon(Graphics g, SeaGlassMenuItemLayoutHelper lh,
                               MenuItemLayoutHelper.LayoutResult lr) {
        if (lh.getArrowIcon() != null) {
            Rectangle arrowRect = lr.getArrowRect();
            SynthIcon.paintIcon(lh.getArrowIcon(), lh.getContext(), g,
                    arrowRect.x, arrowRect.y, arrowRect.width, arrowRect.height);
        }
    }

    /**
     * Paints text at the specified location. This will not attempt to render
     * the text as html nor will it offset by the insets of the component.
     *
     * @param ss
     *            SynthContext
     * @param g
     *            Graphics used to render string in.
     * @param text
     *            Text to render
     * @param x
     *            X location to draw text at.
     * @param y
     *            Upper left corner to draw text at.
     * @param mnemonicIndex
     *            Index to draw string at.
     */
    public void paintText(SynthContext ss, Graphics g, String text, int x, int y, int mnemonicIndex) {
        if (text != null) {
            Graphics2D g2d = (Graphics2D) g.create();

            g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            JComponent c = ss.getComponent();

            // SynthStyle style = ss.getStyle();
            FontMetrics fm = SwingUtilities2.getFontMetrics(c, g2d);

            y += fm.getAscent();
            SwingUtilities2.drawString(c, g2d, text, x, y);

            if (mnemonicIndex >= 0 && mnemonicIndex < text.length()) {
                int underlineX = x + SwingUtilities2.stringWidth(c, fm, text.substring(0, mnemonicIndex));
                int underlineY = y;
                int underlineWidth = fm.charWidth(text.charAt(mnemonicIndex));
                int underlineHeight = 1;

                g2d.fillRect(underlineX, underlineY + fm.getDescent() - 1, underlineWidth, underlineHeight);
            }
        }
    }

    /**
     * Returns a new color with the alpha of the old color cut in half.
     *
     * @param color
     *            the original color.
     *
     * @return the new color.
     */
    // Rossi: used in slider ui "paint ticks".
    public static Color disable(Color color) {
        int alpha = color.getAlpha();

        alpha /= 2;

        return new Color((color.getRGB() & 0xFFFFFF) | (alpha << 24), true);
    }

    /**
     * Paints an icon and text. This will render the text as html, if necessary,
     * and offset the location by the insets of the component.
     *
     * @param ss
     *            SynthContext
     * @param g
     *            Graphics to render string and icon into
     * @param text
     *            Text to layout
     * @param icon
     *            Icon to layout
     * @param hAlign
     *            horizontal alignment
     * @param vAlign
     *            vertical alignment
     * @param hTextPosition
     *            horizontal text position
     * @param vTextPosition
     *            vertical text position
     * @param iconTextGap
     *            gap between icon and text
     * @param mnemonicIndex
     *            Index into text to render the mnemonic at, -1 indicates no
     *            mnemonic.
     * @param textOffset
     *            Amount to offset the text when painting
     */
    public void paintText(SynthContext ss, Graphics g, String text, Icon icon, int hAlign, int vAlign, int hTextPosition,
        int vTextPosition, int iconTextGap, int mnemonicIndex, int textOffset) {
        if ((icon == null) && (text == null)) {
            return;
        }

        Graphics2D g2d = (Graphics2D) g.create();

        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        JComponent c = ss.getComponent();
        FontMetrics fm = SwingUtilities2.getFontMetrics(c, g2d);
        Insets insets = SeaGlassLookAndFeel.getPaintingInsets(ss, paintInsets);

        paintViewR.x = insets.left;
        paintViewR.y = insets.top;
        paintViewR.width = c.getWidth() - (insets.left + insets.right);
        paintViewR.height = c.getHeight() - (insets.top + insets.bottom);

        paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0;
        paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0;

        String clippedText = layoutText(ss, fm, text, icon, hAlign, vAlign, hTextPosition, vTextPosition, paintViewR, paintIconR,
            paintTextR, iconTextGap);

        if (icon != null) {
            Color color = g2d.getColor();

            paintIconR.x += textOffset;
            paintIconR.y += textOffset;
            SeaGlassIcon.paintIcon(icon, ss, g2d, paintIconR.x, paintIconR.y, paintIconR.width, paintIconR.height);
            g2d.setColor(color);
        }

        if (text != null) {
            View v = (View) c.getClientProperty(BasicHTML.propertyKey);

            if (v != null) {
                v.paint(g2d, paintTextR);
            } else {
                paintTextR.x += textOffset;
                paintTextR.y += textOffset;

                paintText(ss, g2d, clippedText, paintTextR, mnemonicIndex);
            }
        }
    }

    /**
     * Draw text with an emphasized background.
     *
     * @param g
     *            the Graphics context to draw with.
     * @param foreground
     *            the foreground color.
     * @param emphasis
     *            the emphasis color.
     * @param s
     *            the text to draw.
     * @param x
     *            the x coordinate to draw at.
     * @param y
     *            the y coordinate to draw at.
     */
    public void drawEmphasizedText(Graphics g, Color foreground, Color emphasis, String s, int x, int y) {
        drawEmphasizedText(g, foreground, emphasis, s, -1, x, y);
    }

    /**
     * Draw text with an emphasized background.
     *
     * @param g
     *            the Graphics context to draw with.
     * @param foreground
     *            the foreground color.
     * @param emphasis
     *            the emphasis color.
     * @param s
     *            the text to draw.
     * @param underlinedIndex
     *            the index to underline.
     * @param x
     *            the x coordinate to draw at.
     * @param y
     *            the y coordinate to draw at.
     */
    public void drawEmphasizedText(Graphics g, Color foreground, Color emphasis, String s, int underlinedIndex, int x, int y) {
        Graphics2D g2d = (Graphics2D) g.create();

        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

        g2d.setColor(emphasis);
        BasicGraphicsUtils.drawStringUnderlineCharAt(g2d, s, underlinedIndex, x, y + 1);
        g2d.setColor(foreground);
        BasicGraphicsUtils.drawStringUnderlineCharAt(g2d, s, underlinedIndex, x, y);
    }

    public static Dimension getPreferredMenuItemSize(SynthContext context, SynthContext accContext, JComponent c, Icon checkIcon,
        Icon arrowIcon, int defaultTextIconGap, String acceleratorDelimiter, boolean useCheckAndArrow, String propertyPrefix) {

        JMenuItem mi = (JMenuItem) c;
        SeaGlassMenuItemLayoutHelper lh = new SeaGlassMenuItemLayoutHelper(context, accContext, mi, checkIcon, arrowIcon,
            MenuItemLayoutHelper.createMaxRect(), defaultTextIconGap, acceleratorDelimiter, SeaGlassLookAndFeel.isLeftToRight(mi),
            useCheckAndArrow, propertyPrefix);

        Dimension result = new Dimension();

        // Calculate the result width
        int gap = lh.getGap();
        result.width = 0;
        MenuItemLayoutHelper.addMaxWidth(lh.getCheckSize(), gap, result);
        MenuItemLayoutHelper.addMaxWidth(lh.getLabelSize(), gap, result);
        MenuItemLayoutHelper.addWidth(lh.getMaxAccOrArrowWidth(), 5 * gap, result);
        // The last gap is unnecessary
        result.width -= gap;

        // Calculate the result height
        result.height = MenuItemLayoutHelper.max(lh.getCheckSize().getHeight(), lh.getLabelSize().getHeight(), lh.getAccSize().getHeight(),
            lh.getArrowSize().getHeight());

        // Take into account menu item insets
        Insets insets = lh.getMenuItem().getInsets();
        if (insets != null) {
            result.width += insets.left + insets.right;
            result.height += insets.top + insets.bottom;
        }

        // if the width is even, bump it up one. This is critical
        // for the focus dash lhne to draw properly
        if (result.width % 2 == 0) {
            result.width++;
        }

        // if the height is even, bump it up one. This is critical
        // for the text to center properly
        if (result.height % 2 == 0) {
            result.height++;
        }

        return result;

    }

}