/*
 * Copyright (c) 2001-2011 JGoodies Karsten Lentzsch. 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 JGoodies Karsten Lentzsch 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 com.jgoodies.looks.windows;

import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicGraphicsUtils;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import javax.swing.text.View;

import com.jgoodies.common.base.SystemUtils;
import com.jgoodies.looks.Options;

/**
 * The JGoodies Windows L&amp;F implementation of {@code TabbedPaneUI}.<p>
 *
 * The flat appearance is work in progress; currently it works only
 * for a single line of tabs and paints distored tabs for multiple lines.
 *
 * @author Karsten Lentzsch
 * @version $Revision: 1.11 $
 */
public final class WindowsTabbedPaneUI extends com.sun.java.swing.plaf.windows.WindowsTabbedPaneUI {


    /** Insets used for the embedded style content border. */
    private static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);

    /** Insets used if we paint no content border. */
    private static final int INSET = SystemUtils.IS_LAF_WINDOWS_XP_ENABLED ? -1 : 1;
    private static final Insets NO_CONTENT_BORDER_NORTH_INSETS = new Insets(INSET, 0, 0, 0);
    private static final Insets NO_CONTENT_BORDER_WEST_INSETS  = new Insets(0, INSET, 0, 0);
    private static final Insets NO_CONTENT_BORDER_SOUTH_INSETS = new Insets(0, 0, INSET, 0);
    private static final Insets NO_CONTENT_BORDER_EAST_INSETS  = new Insets(0, 0, 0, INSET);

    /** Insets used if we paint content border. */
    private static final Insets CONTENT_BORDER_NORTH_INSETS = new Insets(0, 2, 4, 4);
    private static final Insets CONTENT_BORDER_WEST_INSETS  = new Insets(2, 0, 4, 4);
    private static final Insets CONTENT_BORDER_SOUTH_INSETS = new Insets(4, 2, 0, 4);
    private static final Insets CONTENT_BORDER_EAST_INSETS  = new Insets(2, 4, 4, 0);


    /**
     * Describes if tabs are painted with or without icons.
     */
    private static boolean isTabIconsEnabled = Options.isTabIconsEnabled();

    /**
     * Describes if we paint no content border or not; this is false by default.
     * You can disable the content border by setting the client property
     * Options.NO_CONTENT_BORDER_KEY to Boolean.TRUE;
     */
    private Boolean noContentBorder;

    /**
     * Describes if we paint tabs in an embedded style that is with
     * less decoration; this is false by default.
     * You can enable the embedded tabs style by setting the client property
     * Options.EMBEDDED_TABS_KEY to Boolean.TRUE.
     */
    private Boolean embeddedTabs;

    /**
     * Creates and answers the {@code WindowsTabbedPaneUI}.
     *
     * @see javax.swing.plaf.ComponentUI#createUI(JComponent)
     */
    public static ComponentUI createUI(JComponent x) {
        return new WindowsTabbedPaneUI();
    }


    /**
     * Installs the UI.
     *
     * @see javax.swing.plaf.ComponentUI#installUI(JComponent)
     */
    @Override
    public void installUI(JComponent c) {
        super.installUI(c);
        embeddedTabs    = (Boolean) c.getClientProperty(Options.EMBEDDED_TABS_KEY);
        noContentBorder = (Boolean) c.getClientProperty(Options.NO_CONTENT_BORDER_KEY);
    }


    /**
     * Checks and answers if content border will be painted.
     * This is controlled by the component's client property
     * Options.NO_CONTENT_BORDER or Options.EMBEDDED.
     */
    private boolean hasNoContentBorder() {
        return hasEmbeddedTabs() || Boolean.TRUE.equals(noContentBorder);
    }

    /**
     * Checks and answers if tabs are painted with minimal decoration.
     */
    private boolean hasEmbeddedTabs() {
        return embeddedTabs == null
                ? false
                : embeddedTabs.booleanValue();
    }

    /**
     * Creates and answer a handler that listens to property changes.
     * Unlike the superclass BasicTabbedPane, the PlasticTabbedPaneUI
     * uses an extended Handler.
     */
    @Override
    protected PropertyChangeListener createPropertyChangeListener() {
        return new MyPropertyChangeHandler();
    }

    private void doLayout() {
        tabPane.revalidate();
        tabPane.repaint();
    }

    /**
     * Updates the embedded tabs property. This message is sent by
     * my PropertyChangeHandler whenever the embedded tabs property changes.
     */
    private void embeddedTabsPropertyChanged(Boolean newValue) {
        embeddedTabs = newValue;
        doLayout();
    }

     /**
      * Updates the no content border property. This message is sent
      * by my PropertyChangeHandler whenever the noContentBorder
      * property changes.
      */
     private void noContentBorderPropertyChanged(Boolean newValue) {
         noContentBorder = newValue;
         doLayout();
     }



    /**
     * Answers the icon for the tab with the specified index.
     * In case, we have globally switched of the use tab icons,
     * we answer {@code null} if and only if we have a title.
     */
    @Override
    protected Icon getIconForTab(int tabIndex) {
        String title = tabPane.getTitleAt(tabIndex);
        boolean hasTitle = title != null && title.length() > 0;
        return !isTabIconsEnabled  && hasTitle
                    ? null
                    : super.getIconForTab(tabIndex);
    }

    @Override
    protected Insets getContentBorderInsets(int tabPlacement) {
        if (!hasNoContentBorder()) {
            if (SystemUtils.IS_LAF_WINDOWS_XP_ENABLED) {
                switch (tabPlacement) {
                    case RIGHT :
                        return CONTENT_BORDER_EAST_INSETS;
                    case LEFT :
                        return CONTENT_BORDER_WEST_INSETS;
                    case TOP :
                        return CONTENT_BORDER_NORTH_INSETS;
                    case BOTTOM :
                    default :
                    return CONTENT_BORDER_SOUTH_INSETS;
                }
            }
            return contentBorderInsets;
        } else if (hasEmbeddedTabs()) {
            return EMPTY_INSETS;
        } else {
            switch (tabPlacement) {
                case RIGHT :
                    return NO_CONTENT_BORDER_EAST_INSETS;
                case LEFT :
                    return NO_CONTENT_BORDER_WEST_INSETS;
                case TOP :
                    return NO_CONTENT_BORDER_NORTH_INSETS;
                case BOTTOM :
                default :
                    return NO_CONTENT_BORDER_SOUTH_INSETS;
            }
        }
    }

    @Override
    protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) {
        switch (tabPlacement) {
            case RIGHT :
                return isSelected ? 2 : 0;
            case LEFT :
                return isSelected ? -2 : 0;
            case TOP :
            case BOTTOM :
            default :
                return 0;
        }
    }

    @Override
    protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) {
        return 0;
    }

    @Override
    protected Insets getSelectedTabPadInsets(int tabPlacement) {
        if (hasEmbeddedTabs()) {
            return EMPTY_INSETS;
        } else if (hasNoContentBorder()) {
            int inset = SystemUtils.IS_LAF_WINDOWS_XP_ENABLED ? 1 : 0;
            switch (tabPlacement) {
                case LEFT:   return new Insets(1, 2, 1, inset);
                case RIGHT:  return new Insets(1, inset, 1, 2);
                case TOP:    return new Insets(2, 2, inset, 2);
                case BOTTOM: return new Insets(inset, 2, 2, 2);
                default: return EMPTY_INSETS;
            }
        } else {
            Insets superInsets = super.getSelectedTabPadInsets(tabPlacement);
            int equalized = superInsets.left + superInsets.right / 2;
            superInsets.left = superInsets.right = equalized;
            return superInsets;
        }
    }


    @Override
    protected Insets getTabAreaInsets(int tabPlacement) {
        return hasEmbeddedTabs()
                ? /*new Insets(1,1,1,1)*/EMPTY_INSETS
                : super.getTabAreaInsets(tabPlacement);
    }

    /**
     * Paints the top edge of the pane's content border.
     */
    @Override
    protected void paintContentBorderTopEdge(Graphics g, int tabPlacement,
                                         int selectedIndex,
                                         int x, int y, int w, int h) {
        if (hasNoContentBorder() && tabPlacement != TOP) {
            return;
        }
        Rectangle selRect = selectedIndex < 0
                            ? null
                            : getTabBounds(selectedIndex, calcRect);
        if (tabPlacement != TOP || selectedIndex < 0 ||
           selRect.y + selRect.height + 1 < y ||
           selRect.x < x || selRect.x > x + w) {
            // no special case, do the super thing
            super.paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y, w, h);
        } else {
            g.setColor(lightHighlight);
            g.fillRect(x, y, selRect.x + 1-x, 1);
            g.fillRect(selRect.x + selRect.width, y,
                       x+w-2 -selRect.x-selRect.width, 1);
        }
    }

    /**
     * Paints the bottom edge of the pane's content border.
     */
    @Override
    protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement,
                                         int selectedIndex,
                                         int x, int y, int w, int h) {
        if (!hasNoContentBorder()) {
            Rectangle selRect = selectedIndex < 0 ? null :
                           getTabBounds(selectedIndex, calcRect);
            if (tabPlacement != BOTTOM || selectedIndex < 0 ||
             selRect.y - 1 > h + y ||
         selRect.x < x || selRect.x > x + w) {
           // no special case, do the super thing
            super.paintContentBorderBottomEdge(g, tabPlacement, selectedIndex, x, y, w, h);
         } else {
            g.setColor(lightHighlight);
            g.fillRect(x,y+h-1,1,1);
            g.setColor(shadow);
            g.fillRect(x+1, y+h-2, selRect.x - 1-x, 1);
            g.fillRect(selRect.x + selRect.width, y+h-2, x+w-2-selRect.x-selRect.width, 1);
            g.setColor(darkShadow);
            g.fillRect(x, y+h-1, selRect.x - x, 1);
            g.fillRect(selRect.x + selRect.width -1, y+h-1, x+w-selRect.x-selRect.width, 1);
         }
        } else if (!(tabPlacement == BOTTOM)) {
            // no content border really means only one content border:
            // the one edge that touches the tabs
        } else {
            g.setColor(shadow);
            g.fillRect(x,y+h,w,1);
        }
    }

    /**
     * Paints the left Edge of the pane's content border.
     */
    @Override
    protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement,
                                         int selectedIndex,
                                         int x, int y, int w, int h) {
        if (!hasNoContentBorder()) {
            Rectangle selRect = selectedIndex < 0 ? null :
                           getTabBounds(selectedIndex, calcRect);
            if (tabPlacement != LEFT || selectedIndex < 0 ||
                selRect.x + selRect.width + 1 < x ||
                selRect.y < y || selRect.y > y + h) {
                 // no special case, do the super thing
                super.paintContentBorderLeftEdge(g, tabPlacement, selectedIndex, x, y, w, h);
            } else {
               g.setColor(lightHighlight);
               g.fillRect(x, y, 1, selRect.y + 1 - y);
               g.fillRect(x, selRect.y + selRect.height,
                           1, y+h-1-selRect.y-selRect.height);

            }
        } else if (!(tabPlacement == LEFT)) {
            // no content border really means only one content border:
            // the one edge that touches the tabs
        } else {
            g.setColor(shadow);
            g.fillRect(x,y,1,h);
        }
    }

    /**
     * Paints the right Edge of the pane's content border.
     */
    @Override
    protected void paintContentBorderRightEdge(Graphics g, int tabPlacement,
                                         int selectedIndex,
                                         int x, int y, int w, int h) {
        if (!hasNoContentBorder()) {
            Rectangle selRect = selectedIndex < 0 ? null :
                           getTabBounds(selectedIndex, calcRect);
            if (tabPlacement != RIGHT || selectedIndex < 0 ||
               selRect.x - 1 > x+w ||
               selRect.y < y || selRect.y > y + h) {
                // no special case, do the super thing
                super.paintContentBorderRightEdge(g, tabPlacement, selectedIndex, x, y, w, h);
             } else {
               g.setColor(lightHighlight);
               g.fillRect(x+w-1, y,1,1);
               g.setColor(shadow);
               g.fillRect(x+w-2, y+1, 1, selRect.y - 1-y);
               g.fillRect(x+w-2, selRect.y + selRect.height,
                           1, y+h-1-selRect.y- selRect.height);
               g.setColor(darkShadow);
               g.fillRect(x+w-1, y, 1, selRect.y - y);
               g.fillRect(x+w-1, selRect.y + selRect.height-1,
                           1, y+h-selRect.y-selRect.height);

             }
        } else if (!(tabPlacement == RIGHT)) {
            // no content border really means only one content border:
            // the one edge that touches the tabs
        } else {
            g.setColor(shadow);
            g.fillRect(x+w,y,1,h);
        }
    }


   /**
     * Paints the border for a single tab; it does not paint the tab's background.
     */
    @Override
    protected void paintTabBorder(
        Graphics g,
        int tabPlacement,
        int tabIndex,
        int x,
        int y,
        int w,
        int h,
        boolean isSelected) {
        if (!hasEmbeddedTabs()) {
            super.paintTabBorder(g, tabPlacement, tabIndex, x, y, w, h, isSelected);
            return;
        }
        g.translate(x - 1, y - 1);
        int w1, w2, w3;
        int h1, h2, h3;
        switch (tabPlacement) {
            case TOP :
                w1 = 1;
                w2 = w - 2;
                w3 = 1;
                h1 = 1;
                h2 = h - 1;
                h3 = 0;
                break;
            case BOTTOM :
                w1 = 1;
                w2 = w - 2;
                w3 = 1;
                h1 = 0;
                h2 = h - 1;
                h3 = 1;
                break;
            case LEFT :
                w1 = 1;
                w2 = w - 1;
                w3 = 0;
                h1 = 1;
                h2 = h - 3;
                h3 = 1;
                break;
            case RIGHT :
            default :
                w1 = 0;
                w2 = w - 1;
                w3 = 1;
                h1 = 1;
                h2 = h - 3;
                h3 = 1;
        }
        if (isSelected) {
            g.setColor(lightHighlight);
            g.drawRect(w1, h1, w1 + w2 + w3, h1 + h2 + h3);
            g.setColor(shadow);
            g.fillRect(1 + w1, 0, w2, h1);
            g.fillRect(0, 1 + h1, w1, h2);
            g.fillRect(2 * w1 + w2 + 2 * w3, 1 + h1, w3, h2);
            g.fillRect(1 + w1, 2 * h1 + h2 + 2 * h3, w2, h3);
            g.fillRect(1, 1, w1, h1);
            g.fillRect(2 * w1 + w2 + w3, 1, w3, h1);
            g.fillRect(1, 2 * h1 + h2 + h3, w1, h3);
            g.fillRect(2 * w1 + w2 + w3, 2 * h1 + h2 + h3, w3, h3);
        } else {
            g.setColor(shadow);
            g.fillRect(w1 + w2 + 2 * w3, h3 * h2 /2, w3, h2* 2 /3);
            g.fillRect(w3*w2 /2, h1 + h2 + 2 * h3, w2/2 +2, h3);
        }
        g.translate(-x + 1, -y + 1);
    }

    @Override
    protected void paintFocusIndicator(
        Graphics g,
        int tabPlacement,
        Rectangle[] rectangles,
        int tabIndex,
        Rectangle iconRect,
        Rectangle textRect,
        boolean isSelected) {
        if (!hasEmbeddedTabs()) {
            super.paintFocusIndicator(g, tabPlacement, rectangles, tabIndex, iconRect, textRect, isSelected);
            return;
        }
        if (tabPane.hasFocus() && isSelected) {
            g.setColor(focus);
            BasicGraphicsUtils.drawDashedRect(g, textRect.x - 2, textRect.y, textRect.width + 3, textRect.height);
        }
    }

    @Override
    protected boolean shouldRotateTabRuns(int tabPlacement) {
        return !hasEmbeddedTabs();
    }

    /**
     * Copied here from super(super)class to avoid labels being centered on
     * vertical tab runs if they consist of icon and text.
     */
    @Override
    protected void layoutLabel(
        int tabPlacement,
        FontMetrics metrics,
        int tabIndex,
        String title,
        Icon icon,
        Rectangle tabRect,
        Rectangle iconRect,
        Rectangle textRect,
        boolean isSelected) {
        textRect.x = textRect.y = iconRect.x = iconRect.y = 0;

        //fix of issue #4
        View v = getTextViewForTab(tabIndex);
        if (v != null) {
            tabPane.putClientProperty("html", v);
        }

        int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
        int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
        if ((tabPlacement == RIGHT || tabPlacement == LEFT) && icon != null && title != null && !title.equals("")) {
            /* vertical tab runs look ugly if icons and text are centered */
            SwingUtilities.layoutCompoundLabel(
                tabPane,
                metrics,
                title,
                icon,
                SwingConstants.CENTER,
                SwingConstants.LEFT,
                SwingConstants.CENTER,
                SwingConstants.TRAILING,
                tabRect,
                iconRect,
                textRect,
                textIconGap);
            xNudge += 4;
        } else { /* original superclass behavior */
            SwingUtilities.layoutCompoundLabel(
                tabPane,
                metrics,
                title,
                icon,
                SwingConstants.CENTER,
                SwingConstants.CENTER,
                SwingConstants.CENTER,
                SwingConstants.TRAILING,
                tabRect,
                iconRect,
                textRect,
                textIconGap);
        }

        //fix of issue #4
        tabPane.putClientProperty("html", null);

        iconRect.x += xNudge;
        iconRect.y += yNudge;
        textRect.x += xNudge;
        textRect.y += yNudge;
    }


    /**
     * Catches and handles property change events. In addition to the super
     * class behavior we listen to changes of the ancestor, tab placement,
     * and JGoodies options for content border, and embedded tabs.
     */
    private final class MyPropertyChangeHandler extends BasicTabbedPaneUI.PropertyChangeHandler {

        @Override
        public void propertyChange(PropertyChangeEvent e) {
            super.propertyChange(e);

            String pName = e.getPropertyName();
            if (null == pName) {
                return;
            }
            if (pName.equals(Options.EMBEDDED_TABS_KEY)) {
               embeddedTabsPropertyChanged((Boolean)e.getNewValue());
               return;
            }
            if (pName.equals(Options.NO_CONTENT_BORDER_KEY)) {
               noContentBorderPropertyChanged((Boolean)e.getNewValue());
               return;
            }

        }

    }



}