/**
 * This software is released as part of the Pumpernickel project.
 * 
 * All com.pump resources in the Pumpernickel project are distributed under the
 * MIT License:
 * https://raw.githubusercontent.com/mickleness/pumpernickel/master/License.txt
 * 
 * More information about the Pumpernickel project is available here:
 * https://mickleness.github.io/pumpernickel/
 */
package com.pump.plaf;

import java.awt.Color;
import java.awt.Component;
import java.awt.Component.BaselineResizeBehavior;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.ContainerAdapter;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.AbstractButton;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JToggleButton.ToggleButtonModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.TabbedPaneUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.plaf.basic.BasicButtonUI;

import com.pump.awt.DescendantListener;
import com.pump.awt.SplayedLayout;
import com.pump.icon.button.MinimalDuoToneCloseIcon;
import com.pump.plaf.button.QButtonUI;
import com.pump.plaf.button.QButtonUI.HorizontalPosition;
import com.pump.plaf.button.QButtonUI.VerticalPosition;
import com.pump.swing.PartialLineBorder;

/**
 * This is modeled after Safari's tab model. There is one row of tabs (and other
 * possible controls) that is always stretch to fill the available width (or
 * height for vertical tabs).
 * 
 * Features include:
 * <ul>
 * <li>The option to hide the tab row when only one tab is present (the content
 * stays visible, but the tab is hidden).
 * <li>The option to automatically include a close button on all tabs.
 * <li>The option to add custom controls before/after the tabs.
 * </ul>
 * 
 * TODO: it would be nice to add drag-and-drop reordering
 * 
 * <p>
 * This UI does not support the tab layout policy of the JTabbedPane; tabs are
 * only presented in a single continuous scrollable row. So this will only
 * support one or zero runs of tabs.<br>
 * <h3>Context / Design</h3>
 * <p>
 * I would suggest this UI is appropriate for tabbed document interfaces (TDIs),
 * which are a specific implementation of multiple document interfaces (MDIs).
 * (This is all in contrast to single document interfaces (SDIs).)
 * <p>
 * There is a lot of related reading on this online and in well-researched books
 * like <em>About Face</em>. To sum up what I've read so far:
 * <ul>
 * <li>MDIs were most popular in the 90s and early 2000s. This is partly because
 * of resource limitations at the time -- there was no alternative. Some UX
 * designers regard them with a stigma and will suggest SDIs are the better way
 * to go.
 * <li>Still TDIs don't appear to be going away anytime soon. Browsers are the
 * most obvious example. Microsoft Excel is another. Apple, who is notorious for
 * UX scrutiny, recently added tabs to Finder windows. But my favorite example
 * is Notepad vs Notepad++. Notepad is a fine tool, and maybe it's the go-to
 * choice for many users, but Notepad++ (or a similar TDI tool like Atom) is
 * where power-users gravitate to.
 * </ul>
 * <p>
 * If you're interested in displaying a fixed set of tabs (such as in a complex
 * properties or preferences dialog): you should try to make sure the tabs are
 * always visible. Depending on the number of tabs you want to display: this UI
 * may not be a good fit in that case. Horizontal scrolling is relatively subtle
 * (even if you include an indicator like "+3" to indicate 3 more tabs are
 * out-of-sight, some users won't register that).
 * <p>
 * <h3>Implementation</h3>
 * <p>
 * This section describes how parts of this class are implemented, which may be
 * useful if you want to customize either a {@link Style} object or extend the
 * {@link DefaultTab} class.
 * <p>
 * (This section is written as if using the default tab placement; the same
 * components are arranged in subtly different ways for other tab placements.)
 * <p>
 * Each BoxTabbedPaneUI has a <code>controlRow</code> panel that stretches the
 * width of the <code>JTabbedPane</code>.
 * <p>
 * If you have defined leading components (see
 * {@link #PROPERTY_LEADING_COMPONENTS}), then they are anchored to the left
 * side of the <code>controlRow</code>. If you have defined trailing components
 * (see {@link #PROPERTY_TRAILING_COMPONENTS}), then they are anchored on the
 * right side of the <code>controlRow</code>. All of these components are given
 * their preferred size, so their preferred size should be modest. Note it is
 * your responsibility to format these controls. (For example, you may want your
 * components to match the border/fill of your BoxTabbedPaneUI.)
 * <p>
 * The <code>tabContainer</code> panel is given the remaining width. This width
 * is divided up into a series of <code>TabContainer</code> panels with one
 * panel per tab. The <code>TabContainer</code> class is actually a
 * <code>AbstractButton</code>: if you click it you are selecting that tab. You
 * can also transfer keyboard focus to it and press the spacebar.
 * <p>
 * The <code>TabContainer</code> is actually blank, and it contains the
 * component used to render/control the actual tab. By default this component
 * will be the <code>DefaultTab</code> object, but if you have defined custom
 * components using {@link JTabbedPane#setTabComponentAt(int, Component)} then
 * that component is used.
 * <p>
 * The <code>DefaultTab</code> contains a close button that is visible when
 * {@link #PROPERTY_CLOSEABLE_TABS} is enabled. (That also means: this property
 * has no automatic effect if you have supplied your own custom tab components.)
 * <p>
 * <img src=
 * "data:image/png;base64,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"
 * alt="diagram of BoxTabbedPaneUI components">
 */
public class BoxTabbedPaneUI extends TabbedPaneUI {

	public interface Style {
		public void formatControlRow(JTabbedPane tabs, JPanel tabsContainer);

		/**
		 * 
		 * @param tabs
		 * @param tabContainer
		 * @param tabIndex
		 *            the tab index being formatted, or -1 if this button is
		 *            part of the control row but does not affect a tab.
		 */
		public void formatControlRowButton(JTabbedPane tabs,
				AbstractButton tabContainer, int tabIndex);

		public void formatCloseButton(JTabbedPane tabs, JButton closeButton);

		public void formatTabContent(JTabbedPane tabs, JComponent c);
	}

	public static DefaultStyle STYLE_DEFAULT = new DefaultStyle();

	public static Style getStyle(JTabbedPane tabs) {
		Style style = (Style) tabs.getClientProperty(PROPERTY_STYLE);
		if (style == null)
			style = STYLE_DEFAULT;
		return style;
	}

	public static class DefaultStyle implements Style {

		public class TabButtonUI extends BasicButtonUI {

			@Override
			public void paint(Graphics g0, JComponent c) {
				Graphics2D g = (Graphics2D) g0.create();
				boolean isSelected = ((AbstractButton) c).isSelected();
				g.setPaint(createContentGradient(c, isSelected));
				g.fillRect(0, 0, c.getWidth(), c.getHeight());
				if (c.isFocusOwner()) {
					int x = 0;
					int y = 0;
					int w = c.getWidth() - 1;
					int h = c.getHeight() - 1;
					Border b = c.getBorder();
					if (b != null) {
						Insets i = b.getBorderInsets(c);
						x += i.left;
						y += i.top;
						w -= i.left + i.right;
						h -= i.top + i.bottom;
					}
					Rectangle r = new Rectangle(x, y, w, h);
					PlafPaintUtils.paintFocus(g, r, 1,
							PlafPaintUtils.getFocusRingColor());
				}
				g.dispose();
				super.paint(g0, c);
			}

		}

		protected Color borderNormalLight = new Color(0xa1a0a1);
		protected Color borderNormalDark = new Color(0x9e9c9e);
		protected Color borderOuterSelected = new Color(0xbab9ba);
		protected Color borderOuterUnselected = new Color(0xa1a0a1);
		protected Color contentOuterNormal = new Color(0xbdbbbd);
		protected Color contentInnerNormal = new Color(0xb5b3b5);
		protected Color contentOuterSelected = new Color(0xd9d7d9);
		protected Color contentInnerSelected = new Color(0xd1cfd1);
		private boolean outerBorder, contentBorder;

		protected GradientPaint createContentGradient(JComponent c,
				boolean isSelected) {
			JTabbedPane tabs = getTabbedPaneParent(c);
			int placement = tabs == null ? SwingConstants.TOP : tabs
					.getTabPlacement();
			Color outer = isSelected ? contentOuterSelected
					: contentOuterNormal;
			Color inner = isSelected ? contentInnerSelected
					: contentInnerNormal;
			if (placement == SwingConstants.LEFT) {
				return (new GradientPaint(0, 0, outer, c.getWidth(), 0, inner));
			} else if (placement == SwingConstants.RIGHT) {
				return (new GradientPaint(0, 0, inner, c.getWidth(), 0, outer));
			} else if (placement == SwingConstants.BOTTOM) {
				return (new GradientPaint(0, 0, inner, 0, c.getHeight(), outer));
			} else {
				return (new GradientPaint(0, 0, outer, 0, c.getHeight(), inner));
			}
		}

		private JTabbedPane getTabbedPaneParent(Container c) {
			while (c != null) {
				if (c instanceof JTabbedPane)
					return (JTabbedPane) c;
				c = c.getParent();
			}
			return null;
		}

		public DefaultStyle() {
			this(true, true);
		}

		public DefaultStyle(boolean outerBorder, boolean contentBorder) {
			this.outerBorder = outerBorder;
			this.contentBorder = contentBorder;
		}

		private GradientPaint createBorderGradient(int tabPlacement) {
			if (tabPlacement == SwingConstants.BOTTOM) {
				return new GradientPaint(0, 0, borderNormalDark, 0, 25,
						borderNormalLight);
			} else if (tabPlacement == SwingConstants.LEFT) {
				return new GradientPaint(0, 0, borderNormalLight, 25, 0,
						borderNormalDark);
			} else if (tabPlacement == SwingConstants.RIGHT) {
				return new GradientPaint(0, 0, borderNormalDark, 25, 0,
						borderNormalLight);
			} else {
				return new GradientPaint(0, 0, borderNormalLight, 0, 25,
						borderNormalDark);
			}
		}

		@Override
		public void formatControlRow(JTabbedPane tabs, JPanel tabsContainer) {

			Border border;
			Paint paint = createBorderGradient(tabs.getTabPlacement());
			if (tabs.getTabPlacement() == SwingConstants.BOTTOM) {
				border = new PartialLineBorder(paint, true, true,
						tabs.getTabCount() == 0, true);
			} else if (tabs.getTabPlacement() == SwingConstants.LEFT) {
				border = new PartialLineBorder(paint, true,
						tabs.getTabCount() == 0, true, true);
			} else if (tabs.getTabPlacement() == SwingConstants.RIGHT) {
				border = new PartialLineBorder(paint, true, true, true,
						tabs.getTabCount() == 0);
			} else {
				border = new PartialLineBorder(paint, tabs.getTabCount() == 0,
						true, true, true);
			}

			tabsContainer.setUI(new GradientPanelUI(createContentGradient(tabs,
					false)));
			tabsContainer.setBorder(border);
		}

		@Override
		public void formatControlRowButton(JTabbedPane tabs,
				AbstractButton tabContainer, int tabIndex) {
			if (tabIndex >= 0) {
				Border border;
				int p = tabs.getTabPlacement();
				if (p == SwingConstants.LEFT) {
					border = new PartialLineBorder(createBorderGradient(p),
							tabIndex != 0, false, false, false);
					if (isOuterBorder()) {
						Paint paint = tabIndex == tabs.getSelectedIndex() ? borderOuterSelected
								: borderOuterUnselected;
						Border inner = new PartialLineBorder(paint, false,
								true, false, false);
						border = new CompoundBorder(border, inner);
					}
				} else if (p == SwingConstants.RIGHT) {
					border = new PartialLineBorder(createBorderGradient(p),
							tabIndex != 0, false, false, false);
					if (isOuterBorder()) {
						Paint paint = tabIndex == tabs.getSelectedIndex() ? borderOuterSelected
								: borderOuterUnselected;
						Border inner = new PartialLineBorder(paint, false,
								false, false, true);
						border = new CompoundBorder(border, inner);
					}
				} else {
					border = new PartialLineBorder(createBorderGradient(p),
							false, tabIndex != 0, false, false);
					if (isOuterBorder()) {
						Paint paint = tabIndex == tabs.getSelectedIndex() ? borderOuterSelected
								: borderOuterUnselected;
						Border inner = new PartialLineBorder(paint,
								p == SwingConstants.TOP, false,
								p == SwingConstants.BOTTOM, false);
						border = new CompoundBorder(border, inner);
					}
				}
				tabContainer.setBorder(border);
			}

			if (!(tabContainer.getUI() instanceof TabButtonUI)) {
				tabContainer.setUI(new TabButtonUI());
				tabContainer.putClientProperty(
						QButtonUI.PROPERTY_HORIZONTAL_POSITION,
						HorizontalPosition.MIDDLE);
				tabContainer.putClientProperty(
						QButtonUI.PROPERTY_VERTICAL_POSITION,
						VerticalPosition.MIDDLE);
				tabContainer.putClientProperty(
						QButtonUI.PROPERTY_STROKE_PAINTED, Boolean.FALSE);
			}
		}

		public boolean isOuterBorder() {
			return outerBorder;
		}

		@Override
		public void formatCloseButton(JTabbedPane tabs, JButton closeButton) {
			if (!(closeButton.getIcon() instanceof MinimalDuoToneCloseIcon)) {
				MinimalDuoToneCloseIcon icon = new MinimalDuoToneCloseIcon(
						closeButton);
				closeButton.setIcon(icon);
				DescendantMouseListener.installForParentOf(closeButton,
						icon.getParentRollover());
			}
			closeButton.setMargin(new Insets(0, 0, 0, 0));
			closeButton.setBorder(new EmptyBorder(0, 0, 0, 0));
		}

		@Override
		public void formatTabContent(JTabbedPane tabs, JComponent c) {
			if (contentBorder) {
				Border border;
				if (tabs.getTabPlacement() == SwingConstants.LEFT) {
					border = new PartialLineBorder(borderNormalDark, true,
							false, true, true);
				} else if (tabs.getTabPlacement() == SwingConstants.BOTTOM) {
					border = new PartialLineBorder(borderNormalDark, true,
							true, false, true);
				} else if (tabs.getTabPlacement() == SwingConstants.RIGHT) {
					border = new PartialLineBorder(borderNormalDark, true,
							true, true, false);
				} else {
					border = new PartialLineBorder(borderNormalDark, false,
							true, true, true);
				}
				c.setBorder(border);
			}
		}
	}

	/**
	 * An optional client property for a <code>java.util.List</code> of
	 * JComponents to always place on the trailing (right) side of the tabs.
	 */
	public static final String PROPERTY_TRAILING_COMPONENTS = BoxTabbedPaneUI.class
			.getName() + "#trailingComponents";

	/**
	 * An optional client property for a <code>java.util.List</code> of
	 * JComponents to always place on the leading (left) side of the tabs.
	 */
	public static final String PROPERTY_LEADING_COMPONENTS = BoxTabbedPaneUI.class
			.getName() + "#leadingComponents";

	/**
	 * This client property on tabs maps to the Integer index of that tab
	 * relative to the JTabbedPane.
	 */
	private static final String PROPERTY_TAB_INDEX = BoxTabbedPaneUI.class
			.getName() + "#tabIndex";

	/**
	 * This client property on JTabbedPane resolves to an internal Data object.
	 */
	private static final String PROPERTY_DATA = BoxTabbedPaneUI.class.getName()
			+ "#data";

	/**
	 * This optional property on JTabbedPane resolves to a Boolean used to
	 * indicate whether tabs should be closeable by default. Note if you have
	 * defined a custom tab component using
	 * {@link JTabbedPane#setTabComponentAt(int, Component)} then that component
	 * is used and this property is not automatically consulted. This value is
	 * assumed to be false by default.
	 */
	public static final String PROPERTY_CLOSEABLE_TABS = BoxTabbedPaneUI.class
			.getName() + "#closeableTabs";

	/**
	 * This optional property on JTabbedPane resolves to a Style object used to
	 * help format colors, borders, inner component UIs.
	 */
	public static final String PROPERTY_STYLE = BoxTabbedPaneUI.class.getName()
			+ "#style";

	/**
	 * This optional property on JTabbedPane resolves to a Boolean used to
	 * indicate whether the tab UI controls should be hidden if there is only
	 * one tab. This value is assumed to be false by default.
	 */
	public static final String PROPERTY_HIDE_SINGLE_TAB = BoxTabbedPaneUI.class
			.getName() + "#hideSingleTab";

	/**
	 * This caches a TabContainer on each Tab object. If we recreate the
	 * TabContainer with every refresh: the icons/animations may flicker a
	 * little.
	 */
	private static final String PROPERTY_TAB_CONTAINER = BoxTabbedPaneUI.class
			.getName() + "#tabContainer";

	private static class UIResourcePanel extends JPanel implements UIResource {
		private static final long serialVersionUID = 1L;

		UIResourcePanel(LayoutManager layoutManager) {
			super(layoutManager);
		}
	}

	private static class BoxTabbedPaneUILayoutManager implements LayoutManager {

		@Override
		public void addLayoutComponent(String name, Component comp) {
		}

		@Override
		public void removeLayoutComponent(Component comp) {
		}

		@Override
		public Dimension preferredLayoutSize(Container parent) {
			return getLayoutSize(parent, true);
		}

		@Override
		public Dimension minimumLayoutSize(Container parent) {
			return getLayoutSize(parent, false);
		}

		private Dimension getLayoutSize(Container parent, boolean preferred) {
			int tabPlacement = ((JTabbedPane) parent).getTabPlacement();
			boolean verticalPlacement = tabPlacement == JTabbedPane.TOP
					|| tabPlacement == JTabbedPane.BOTTOM;
			Dimension additional = new Dimension(0, 0);
			Dimension max = new Dimension(0, 0);
			for (int a = 0; a < parent.getComponentCount(); a++) {
				Component c = parent.getComponent(a);
				Dimension d = preferred ? c.getPreferredSize() : c
						.getMinimumSize();
				if (c instanceof UIResourcePanel) {
					if (verticalPlacement) {
						additional.height += d.height;
						additional.width = Math.max(additional.width, d.width);
					} else {
						additional.width += d.width;
						additional.height = Math.max(additional.height,
								d.height);
					}
				} else {
					max.width = Math.max(max.width, d.width);
					max.height = Math.max(max.height, d.height);
				}
			}
			if (verticalPlacement) {
				return new Dimension(Math.max(additional.width, max.width),
						additional.height + max.height);
			}
			return new Dimension(additional.width + max.width, Math.max(
					additional.height, max.height));
		}

		@Override
		public void layoutContainer(Container parent) {
			int y = 0;
			int x = 0;
			int width = parent.getWidth();
			int height = parent.getHeight();

			int tabPlacement = ((JTabbedPane) parent).getTabPlacement();
			for (int a = 0; a < parent.getComponentCount(); a++) {
				Component c = parent.getComponent(a);
				if (c instanceof UIResourcePanel && c.isVisible()) {
					Dimension d = c.getPreferredSize();
					if (tabPlacement == JTabbedPane.TOP) {
						c.setBounds(x, y, width, d.height);
						y += d.height;
						height -= d.height;
					} else if (tabPlacement == JTabbedPane.BOTTOM) {
						c.setBounds(x, height - d.height, width, d.height);
						height -= d.height;
					} else if (tabPlacement == JTabbedPane.LEFT) {
						c.setBounds(x, y, d.width, height);
						x += d.width;
						width -= d.width;
					} else if (tabPlacement == JTabbedPane.RIGHT) {
						c.setBounds(width - d.width, y, d.width, height);
						width -= d.width;
					}
				}
			}
			for (int a = 0; a < parent.getComponentCount(); a++) {
				Component c = parent.getComponent(a);
				if (!(c instanceof UIResourcePanel)) {
					c.setBounds(x, y, width, height);
				}
			}
		}

	}

	private static final String[] REFRESH_PROPERTIES = new String[] {
			"mnemonicAt", "displayedMnemonicIndexAt", "indexForTitle",
			"tabLayoutPolicy", "opaque", "background", "indexForTabComponent",
			"indexForNullComponent", "font", PROPERTY_HIDE_SINGLE_TAB,
			PROPERTY_CLOSEABLE_TABS };

	/**
	 * This is a cluster of data related to a specific JTabbedPane.
	 * <p>
	 * In theory a TabbedPaneUI can be applied to multiple JTabbedPanes. If that
	 * happens each pane gets its own Data object to control it.
	 */
	class Data {

		JPanel controlRow = new UIResourcePanel(new GridBagLayout());
		JPanel leadingComponents = new JPanel(new GridBagLayout());
		JPanel tabsContainer = new JPanel();
		JPanel trailingComponents = new JPanel(new GridBagLayout());
		JTabbedPane tabs;

		ChangeListener refreshChangeListener = new ChangeListener() {

			@Override
			public void stateChanged(ChangeEvent e) {
				refreshTabStates();
			}
		};

		PropertyChangeListener refreshPropertyListener = new PropertyChangeListener() {

			@Override
			public void propertyChange(PropertyChangeEvent evt) {
				refreshTabStates();
			}

		};

		PropertyChangeListener refreshExtraComponentsListener = new PropertyChangeListener() {

			@Override
			public void propertyChange(PropertyChangeEvent evt) {
				refreshExtraComponents();
			}

		};

		PropertyChangeListener refreshStyleListener = new PropertyChangeListener() {

			@Override
			public void propertyChange(PropertyChangeEvent evt) {
				refreshStyle();
				refreshTabStates();
			}

		};

		PropertyChangeListener refreshContentBorderListener = new PropertyChangeListener() {

			@Override
			public void propertyChange(PropertyChangeEvent evt) {
				refreshContentBorder();
			}

		};

		PropertyChangeListener tabPlacementPropertyListener = new PropertyChangeListener() {

			@Override
			public void propertyChange(PropertyChangeEvent evt) {
				relayoutControlRow();
				refreshExtraComponents();
				refreshTabStates();
				refreshStyle();
				refreshContentBorder();
			}

		};

		ContainerListener containerListener = new ContainerAdapter() {

			@Override
			public void componentAdded(ContainerEvent e) {
				refreshTabStates();
				refreshContentBorder();
				refreshStyle();
			}

			@Override
			public void componentRemoved(ContainerEvent e) {
				refreshTabStates();
				refreshContentBorder();
				refreshStyle();
			}

		};

		public Data(JTabbedPane tabs) {
			this.tabs = tabs;
			controlRow.setOpaque(false);
			leadingComponents.setOpaque(false);
			tabsContainer.setOpaque(false);
			trailingComponents.setOpaque(false);
		}

		public void install() {
			tabs.setLayout(new BoxTabbedPaneUILayoutManager());
			removeNonUIResources(tabs);
			tabs.add(controlRow);

			relayoutControlRow();

			for (String property : new String[] { PROPERTY_STYLE }) {
				tabs.addPropertyChangeListener(property, refreshStyleListener);
			}

			for (String property : new String[] { PROPERTY_TRAILING_COMPONENTS,
					PROPERTY_LEADING_COMPONENTS }) {
				tabs.addPropertyChangeListener(property,
						refreshExtraComponentsListener);
			}

			tabs.addPropertyChangeListener("tabPlacement",
					tabPlacementPropertyListener);

			for (String property : REFRESH_PROPERTIES) {
				tabs.addPropertyChangeListener(property,
						refreshPropertyListener);
			}
			tabs.addContainerListener(containerListener);
			tabs.getModel().addChangeListener(refreshChangeListener);

			refreshExtraComponents();
			refreshTabStates();
			refreshContentBorder();
			refreshStyle();
		}

		private void refreshContentBorder() {
			for (int a = 0; a < tabs.getComponentCount(); a++) {
				Component c = tabs.getComponent(a);
				if (!(c instanceof UIResource) && (c instanceof JComponent)) {
					getStyle(tabs).formatTabContent(tabs, (JComponent) c);
				}
			}
		}

		private void refreshStyle() {
			getStyle(tabs).formatControlRow(tabs, tabsContainer);
		}

		private void relayoutControlRow() {
			controlRow.removeAll();
			GridBagConstraints c = new GridBagConstraints();
			c.gridx = 0;
			c.gridy = 0;
			c.weightx = 0;
			c.weighty = 0;
			c.fill = GridBagConstraints.BOTH;
			if (tabs.getTabPlacement() == SwingConstants.LEFT) {
				controlRow.add(trailingComponents, c);
				c.gridy++;
				c.weighty = 1;
				controlRow.add(tabsContainer, c);
				c.gridy++;
				c.weighty = 0;
				controlRow.add(leadingComponents, c);
			} else if (tabs.getTabPlacement() == SwingConstants.RIGHT) {
				controlRow.add(leadingComponents, c);
				c.gridy++;
				c.weighty = 1;
				controlRow.add(tabsContainer, c);
				c.gridy++;
				c.weighty = 0;
				controlRow.add(trailingComponents, c);
			} else {
				controlRow.add(leadingComponents, c);
				c.gridx++;
				c.weightx = 1;
				controlRow.add(tabsContainer, c);
				c.gridx++;
				c.weightx = 0;
				controlRow.add(trailingComponents, c);
			}

			controlRow.revalidate();
		}

		int lastTabPlacement = -1;

		@SuppressWarnings("unchecked")
		private void refreshExtraComponents() {
			List<JComponent> newLeadingComponents = (List<JComponent>) tabs
					.getClientProperty(PROPERTY_LEADING_COMPONENTS);
			List<JComponent> newTrailingComponents = (List<JComponent>) tabs
					.getClientProperty(PROPERTY_TRAILING_COMPONENTS);
			int tabPlacement = tabs.getTabPlacement();
			boolean forceReinstall = tabPlacement != lastTabPlacement;
			lastTabPlacement = tabPlacement;
			installExtraComponents(leadingComponents, newLeadingComponents,
					forceReinstall);
			installExtraComponents(trailingComponents, newTrailingComponents,
					forceReinstall);
		}

		ComponentListener extraComponentListener = new ComponentAdapter() {

			@Override
			public void componentShown(ComponentEvent e) {
				refreshExtraContainerVisibility();
			}

			@Override
			public void componentHidden(ComponentEvent e) {
				refreshExtraContainerVisibility();
			}

		};

		private void installExtraComponents(Container container,
				List<JComponent> components, boolean forceReinstall) {
			if (components == null)
				components = new ArrayList<>();
			Component[] oldComponents = container.getComponents();
			if (!Arrays.asList(oldComponents).equals(components)) {
				forceReinstall = true;
			}

			if (forceReinstall) {
				container.removeAll();
				GridBagConstraints c = new GridBagConstraints();
				c.gridx = 0;
				c.gridy = 100;
				c.weightx = 1;
				c.weighty = 1;
				c.fill = GridBagConstraints.BOTH;
				for (JComponent jc : components) {
					container.add(jc, c);
					if (tabs.getTabPlacement() == SwingConstants.LEFT) {
						c.gridy--;
					} else if (tabs.getTabPlacement() == SwingConstants.RIGHT) {
						c.gridy++;
					} else {
						c.gridx++;
					}
					jc.removeComponentListener(extraComponentListener);
					jc.addComponentListener(extraComponentListener);

					for (Component oldComponent : oldComponents) {
						if (components.contains(oldComponent)) {
							oldComponent
									.removeComponentListener(extraComponentListener);
						}
					}
				}
				container.revalidate();
			}
			refreshExtraContainerVisibility();
		}

		private void refreshExtraContainerVisibility() {
			leadingComponents
					.setVisible(containsVisibleComponent(leadingComponents));
			trailingComponents
					.setVisible(containsVisibleComponent(trailingComponents));
		}

		private boolean containsVisibleComponent(Container container) {
			for (Component c : container.getComponents()) {
				if (c.isVisible())
					return true;
			}
			return false;
		}

		public void uninstall() {
			tabs.remove(controlRow);

			for (String property : new String[] { PROPERTY_STYLE }) {
				tabs.removePropertyChangeListener(property,
						refreshStyleListener);
			}

			for (String property : new String[] { PROPERTY_TRAILING_COMPONENTS,
					PROPERTY_LEADING_COMPONENTS }) {
				tabs.removePropertyChangeListener(property,
						refreshExtraComponentsListener);
			}

			tabs.removePropertyChangeListener("tabPlacement",
					tabPlacementPropertyListener);

			for (String property : REFRESH_PROPERTIES) {
				tabs.removePropertyChangeListener(property,
						refreshPropertyListener);
			}
			tabs.removeContainerListener(containerListener);
			tabs.getModel().removeChangeListener(refreshChangeListener);
		}

		private void removeNonUIResources(Container c) {
			for (Component comp : c.getComponents()) {
				if (!(comp instanceof UIResource)) {
					c.remove(comp);
				}
			}
		}

		protected void refreshTabStates() {
			List<Component> newTabs = new ArrayList<>();
			for (int a = 0; a < tabs.getTabCount(); a++) {
				JComponent tab = (JComponent) tabs.getTabComponentAt(a);
				if (tab == null)
					tab = getDefaultTab(a);

				TabContainer tabContainer = (TabContainer) tab
						.getClientProperty(PROPERTY_TAB_CONTAINER);
				if (tabContainer == null) {
					tabContainer = new TabContainer(tabs, a, tab);
					tab.putClientProperty(PROPERTY_TAB_CONTAINER, tabContainer);
				}

				newTabs.add(tabContainer);
				tab.putClientProperty(PROPERTY_TAB_INDEX, a);
				getStyle(tabs).formatControlRowButton(tabs, tabContainer, a);
			}

			Boolean hideSingleTab = (Boolean) tabs
					.getClientProperty(PROPERTY_HIDE_SINGLE_TAB);
			if (hideSingleTab == null)
				hideSingleTab = Boolean.FALSE;
			controlRow.setVisible(!(tabs.getTabCount() <= 1 && hideSingleTab));

			if (!(tabsContainer.getLayout() instanceof SplayedLayout)) {
				SplayedLayout l = new SplayedLayout(true) {

					@Override
					protected Collection<JComponent> getEmphasizedComponents(
							JComponent container) {
						Collection<JComponent> returnValue = super
								.getEmphasizedComponents(container);

						for (Component c : container.getComponents()) {
							if (c instanceof AbstractButton) {
								AbstractButton ab = (AbstractButton) c;
								if (ab.isSelected())
									returnValue.add(ab);
							}
						}
						return returnValue;
					}

				};
				tabsContainer.setLayout(l);
			}
			int orientation = tabs.getTabPlacement() == SwingConstants.LEFT
					|| tabs.getTabPlacement() == SwingConstants.RIGHT ? SwingConstants.VERTICAL
					: SwingConstants.HORIZONTAL;
			((SplayedLayout) tabsContainer.getLayout()).setOrientation(null,
					orientation);

			setComponents(tabsContainer, newTabs);

			tabsContainer.revalidate();
		}

		/**
		 * This is basically
		 * <code>java.awt.Container.setComponents(Component[])</code>. This is
		 * functionally the same as removing all of a container's children and
		 * then adding them back again, but this method makes individual changes
		 * to minimize the number of container/hierarchy changes that listeners
		 * hear.
		 */
		private void setComponents(Container container,
				List<Component> components) {
			Component[] oldComponents = container.getComponents();
			for (int a = 0; a < oldComponents.length; a++) {
				if (!components.contains(oldComponents[a])) {
					container.remove(oldComponents[a]);
				}
			}
			oldComponents = container.getComponents();
			int oldCtr = 0;
			int newCtr = 0;
			while (newCtr < components.size()) {
				Component oldComponent = oldCtr < oldComponents.length ? oldComponents[oldCtr]
						: null;
				if (oldComponent != components.get(newCtr)) {
					container.add(components.get(newCtr), oldCtr);
				} else {
					oldCtr++;
				}
				newCtr++;
			}
		}

		Map<Integer, DefaultTab> tabMap = new HashMap<>();

		private DefaultTab getDefaultTab(int a) {
			DefaultTab returnValue = tabMap.get(a);
			if (returnValue == null) {
				returnValue = createDefaultTab(tabs, a);
				tabMap.put(a, returnValue);
			} else {
				returnValue.decorateLabel(a);
			}
			return returnValue;
		}

		public int getTabRunCount() {
			return 1;
		}

		public Rectangle getTabBounds(int index) {
			for (int a = 0; a < controlRow.getComponentCount(); a++) {
				Component c = controlRow.getComponent(a);
				if (c instanceof JComponent) {
					JComponent jc = (JComponent) c;
					Integer i = (Integer) jc
							.getClientProperty(PROPERTY_TAB_INDEX);
					if (i != null && i == index)
						return SwingUtilities.convertRectangle(controlRow,
								jc.getBounds(), tabs);
				}
			}
			return null;
		}

		public int getTabForCoordinate(int x, int y) {
			Component c = SwingUtilities.getDeepestComponentAt(tabs, x, y);
			while (c != tabs) {
				if (c instanceof JComponent) {
					JComponent jc = (JComponent) c;
					Integer i = (Integer) jc
							.getClientProperty(PROPERTY_TAB_INDEX);
					if (i != null)
						return i;
				}
				c = c.getParent();
			}
			return -1;
		}
	}

	static class TabContainer extends AbstractButton {
		/**
		 * This converts MouseEvent sources to an AbstractButton.
		 */
		private static class MyBasicButtonListener extends BasicButtonListener {

			AbstractButton button;

			public MyBasicButtonListener(AbstractButton b) {
				super(b);
				button = b;
			}

			@Override
			public void mouseMoved(MouseEvent e) {
				super.mouseMoved(convert(e));
			}

			@Override
			public void mouseDragged(MouseEvent e) {
				super.mouseDragged(convert(e));
			}

			@Override
			public void mouseClicked(MouseEvent e) {
				super.mouseClicked(convert(e));
			}

			@Override
			public void mousePressed(MouseEvent e) {
				super.mousePressed(convert(e));
			}

			@Override
			public void mouseReleased(MouseEvent e) {
				super.mouseReleased(convert(e));
			}

			@Override
			public void mouseEntered(MouseEvent e) {
				super.mouseEntered(convert(e));
			}

			@Override
			public void mouseExited(MouseEvent e) {
				super.mouseExited(convert(e));
			}

			private MouseEvent convert(MouseEvent e) {
				if (e.getSource() == button)
					return e;
				return SwingUtilities.convertMouseEvent(e.getComponent(), e,
						button);
			}
		}

		JTabbedPane tabs;
		int tabIndex;

		public TabContainer(JTabbedPane tabs, int tabIndex, JComponent tab) {
			this.tabs = tabs;
			this.tabIndex = tabIndex;

			setLayout(new GridBagLayout());
			GridBagConstraints c = new GridBagConstraints();
			c.gridx = 0;
			c.gridy = 0;
			c.weightx = 1;
			c.weighty = 1;
			c.fill = GridBagConstraints.BOTH;
			add(tab, c);
			setFocusable(true);
			setFocusPainted(true);
			setModel(new ToggleButtonModel());
			refreshSelectedState();
			getModel().addChangeListener(new ChangeListener() {
				@Override
				public void stateChanged(ChangeEvent e) {
					refreshSelectedState();
				}
			});

			tabs.addChangeListener(new ChangeListener() {
				@Override
				public void stateChanged(ChangeEvent e) {
					refreshSelectedState();
				}
			});

			BasicButtonListener buttonListener = new MyBasicButtonListener(this);
			buttonListener.installKeyboardActions(this);
			addFocusListener(buttonListener);
			DescendantListener
					.addMouseListener(this, (MouseListener) buttonListener,
							false, AbstractButton.class);
			DescendantListener.addMouseListener(this,
					(MouseMotionListener) buttonListener, false,
					AbstractButton.class);

			addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent e) {
					TabContainer.this.tabs
							.setSelectedIndex(TabContainer.this.tabIndex);
				}

			});

			setFocusable(true);
			setRequestFocusEnabled(false);
		}

		private void refreshSelectedState() {
			getModel().setSelected(tabs.getSelectedIndex() == tabIndex);
		}

		private static final long serialVersionUID = 1L;

	}

	public static class DefaultTab extends JPanel {
		private static final long serialVersionUID = 1L;

		protected final JTabbedPane tabs;
		private JLabel label = new JLabel();
		private JButton closeButton = new JButton();
		private int tabIndex = -1;

		private PropertyChangeListener closeableTabsListener = new PropertyChangeListener() {

			@Override
			public void propertyChange(PropertyChangeEvent evt) {
				refreshCloseableButton();
			}

		};

		public DefaultTab(JTabbedPane tabs, int tabIndex) {
			setLayout(new GridBagLayout());
			this.tabs = tabs;

			closeButton.setContentAreaFilled(false);
			closeButton.setBorderPainted(false);

			label.setHorizontalAlignment(SwingConstants.CENTER);

			Font font = UIManager.getFont("TabbedPane.smallFont");
			if (font != null)
				label.setFont(font);

			setOpaque(false);
			decorateLabel(tabIndex);

			closeButton.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent e) {
					doCloseTab(DefaultTab.this.tabIndex);
				}

			});

			tabs.addPropertyChangeListener(PROPERTY_CLOSEABLE_TABS,
					closeableTabsListener);
			refreshCloseableButton();
		}

		/**
		 * Remove a tab at a given index.
		 * <p>
		 * The default implementation simply calls
		 * <code>tabs.removeTabAt(tabIndex)</code>, but subclasses can override
		 * this as needed. For example, you may need to prompt the user to
		 * confirm discarding unsaved changes.
		 * 
		 * @param tabIndex
		 *            the index of the tab to close.
		 */
		protected void doCloseTab(int tabIndex) {
			tabs.removeTabAt(tabIndex);
		}

		private void refreshCloseableButton() {
			Boolean b = (Boolean) tabs
					.getClientProperty(PROPERTY_CLOSEABLE_TABS);
			if (b == null)
				b = Boolean.FALSE;
			closeButton.setVisible(b);
			int i;
			if (b) {
				Dimension d = closeButton.getPreferredSize();
				i = Math.max(d.width, d.height);
			} else {
				i = 0;
			}
			i += 2;
			label.setBorder(new EmptyBorder(0, i, 0, i));

			getStyle(tabs).formatCloseButton(tabs, closeButton);
		}

		protected JLabel getLabel() {
			return label;
		}

		protected JButton getCloseButton() {
			return closeButton;
		}

		protected GridBagConstraints createLabelConstraints() {
			GridBagConstraints labelConstraints = new GridBagConstraints();
			labelConstraints.gridx = 0;
			labelConstraints.gridy = 0;
			labelConstraints.weightx = 1;
			labelConstraints.weighty = 1;
			labelConstraints.fill = GridBagConstraints.BOTH;
			labelConstraints.insets = new Insets(3, 3, 3, 3);
			return labelConstraints;
		}

		protected GridBagConstraints createCloseButtonConstraints() {
			GridBagConstraints closeButtonConstraints = new GridBagConstraints();
			closeButtonConstraints.gridx = 0;
			closeButtonConstraints.gridy = 0;
			closeButtonConstraints.weightx = 1;
			closeButtonConstraints.weighty = 1;
			closeButtonConstraints.fill = GridBagConstraints.NONE;
			if (tabs.getTabPlacement() == SwingConstants.LEFT) {
				closeButtonConstraints.anchor = GridBagConstraints.SOUTH;
			} else if (tabs.getTabPlacement() == SwingConstants.RIGHT) {
				closeButtonConstraints.anchor = GridBagConstraints.NORTH;
			} else {
				closeButtonConstraints.anchor = GridBagConstraints.WEST;
			}
			if (tabs.getTabPlacement() == SwingConstants.LEFT) {
				closeButtonConstraints.insets = new Insets(0, 0, 3, 0);
			} else if (tabs.getTabPlacement() == SwingConstants.RIGHT) {
				closeButtonConstraints.insets = new Insets(3, 0, 0, 0);
			} else {
				closeButtonConstraints.insets = new Insets(0, 3, 0, 0);
			}
			return closeButtonConstraints;
		}

		public void decorateLabel(int index) {
			tabIndex = index;
			Color background = tabs.getBackgroundAt(index);
			setBackground(background);

			Icon disabledIcon = tabs.getDisabledIconAt(index);
			Icon icon = tabs.getIconAt(index);

			if (!tabs.isEnabledAt(index) || !tabs.isEnabled()) {
				if (disabledIcon != null)
					icon = disabledIcon;
			}
			getLabel().setIcon(icon);

			// TODO: we don't current support mnemonics
			tabs.getDisplayedMnemonicIndexAt(index);
			tabs.getMnemonicAt(index);

			Color foreground = tabs.getForegroundAt(index);
			if (foreground == null)
				foreground = UIManager.getColor("Label.foreground");
			getLabel().setForeground(foreground);

			setToolTipText(tabs.getToolTipTextAt(index));

			String title = tabs.getTitleAt(index);
			if (title == null)
				title = "";
			getLabel().setText(title);

			getStyle(tabs).formatCloseButton(tabs, closeButton);

			addChild(tabs.getTabPlacement());
		}

		/**
		 * Add the label to this DefaultTab; if necessary wrap it inside a
		 * RotatedComponent.
		 * 
		 * @param tabPlacement
		 *            the tab placement (SwingConstants.TOP,
		 *            SwingConstants.LEFT, SwingConstants.BOTTOM,
		 *            SwingConstants.RIGHT)
		 */
		protected void addChild(int tabPlacement) {
			RotatedPanel.Rotation rotation = RotatedPanel.Rotation.NONE;
			if (tabPlacement == SwingConstants.LEFT) {
				rotation = RotatedPanel.Rotation.COUNTER_CLOCKWISE;
			} else if (tabPlacement == SwingConstants.RIGHT) {
				rotation = RotatedPanel.Rotation.CLOCKWISE;
			}

			Component child = getComponentCount() == 0 ? null : getComponent(0);
			if (rotation == RotatedPanel.Rotation.NONE) {
				if (child == getLabel()) {
					return;
				} else {
					removeAll();
					add(getCloseButton(), createCloseButtonConstraints());
					add(getLabel(), createLabelConstraints());
					revalidate();
				}
			} else {
				if (child instanceof RotatedPanel) {
					RotatedPanel rc = (RotatedPanel) child;
					rc.setRotation(rotation);
				} else {
					removeAll();
					add(getCloseButton(), createCloseButtonConstraints());
					add(new RotatedPanel(getLabel(), rotation),
							createLabelConstraints());
					revalidate();
				}
			}
		}

	}

	protected Data getData(JTabbedPane tabs) {
		Data d = (Data) tabs.getClientProperty(PROPERTY_DATA);
		if (d == null) {
			d = new Data(tabs);
			tabs.putClientProperty(PROPERTY_DATA, d);
		}
		return d;
	}

	public DefaultTab createDefaultTab(JTabbedPane tabs, int a) {
		return new DefaultTab(tabs, a);
	}

	@Override
	public void installUI(JComponent c) {
		super.installUI(c);
		Data data = getData((JTabbedPane) c);
		data.install();
	}

	@Override
	public void uninstallUI(JComponent c) {
		super.uninstallUI(c);
		Data data = getData((JTabbedPane) c);
		data.uninstall();
	}

	@Override
	public boolean contains(JComponent c, int x, int y) {
		return x >= 0 && x < c.getWidth() && y >= 0 && y < c.getHeight();
	}

	@Override
	public int getBaseline(JComponent c, int width, int height) {
		// TODO
		return -1;
	}

	@Override
	public BaselineResizeBehavior getBaselineResizeBehavior(JComponent c) {
		return BaselineResizeBehavior.OTHER;
	}

	@Override
	public int tabForCoordinate(JTabbedPane pane, int x, int y) {
		return getData(pane).getTabForCoordinate(x, y);
	}

	@Override
	public Rectangle getTabBounds(JTabbedPane pane, int index) {
		return getData(pane).getTabBounds(index);
	}

	@Override
	public int getTabRunCount(JTabbedPane pane) {
		return getData(pane).getTabRunCount();
	}

}