/*
 * Copyright (c) 2005-2010 Flamingo 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 Flamingo 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.flamingo.api.ribbon.resize;

import java.awt.Insets;
import java.util.*;

import javax.swing.JComponent;

import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
import org.pushingpixels.flamingo.api.common.CommandButtonDisplayState;
import org.pushingpixels.flamingo.api.ribbon.*;
import org.pushingpixels.flamingo.internal.ui.ribbon.*;

/**
 * The core resize policies. Provides a number of built in resize policies that
 * respect the application element priorities passed to
 * {@link JRibbonBand#addCommandButton(org.pushingpixels.flamingo.api.common.AbstractCommandButton, org.pushingpixels.flamingo.api.ribbon.RibbonElementPriority)}
 * and
 * {@link JRibbonBand#addRibbonGallery(String, java.util.List, java.util.Map, int, int, org.pushingpixels.flamingo.api.ribbon.RibbonElementPriority)}
 * APIs. There are three types of built in resize policies: </p>
 * 
 * <ul>
 * <li>Resize policies for the {@link JFlowRibbonBand}s. The {@link FlowTwoRows}
 * and {@link FlowThreeRows} allow placing the flow ribbon band content in two
 * and three rows respectively.</li>
 * <li>Resize policies for the {@link JRibbonBand}s. The
 * {@link BaseCoreRibbonBandResizePolicy} is the base class for these policies.
 * These policies respect the {@link RibbonElementPriority} associated on
 * command buttons and ribbon galleries in
 * {@link RibbonBandResizePolicy#getPreferredWidth(int, int)} and
 * {@link RibbonBandResizePolicy#install(int, int)}. While
 * {@link RibbonBandResizePolicy#install(int, int)} call on a
 * {@link JFlowRibbonBand} only changes the bounds of the flow components, this
 * call on a {@link JRibbonBand} can also change the display state of the
 * command buttons (with
 * {@link AbstractCommandButton#setDisplayState(org.pushingpixels.flamingo.api.common.CommandButtonDisplayState)}
 * ) and the number of visible buttons in the ribbon galleries.</li>
 * <li>The collapsed policy that replaces the entire content of the ribbon band
 * with a single popup button. This is done when there is not enough horizontal
 * space to show the content of the ribbon band under the most restrictive
 * resize policy. Activating the popup button will show the original content
 * under the most permissive resize policy in a popup. This policy is
 * implemented in the {@link IconRibbonBandResizePolicy}.</li>
 * </ul>
 * 
 * <p>
 * In addition to the specific resize policies, this class provides three core
 * resize policies lists for {@link JRibbonBand}s:
 * </p>
 * 
 * <ul>
 * <li>{@link #getCorePoliciesPermissive(JRibbonBand)} returns a list that
 * starts with a resize policy that shows all command buttons in the
 * {@link CommandButtonDisplayState#BIG} and ribbon galleries with the largest
 * number of visible buttons, fully utilizing the available screen space.</li>
 * <li>{@link #getCorePoliciesRestrictive(JRibbonBand)} returns a list that
 * starts with a resize policy that respects the associated ribbon element
 * priority set on the specific components.</li>
 * <li> {@link #getCorePoliciesNone(JRibbonBand)} returns a list that only has a
 * <code>mirror</code> resize policy that respects the associated ribbon element
 * priority set on the specific components.</li>
 * </ul>
 * 
 * <p>
 * Note that as mentioned above, all the three lists above have the
 * <code>collapsed</code> policy as their last element.
 * </p>
 * 
 * <p>
 * In addition, the
 * {@link #getCoreFlowPoliciesRestrictive(JFlowRibbonBand, int)} returns a
 * restrictive resize policy for {@link JFlowRibbonBand}s. The list starts with
 * the two-row policy, goes to the three-row policy and then finally to the
 * collapsed policy.
 * </p>
 * 
 * @author Kirill Grouchnikov
 */
public class CoreRibbonResizePolicies {
	/**
	 * Maps the element priority associated with a ribbon band component to the
	 * element priority assigned by the specific resize policy.
	 * 
	 * @author Kirill Grouchnikov
	 */
	static interface Mapping {
		/**
		 * Maps the element priority associated with a ribbon band component to
		 * the element priority assigned by the specific resize policy.
		 * 
		 * @param priority
		 *            The element priority associated with a ribbon band
		 *            component
		 * @return The element priority assigned by the specific resize policy.
		 */
		RibbonElementPriority map(RibbonElementPriority priority);
	}

	/**
	 * Returns a list that starts with a resize policy that shows all command
	 * buttons in the {@link CommandButtonDisplayState#BIG} and ribbon galleries
	 * with the largest number of visible buttons. The last entry is the
	 * {@link IconRibbonBandResizePolicy}.
	 * 
	 * @param ribbonBand
	 *            Ribbon band.
	 * @return The permissive list of core ribbon band resize policies.
	 */
	public static List<RibbonBandResizePolicy> getCorePoliciesPermissive(
			JRibbonBand ribbonBand) {
		List<RibbonBandResizePolicy> result = new ArrayList<RibbonBandResizePolicy>();
		result.add(new CoreRibbonResizePolicies.None(ribbonBand
				.getControlPanel()));
		result.add(new CoreRibbonResizePolicies.Low2Mid(ribbonBand
				.getControlPanel()));
		result.add(new CoreRibbonResizePolicies.Mid2Mid(ribbonBand
				.getControlPanel()));
		result.add(new CoreRibbonResizePolicies.Mirror(ribbonBand
				.getControlPanel()));
		result.add(new CoreRibbonResizePolicies.Mid2Low(ribbonBand
				.getControlPanel()));
		result.add(new CoreRibbonResizePolicies.High2Mid(ribbonBand
				.getControlPanel()));
		result.add(new CoreRibbonResizePolicies.High2Low(ribbonBand
				.getControlPanel()));
		result
				.add(new IconRibbonBandResizePolicy(ribbonBand
						.getControlPanel()));
		return result;
	}

	/**
	 * Returns a list that starts with a resize policy that respects the
	 * associated ribbon element priority set on the specific components. The
	 * last entry is the {@link IconRibbonBandResizePolicy}.
	 * 
	 * @param ribbonBand
	 *            Ribbon band.
	 * @return The restrictive list of core ribbon band resize policies.
	 */
	public static List<RibbonBandResizePolicy> getCorePoliciesRestrictive(
			JRibbonBand ribbonBand) {
		List<RibbonBandResizePolicy> result = new ArrayList<RibbonBandResizePolicy>();
		result.add(new CoreRibbonResizePolicies.Mirror(ribbonBand
				.getControlPanel()));
		result.add(new CoreRibbonResizePolicies.Mid2Low(ribbonBand
				.getControlPanel()));
		result.add(new CoreRibbonResizePolicies.High2Mid(ribbonBand
				.getControlPanel()));
		result.add(new CoreRibbonResizePolicies.High2Low(ribbonBand
				.getControlPanel()));
		result
				.add(new IconRibbonBandResizePolicy(ribbonBand
						.getControlPanel()));
		return result;
	}

	/**
	 * Returns a list that only has a <code>mirror</code> resize policy that
	 * respects the associated ribbon element priority set on the specific
	 * components. The last entry is the {@link IconRibbonBandResizePolicy}.
	 * 
	 * @param ribbonBand
	 *            Ribbon band.
	 * @return The mirror list of core ribbon band resize policies.
	 */
	public static List<RibbonBandResizePolicy> getCorePoliciesNone(
			JRibbonBand ribbonBand) {
		List<RibbonBandResizePolicy> result = new ArrayList<RibbonBandResizePolicy>();
		result.add(new CoreRibbonResizePolicies.Mirror(ribbonBand
				.getControlPanel()));
		result
				.add(new IconRibbonBandResizePolicy(ribbonBand
						.getControlPanel()));
		return result;
	}

	/**
	 * The base class for mapping-based core resize policies.
	 * 
	 * @author Kirill Grouchnikov
	 */
	protected static abstract class BaseCoreRibbonBandResizePolicy extends
			BaseRibbonBandResizePolicy<JBandControlPanel> {
		/**
		 * The element priority mapping.
		 */
		protected Mapping mapping;

		/**
		 * Creates a new resize policy.
		 * 
		 * @param controlPanel
		 *            The control panel of the associated ribbon band.
		 * @param mapping
		 *            The element priority mapping.
		 */
		protected BaseCoreRibbonBandResizePolicy(
				JBandControlPanel controlPanel, Mapping mapping) {
			super(controlPanel);
			this.mapping = mapping;
		}

		/**
		 * Returns the total width of the specified buttons.
		 * 
		 * @param gap
		 *            Inter component gap.
		 * @param bigButtons
		 *            List of buttons in big display state.
		 * @param mediumButtons
		 *            List of buttons in medium display state.
		 * @param smallButtons
		 *            List of buttons in small display state.
		 * @return Total width of the specified buttons.
		 */
		protected int getWidth(int gap,
				java.util.List<AbstractCommandButton> bigButtons,
				java.util.List<AbstractCommandButton> mediumButtons,
				java.util.List<AbstractCommandButton> smallButtons) {
			int result = 0;
			boolean hasLeadingContent = false;
			for (AbstractCommandButton top : bigButtons) {
				if (hasLeadingContent) {
					result += gap;
				}
				result += getPreferredWidth(top, RibbonElementPriority.TOP);
				hasLeadingContent = true;
			}

			int medSize = mediumButtons.size();
			if (medSize > 0) {
				// try to move buttons from low to med to make
				// three-somes.
				while (((mediumButtons.size() % 3) != 0)
						&& (smallButtons.size() > 0)) {
					AbstractCommandButton low = smallButtons.remove(0);
					mediumButtons.add(low);
				}
			}

			// at this point, mediumButtons list contains either
			// threesomes, or there are no buttons in lowButtons.
			int index3 = 0;
			int maxWidth3 = 0;
			for (AbstractCommandButton medium : mediumButtons) {
				int medWidth = getPreferredWidth(medium,
						RibbonElementPriority.MEDIUM);
				maxWidth3 = Math.max(maxWidth3, medWidth);
				index3++;

				if (index3 == 3) {
					// last button in threesome
					index3 = 0;
					if (hasLeadingContent) {
						result += gap;
					}
					result += maxWidth3;
					hasLeadingContent = true;
					maxWidth3 = 0;
				}
			}
			// at this point, maxWidth3 may be non-zero. We can safely
			// add it, since in this case there will be no buttons
			// left in mediumButtons
			if (maxWidth3 > 0) {
				if (hasLeadingContent) {
					result += gap;
				}
				result += maxWidth3;
				hasLeadingContent = true;
			}

			index3 = 0;
			maxWidth3 = 0;
			for (AbstractCommandButton low : smallButtons) {
				int lowWidth = getPreferredWidth(low, RibbonElementPriority.LOW);
				maxWidth3 = Math.max(maxWidth3, lowWidth);
				index3++;

				if (index3 == 3) {
					// last button in threesome
					index3 = 0;
					if (hasLeadingContent) {
						result += gap;
					}
					result += maxWidth3;
					hasLeadingContent = true;
					maxWidth3 = 0;
				}
			}
			// at this point, maxWidth3 may be non-zero. We can safely
			// add it, since in this case there will be no buttons left
			if (maxWidth3 > 0) {
				if (hasLeadingContent) {
					result += gap;
				}
				result += maxWidth3;
				hasLeadingContent = true;
			}

			return result;
		}

		/**
		 * Returns the preferred width of the specified command button under the
		 * specified display priority.
		 * 
		 * @param button
		 *            Command button.
		 * @param buttonDisplayPriority
		 *            Button display priority.
		 * @return The preferred width of the specified command button under the
		 *         specified display priority.
		 */
		private int getPreferredWidth(AbstractCommandButton button,
				RibbonElementPriority buttonDisplayPriority) {
			CommandButtonDisplayState displayState = null;
			switch (buttonDisplayPriority) {
			case TOP:
				displayState = CommandButtonDisplayState.BIG;
				break;
			case MEDIUM:
				displayState = CommandButtonDisplayState.MEDIUM;
				break;
			case LOW:
				displayState = CommandButtonDisplayState.SMALL;
				break;
			}
			return displayState.createLayoutManager(button).getPreferredSize(
					button).width;
		}

		@Override
		public int getPreferredWidth(int availableHeight, int gap) {
			int result = 0;

			Insets ins = this.controlPanel.getInsets();

			for (JBandControlPanel.ControlPanelGroup controlPanelGroup : this.controlPanel
					.getControlPanelGroups()) {
				boolean isCoreContent = controlPanelGroup.isCoreContent();
				if (isCoreContent) {
					List<JRibbonComponent> ribbonComps = controlPanelGroup
							.getRibbonComps();
					Map<JRibbonComponent, Integer> ribbonCompRowSpans = controlPanelGroup
							.getRibbonCompsRowSpans();
					// if a group has a title, then the core components in that
					// group start from the second row
					int startRowIndex = (controlPanelGroup.getGroupTitle() == null) ? 0
							: 1;
					int rowIndex = startRowIndex;
					int maxWidthInCurrColumn = 0;
					for (int i = 0; i < ribbonComps.size(); i++) {
						JRibbonComponent ribbonComp = ribbonComps.get(i);
						int rowSpan = ribbonCompRowSpans.get(ribbonComp);

						// do we need to start a new column?
						int nextRowIndex = rowIndex + rowSpan;
						if (nextRowIndex > 3) {
							result += maxWidthInCurrColumn;
							result += gap;
							maxWidthInCurrColumn = 0;
							rowIndex = startRowIndex;
						}

						RibbonElementPriority targetPriority = RibbonElementPriority.TOP;
						if (ribbonComp.isResizingAware()) {
							targetPriority = this.mapping
									.map(RibbonElementPriority.TOP);
						}
						int prefWidth = ribbonComp.getUI().getPreferredSize(
								targetPriority).width;
						maxWidthInCurrColumn = Math.max(maxWidthInCurrColumn,
								prefWidth);
						rowIndex += rowSpan;
					}
					if ((rowIndex > 0) && (rowIndex <= 3)) {
						result += maxWidthInCurrColumn;
						result += gap;
					}
				} else {
					int galleryAvailableHeight = availableHeight - ins.top
							- ins.bottom;
					// ribbon galleries
					result += this.getPreferredGalleryWidth(controlPanelGroup,
							galleryAvailableHeight, gap);

					// ribbon buttons
					result += this.getPreferredButtonWidth(controlPanelGroup,
							gap);
				}

				result += gap * 3 / 2;
			}
			// no gap after the last group
			result -= gap * 3 / 2;

			// control panel insets
			result += ins.left + ins.right;
			result += gap;

			return result;
		}

		/**
		 * Returns the preferred width of all the buttons in the specified
		 * control panel group.
		 * 
		 * @param controlPanelGroup
		 *            A single control panel group in the associated ribbon
		 *            band.
		 * @param gap
		 *            Inter component gap.
		 * @return The preferred width of all the buttons in the specified
		 *         control panel group.
		 */
		protected int getPreferredButtonWidth(
				JBandControlPanel.ControlPanelGroup controlPanelGroup, int gap) {
			Map<RibbonElementPriority, List<AbstractCommandButton>> mapped = new HashMap<RibbonElementPriority, List<AbstractCommandButton>>();
			for (RibbonElementPriority rep : RibbonElementPriority.values()) {
				mapped.put(rep, new ArrayList<AbstractCommandButton>());
			}

			for (RibbonElementPriority elementPriority : RibbonElementPriority
					.values()) {
				// map the priority
				RibbonElementPriority mappedPriority = mapping
						.map(elementPriority);
				for (AbstractCommandButton button : controlPanelGroup
						.getRibbonButtons(elementPriority)) {
					// and add the button to a list based on the mapped priority
					mapped.get(mappedPriority).add(button);
				}
			}

			// at this point, the lists in the 'mapped' contain the buttons
			// grouped by the priority computed by the resize policy.
			return this.getWidth(gap, mapped.get(RibbonElementPriority.TOP),
					mapped.get(RibbonElementPriority.MEDIUM), mapped
							.get(RibbonElementPriority.LOW));

		}

		/**
		 * Returns the preferred width of all the ribbon galleries in the
		 * specified control panel group.
		 * 
		 * @param controlPanelGroup
		 *            A single control panel group in the associated ribbon
		 *            band.
		 * @param galleryAvailableHeight
		 *            Available height for the ribbon galleries.
		 * @param gap
		 *            Inter component gap.
		 * @return The preferred width of all the ribbon galleries in the
		 *         specified control panel group.
		 */
		private int getPreferredGalleryWidth(
				JBandControlPanel.ControlPanelGroup controlPanelGroup,
				int galleryAvailableHeight, int gap) {
			int result = 0;
			for (RibbonElementPriority elementPriority : RibbonElementPriority
					.values()) {
				// map the priority
				RibbonElementPriority mappedPriority = mapping
						.map(elementPriority);
				// go over all galleries registered with the specific priority
				for (JRibbonGallery gallery : controlPanelGroup
						.getRibbonGalleries(elementPriority))
					// and take the preferred width under the mapped priority
					result += (gallery.getPreferredWidth(mappedPriority,
							galleryAvailableHeight) + gap);
			}

			return result;
		}

		@Override
		public void install(int availableHeight, int gap) {
			for (JBandControlPanel.ControlPanelGroup controlPanelGroup : this.controlPanel
					.getControlPanelGroups()) {
				boolean isCoreContent = controlPanelGroup.isCoreContent();
				if (isCoreContent) {
					List<JRibbonComponent> ribbonComps = controlPanelGroup
							.getRibbonComps();
					for (int i = 0; i < ribbonComps.size(); i++) {
						JRibbonComponent ribbonComp = ribbonComps.get(i);
						RibbonElementPriority targetPriority = RibbonElementPriority.TOP;
						if (ribbonComp.isResizingAware()) {
							targetPriority = this.mapping
									.map(RibbonElementPriority.TOP);
						}
						ribbonComp.setDisplayPriority(targetPriority);
					}
				} else {
					// set the display priority for the galleries
					for (RibbonElementPriority elementPriority : RibbonElementPriority
							.values()) {
						// map the priority
						RibbonElementPriority mappedPriority = mapping
								.map(elementPriority);
						// go over all galleries registered with the specific
						// priority
						for (JRibbonGallery gallery : controlPanelGroup
								.getRibbonGalleries(elementPriority))
							// and set the display priority based on the
							// specific
							// resize policy
							gallery.setDisplayPriority(mappedPriority);
					}

					// set the display priority for the buttons
					Map<RibbonElementPriority, List<AbstractCommandButton>> mapped = new HashMap<RibbonElementPriority, List<AbstractCommandButton>>();
					for (RibbonElementPriority rep : RibbonElementPriority
							.values()) {
						mapped.put(rep, new ArrayList<AbstractCommandButton>());
					}

					for (RibbonElementPriority elementPriority : RibbonElementPriority
							.values()) {
						// map the priority
						RibbonElementPriority mappedPriority = mapping
								.map(elementPriority);
						for (AbstractCommandButton button : controlPanelGroup
								.getRibbonButtons(elementPriority)) {
							// and add the button to a list based on the mapped
							// priority
							mapped.get(mappedPriority).add(button);
						}
					}

					// start from the top priority
					for (AbstractCommandButton big : mapped
							.get(RibbonElementPriority.TOP)) {
						big.setDisplayState(CommandButtonDisplayState.BIG);
					}

					// next - medium priority
					if (mapped.get(RibbonElementPriority.MEDIUM).size() > 0) {
						// try to move buttons from small to medium to make
						// three-somes.
						while (((mapped.get(RibbonElementPriority.MEDIUM)
								.size() % 3) != 0)
								&& (mapped.get(RibbonElementPriority.LOW)
										.size() > 0)) {
							AbstractCommandButton low = mapped.get(
									RibbonElementPriority.LOW).get(0);
							mapped.get(RibbonElementPriority.LOW).remove(low);
							mapped.get(RibbonElementPriority.MEDIUM).add(low);
						}
					}
					for (AbstractCommandButton medium : mapped
							.get(RibbonElementPriority.MEDIUM)) {
						medium
								.setDisplayState(CommandButtonDisplayState.MEDIUM);
					}

					// finally - low priority
					for (AbstractCommandButton low : mapped
							.get(RibbonElementPriority.LOW)) {
						low.setDisplayState(CommandButtonDisplayState.SMALL);
					}
				}
			}
		}
	}

	/**
	 * Core resize policy that maps all {@link RibbonElementPriority}s to
	 * {@link RibbonElementPriority#TOP}.
	 * 
	 * @author Kirill Grouchnikov
	 */
	public static final class None extends BaseCoreRibbonBandResizePolicy {
		/**
		 * Creates the new resize policy of type <code>NONE</code>.
		 * 
		 * @param controlPanel
		 *            The control panel of the associated ribbon band.
		 */
		public None(JBandControlPanel controlPanel) {
			super(controlPanel, new Mapping() {
				@Override
				public RibbonElementPriority map(RibbonElementPriority priority) {
					return RibbonElementPriority.TOP;
				}
			});
		}
	}

	/**
	 * Core resize policy that maps:
	 * 
	 * <ul>
	 * <li>{@link RibbonElementPriority#TOP} ->
	 * {@link RibbonElementPriority#TOP}</li>
	 * <li>{@link RibbonElementPriority#MEDIUM} ->
	 * {@link RibbonElementPriority#TOP}</li>
	 * <li>{@link RibbonElementPriority#LOW} ->
	 * {@link RibbonElementPriority#MEDIUM}</li>
	 * </ul>
	 * 
	 * @author Kirill Grouchnikov
	 */
	public static final class Low2Mid extends BaseCoreRibbonBandResizePolicy {
		/**
		 * Creates the new resize policy of type <code>LOW2MID</code>.
		 * 
		 * @param controlPanel
		 *            The control panel of the associated ribbon band.
		 */
		public Low2Mid(JBandControlPanel controlPanel) {
			super(controlPanel, new Mapping() {
				@Override
				public RibbonElementPriority map(RibbonElementPriority priority) {
					switch (priority) {
					case TOP:
						return RibbonElementPriority.TOP;
					case MEDIUM:
						return RibbonElementPriority.TOP;
					case LOW:
						return RibbonElementPriority.MEDIUM;
					}
					return null;
				}
			});
		}
	}

	/**
	 * Core resize policy that maps:
	 * 
	 * <ul>
	 * <li>{@link RibbonElementPriority#TOP} ->
	 * {@link RibbonElementPriority#TOP}</li>
	 * <li>{@link RibbonElementPriority#MEDIUM} ->
	 * {@link RibbonElementPriority#MEDIUM}</li>
	 * <li>{@link RibbonElementPriority#LOW} ->
	 * {@link RibbonElementPriority#MEDIUM}</li>
	 * </ul>
	 * 
	 * @author Kirill Grouchnikov
	 */
	public static final class Mid2Mid extends BaseCoreRibbonBandResizePolicy {
		/**
		 * Creates the new resize policy of type <code>MID2MID</code>.
		 * 
		 * @param controlPanel
		 *            The control panel of the associated ribbon band.
		 */
		public Mid2Mid(JBandControlPanel controlPanel) {
			super(controlPanel, new Mapping() {
				@Override
				public RibbonElementPriority map(RibbonElementPriority priority) {
					switch (priority) {
					case TOP:
						return RibbonElementPriority.TOP;
					case MEDIUM:
						return RibbonElementPriority.MEDIUM;
					case LOW:
						return RibbonElementPriority.MEDIUM;
					}
					return null;
				}
			});
		}
	}

	/**
	 * Mirror core resize policy that maps the values of
	 * {@link RibbonElementPriority}s to themselves.
	 * 
	 * @author Kirill Grouchnikov
	 */
	public static final class Mirror extends BaseCoreRibbonBandResizePolicy {
		/**
		 * Creates the new resize policy of type <code>MIRROR</code>.
		 * 
		 * @param controlPanel
		 *            The control panel of the associated ribbon band.
		 */
		public Mirror(JBandControlPanel controlPanel) {
			super(controlPanel, new Mapping() {
				@Override
				public RibbonElementPriority map(RibbonElementPriority priority) {
					return priority;
				}
			});
		}
	}

	/**
	 * Core resize policy that maps:
	 * 
	 * <ul>
	 * <li>{@link RibbonElementPriority#TOP} ->
	 * {@link RibbonElementPriority#TOP}</li>
	 * <li>{@link RibbonElementPriority#MEDIUM} ->
	 * {@link RibbonElementPriority#LOW}</li>
	 * <li>{@link RibbonElementPriority#LOW} ->
	 * {@link RibbonElementPriority#LOW}</li>
	 * </ul>
	 * 
	 * @author Kirill Grouchnikov
	 */
	public static final class Mid2Low extends BaseCoreRibbonBandResizePolicy {
		/**
		 * Creates the new resize policy of type <code>MID2LOW</code>.
		 * 
		 * @param controlPanel
		 *            The control panel of the associated ribbon band.
		 */
		public Mid2Low(JBandControlPanel controlPanel) {
			super(controlPanel, new Mapping() {
				@Override
				public RibbonElementPriority map(RibbonElementPriority priority) {
					switch (priority) {
					case TOP:
						return RibbonElementPriority.TOP;
					case MEDIUM:
						return RibbonElementPriority.LOW;
					case LOW:
						return RibbonElementPriority.LOW;
					}
					return null;
				}
			});
		}
	}

	/**
	 * Core resize policy that maps:
	 * 
	 * <ul>
	 * <li>{@link RibbonElementPriority#TOP} ->
	 * {@link RibbonElementPriority#MEDIUM}</li>
	 * <li>{@link RibbonElementPriority#MEDIUM} ->
	 * {@link RibbonElementPriority#LOW}</li>
	 * <li>{@link RibbonElementPriority#LOW} ->
	 * {@link RibbonElementPriority#LOW}</li>
	 * </ul>
	 * 
	 * @author Kirill Grouchnikov
	 */
	public static final class High2Mid extends BaseCoreRibbonBandResizePolicy {
		/**
		 * Creates the new resize policy of type <code>HIGH2MID</code>.
		 * 
		 * @param controlPanel
		 *            The control panel of the associated ribbon band.
		 */
		public High2Mid(JBandControlPanel controlPanel) {
			super(controlPanel, new Mapping() {
				@Override
				public RibbonElementPriority map(RibbonElementPriority priority) {
					switch (priority) {
					case TOP:
						return RibbonElementPriority.MEDIUM;
					case MEDIUM:
						return RibbonElementPriority.LOW;
					case LOW:
						return RibbonElementPriority.LOW;
					}
					return null;
				}
			});
		}
	}

	/**
	 * Core resize policy that maps all {@link RibbonElementPriority}s to
	 * {@link RibbonElementPriority#LOW}.
	 * 
	 * @author Kirill Grouchnikov
	 */
	public static final class High2Low extends BaseCoreRibbonBandResizePolicy {
		/**
		 * Creates the new resize policy of type <code>HIGH2LOW</code>.
		 * 
		 * @param controlPanel
		 *            The control panel of the associated ribbon band.
		 */
		public High2Low(JBandControlPanel controlPanel) {
			super(controlPanel, new Mapping() {
				@Override
				public RibbonElementPriority map(RibbonElementPriority priority) {
					return RibbonElementPriority.LOW;
				}
			});
		}
	}

	/**
	 * Core resize policy for {@link JFlowRibbonBand} that places the content in
	 * two rows.
	 * 
	 * @author Kirill Grouchnikov
	 */
	public static class FlowTwoRows extends
			BaseRibbonBandResizePolicy<JFlowBandControlPanel> {
		/**
		 * Creates a new two-row resize policy for {@link JFlowRibbonBand}s.
		 * 
		 * @param controlPanel
		 *            The control panel of the associated ribbon band.
		 */
		public FlowTwoRows(JFlowBandControlPanel controlPanel) {
			super(controlPanel);
		}

		@Override
		public int getPreferredWidth(int availableHeight, int gap) {
			int compCount = controlPanel.getFlowComponents().size();
			int[] widths = new int[compCount];
			int index = 0;
			int currBestResult = 0;
			for (JComponent flowComp : controlPanel.getFlowComponents()) {
				int pref = flowComp.getPreferredSize().width;
				widths[index++] = pref;
				currBestResult += (pref + gap);
			}

			// need to find the inflection point that results in
			// lowest value for max length of two sub-sequences
			for (int inflectionIndex = 0; inflectionIndex < (compCount - 1); inflectionIndex++) {
				int w1 = 0;
				for (int index1 = 0; index1 <= inflectionIndex; index1++) {
					w1 += widths[index1] + gap;
				}
				int w2 = 0;
				for (int index2 = inflectionIndex + 1; index2 < compCount; index2++) {
					w2 += widths[index2] + gap;
				}

				int width = Math.max(w1, w2);
				if (width < currBestResult)
					currBestResult = width;
			}

			return currBestResult;
		}

		@Override
		public void install(int availableHeight, int gap) {
		}
	}

	/**
	 * Core resize policy for {@link JFlowRibbonBand} that places the content in
	 * three rows.
	 * 
	 * @author Kirill Grouchnikov
	 */
	public static class FlowThreeRows extends
			BaseRibbonBandResizePolicy<JFlowBandControlPanel> {
		/**
		 * Creates a new three-row resize policy for {@link JFlowRibbonBand}s.
		 * 
		 * @param controlPanel
		 *            The control panel of the associated ribbon band.
		 */
		public FlowThreeRows(JFlowBandControlPanel controlPanel) {
			super(controlPanel);
		}

		@Override
		public int getPreferredWidth(int availableHeight, int gap) {
			int compCount = controlPanel.getFlowComponents().size();
			int[] widths = new int[compCount];
			int index = 0;
			int currBestResult = 0;
			for (JComponent flowComp : controlPanel.getFlowComponents()) {
				int pref = flowComp.getPreferredSize().width;
				widths[index++] = pref;
				currBestResult += (pref + gap);
			}

			// need to find the inflection points that results in
			// lowest value for max length of three sub-sequences
			for (int inflectionIndex1 = 0; inflectionIndex1 < (compCount - 2); inflectionIndex1++) {
				for (int inflectionIndex2 = inflectionIndex1 + 1; inflectionIndex2 < (compCount - 1); inflectionIndex2++) {
					int w1 = 0;
					for (int index1 = 0; index1 <= inflectionIndex1; index1++) {
						w1 += widths[index1] + gap;
					}
					int w2 = 0;
					for (int index2 = inflectionIndex1 + 1; index2 <= inflectionIndex2; index2++) {
						w2 += widths[index2] + gap;
					}
					int w3 = 0;
					for (int index3 = inflectionIndex2 + 1; index3 < compCount; index3++) {
						w3 += widths[index3] + gap;
					}

					int width = Math.max(Math.max(w1, w2), w3);
					if (width < currBestResult)
						currBestResult = width;
				}
			}

			return currBestResult;
		}

		@Override
		public void install(int availableHeight, int gap) {
		}
	}

	/**
	 * Returns a list that has {@link FlowTwoRows} policy followed by the
	 * {@link FlowThreeRows} resize policy. The last entry is the
	 * {@link IconRibbonBandResizePolicy}.
	 * 
	 * @param ribbonBand
	 *            Ribbon band.
	 * @param stepsToRepeat
	 *            The number of times each one of the {@link FlowTwoRows} /
	 *            {@link FlowThreeRows} should appear consecutively in the
	 *            returned list.
	 * @return The restrictive list of core ribbon band resize policies.
	 */
	public static List<RibbonBandResizePolicy> getCoreFlowPoliciesRestrictive(
			JFlowRibbonBand ribbonBand, int stepsToRepeat) {
		List<RibbonBandResizePolicy> result = new ArrayList<RibbonBandResizePolicy>();
		for (int i = 0; i < stepsToRepeat; i++) {
			result.add(new FlowTwoRows(ribbonBand.getControlPanel()));
		}
		for (int i = 0; i < stepsToRepeat; i++) {
			result.add(new FlowThreeRows(ribbonBand.getControlPanel()));
		}
		result
				.add(new IconRibbonBandResizePolicy(ribbonBand
						.getControlPanel()));
		return result;
	}

}
