/*
 * 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.internal.ui.ribbon;

import java.awt.*;
import java.util.*;
import java.util.List;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.ComponentUI;

import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
import org.pushingpixels.flamingo.api.common.CommandButtonDisplayState;
import org.pushingpixels.flamingo.api.ribbon.*;
import org.pushingpixels.flamingo.api.ribbon.resize.IconRibbonBandResizePolicy;
import org.pushingpixels.flamingo.api.ribbon.resize.RibbonBandResizePolicy;
import org.pushingpixels.flamingo.internal.ui.ribbon.BasicRibbonBandUI.CollapsedButtonPopupPanel;

/**
 * Basic UI for control panel of ribbon band {@link JBandControlPanel}.
 * 
 * @author Kirill Grouchnikov
 */
public class BasicBandControlPanelUI extends AbstractBandControlPanelUI {
	private JSeparator[] groupSeparators;

	private JLabel[] groupLabels;

	protected ChangeListener changeListener;

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
	 */
	public static ComponentUI createUI(JComponent c) {
		return new BasicBandControlPanelUI();
	}

	/**
	 * Invoked by <code>installUI</code> to create a layout manager object to
	 * manage the {@link JBandControlPanel}.
	 * 
	 * @return a layout manager object
	 */
	@Override
	protected LayoutManager createLayoutManager() {
		return new ControlPanelLayout();
	}

	@Override
	protected void installListeners() {
		super.installListeners();

		this.changeListener = new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent e) {
				syncGroupHeaders();
				controlPanel.revalidate();
			}
		};
		((JBandControlPanel) this.controlPanel)
				.addChangeListener(this.changeListener);
	}

	@Override
	protected void uninstallListeners() {
		((JBandControlPanel) this.controlPanel)
				.removeChangeListener(this.changeListener);
		this.changeListener = null;

		super.uninstallListeners();
	}

	@Override
	protected void installComponents() {
		super.installComponents();

		this.syncGroupHeaders();
	}

	@Override
	protected void uninstallComponents() {
		if (this.groupSeparators != null) {
			for (JSeparator groupSeparator : this.groupSeparators) {
				this.controlPanel.remove(groupSeparator);
			}
		}
		if (this.groupLabels != null) {
			for (JLabel groupLabel : this.groupLabels) {
				if (groupLabel != null)
					this.controlPanel.remove(groupLabel);
			}
		}

		super.uninstallComponents();
	}

	protected void syncGroupHeaders() {
		if (this.groupSeparators != null) {
			for (JSeparator groupSeparator : this.groupSeparators) {
				this.controlPanel.remove(groupSeparator);
			}
		}
		if (this.groupLabels != null) {
			for (JLabel groupLabel : this.groupLabels) {
				if (groupLabel != null)
					this.controlPanel.remove(groupLabel);
			}
		}

		int groupCount = ((JBandControlPanel) this.controlPanel)
				.getControlPanelGroupCount();
		if (groupCount > 1) {
			this.groupSeparators = new JSeparator[groupCount - 1];
			for (int i = 0; i < groupCount - 1; i++) {
				this.groupSeparators[i] = new JSeparator(JSeparator.VERTICAL);
				this.controlPanel.add(this.groupSeparators[i]);
			}
		}
		if (groupCount > 0) {
			this.groupLabels = new JLabel[groupCount];
			for (int i = 0; i < groupCount; i++) {
				String title = ((JBandControlPanel) this.controlPanel)
						.getControlPanelGroupTitle(i);
				if (title != null) {
					this.groupLabels[i] = new JLabel(title);
					this.controlPanel.add(this.groupLabels[i]);
				}
			}
		}

	}

	/**
	 * Layout for the control panel of ribbon band.
	 * 
	 * @author Kirill Grouchnikov
	 */
	private class ControlPanelLayout implements LayoutManager {

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
		 * java.awt.Component)
		 */
		public void addLayoutComponent(String name, Component c) {
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
		 */
		public void removeLayoutComponent(Component c) {
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
		 */
		public Dimension preferredLayoutSize(Container c) {
			// The height of ribbon band control panel is
			// computed based on the preferred height of a command
			// button in BIG state.
			int buttonHeight = dummy.getPreferredSize().height;
			int vGap = getLayoutGap() * 3 / 4;
			int minusGaps = buttonHeight - 2 * vGap;
			switch (minusGaps % 3) {
			case 1:
				buttonHeight += 2;
				break;
			case 2:
				buttonHeight++;
				break;
			}

			Insets ins = c.getInsets();

			// System.out.println("Control panel pref = "
			// + (buttonHeight + ins.top + ins.bottom));

			return new Dimension(c.getWidth(), buttonHeight + ins.top
					+ ins.bottom);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
		 */
		public Dimension minimumLayoutSize(Container c) {
			return this.preferredLayoutSize(c);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
		 */
		public void layoutContainer(Container c) {
			// System.out.println("Control panel real = " + c.getHeight());

			AbstractRibbonBand ribbonBand = ((JBandControlPanel) c)
					.getRibbonBand();
			RibbonBandResizePolicy currentResizePolicy = ribbonBand
					.getCurrentResizePolicy();
			if (currentResizePolicy == null)
				return;

			boolean ltr = c.getComponentOrientation().isLeftToRight();

			// need place for border
			Insets ins = c.getInsets();
			int gap = getLayoutGap();
			int x = ltr ? ins.left + gap / 2 : c.getWidth() - ins.right - gap
					/ 2;
			int availableHeight = c.getHeight() - ins.top - ins.bottom;

			if (SwingUtilities.getAncestorOfClass(
					CollapsedButtonPopupPanel.class, c) != null) {
				// install the most permissive resize policy on the popup
				// panel of a collapsed ribbon band
				List<RibbonBandResizePolicy> resizePolicies = ribbonBand
						.getResizePolicies();
				resizePolicies.get(0).install(availableHeight, gap);
			} else {
				if (currentResizePolicy instanceof IconRibbonBandResizePolicy) {
					return;
				}

				// Installs the resize policy - this updates the display
				// priority of all the galleries and buttons
				currentResizePolicy.install(availableHeight, gap);
			}

			int controlPanelGroupIndex = 0;
			for (JBandControlPanel.ControlPanelGroup controlPanelGroup : ((JBandControlPanel) controlPanel)
					.getControlPanelGroups()) {
				// handle the group separators
				if (controlPanelGroupIndex > 0) {
					int prefW = groupSeparators[controlPanelGroupIndex - 1]
							.getPreferredSize().width;
					int sepX = ltr ? x - gap + (gap - prefW) / 2 : x + gap / 2
							- (gap - prefW) / 2;
					groupSeparators[controlPanelGroupIndex - 1].setBounds(sepX,
							ins.top, prefW, availableHeight);
				}

				boolean hasLeadingComponent = false;
				boolean isCoreContent = controlPanelGroup.isCoreContent();
				if (isCoreContent) {
					// how much vertical space is available in each row?
					int singleRowHeight = availableHeight / 3;

					boolean hasTitle = (controlPanelGroup.getGroupTitle() != null);
					int maxWidthInCurrColumn = 0;
					if (hasTitle) {
						JLabel titleLabel = groupLabels[controlPanelGroupIndex];
						int pw = titleLabel.getPreferredSize().width;
						int titleLabelHeight = Math.min(singleRowHeight - gap
								/ 4, titleLabel.getPreferredSize().height);
						int yNudge = singleRowHeight - titleLabelHeight;
						int baseline = (titleLabelHeight > 0) ? titleLabel
								.getBaseline(pw, titleLabelHeight) : 0;
						if (ltr) {
							titleLabel.setBounds(x + gap, ins.top + yNudge
									- titleLabelHeight + baseline, pw,
									titleLabelHeight);
						} else {
							titleLabel.setBounds(x - gap - pw, ins.top + yNudge
									- titleLabelHeight + baseline, pw,
									titleLabelHeight);
						}
						maxWidthInCurrColumn = gap + pw;
					}
					List<JRibbonComponent> ribbonComps = controlPanelGroup
							.getRibbonComps();
					Map<JRibbonComponent, Integer> ribbonCompRowSpans = controlPanelGroup
							.getRibbonCompsRowSpans();
					List<JRibbonComponent> currColumn = new ArrayList<JRibbonComponent>();

					// if a group has a title, then the core components in that
					// group will take two rows instead of three
					int startingRow = hasTitle ? 1 : 0;
					int rowIndex = startingRow;

					for (int i = 0; i < ribbonComps.size(); i++) {
						JRibbonComponent coreComp = ribbonComps.get(i);
						int prefWidth = coreComp.getPreferredSize().width;
						int rowSpan = ribbonCompRowSpans.get(coreComp);

						// do we need to start a new column?
						int nextRowIndex = rowIndex + rowSpan;
						if (nextRowIndex > 3) {
							if (ltr) {
								if (hasLeadingComponent)
									x += gap;
								x += maxWidthInCurrColumn;
							} else {
								if (hasLeadingComponent)
									x -= gap;
								x -= maxWidthInCurrColumn;
							}
							hasLeadingComponent = true;
							maxWidthInCurrColumn = 0;
							rowIndex = startingRow;
							currColumn.clear();
						}

						// how much vertical space does a component get?
						int compHeight = Math.min(rowSpan * singleRowHeight
								- gap / 4, coreComp.getPreferredSize().height);
						int yNudge = rowSpan * singleRowHeight - compHeight;
						int y = rowIndex * singleRowHeight + ins.top;

						if (ltr) {
							coreComp.setBounds(x, y + yNudge, prefWidth,
									compHeight);
						} else {
							coreComp.setBounds(x - prefWidth, y + yNudge,
									prefWidth, compHeight);
						}
						maxWidthInCurrColumn = Math.max(maxWidthInCurrColumn,
								prefWidth);
						currColumn.add(coreComp);

						coreComp.putClientProperty(
								AbstractBandControlPanelUI.TOP_ROW, Boolean
										.valueOf(rowIndex == 0));
						coreComp.putClientProperty(
								AbstractBandControlPanelUI.MID_ROW, Boolean
										.valueOf((rowIndex > 0)
												&& (rowIndex < 2)));
						coreComp.putClientProperty(
								AbstractBandControlPanelUI.BOTTOM_ROW, Boolean
										.valueOf(rowIndex == 2));

						// scan the components in this column and make them to
						// have the same width as the widest component in this
						// column
						for (JRibbonComponent comp : currColumn) {
							Rectangle bounds = comp.getBounds();
							if (ltr) {
								comp.setBounds(bounds.x, bounds.y,
										maxWidthInCurrColumn, bounds.height);
							} else {
								comp.setBounds(bounds.x + bounds.width
										- maxWidthInCurrColumn, bounds.y,
										maxWidthInCurrColumn, bounds.height);
							}
							comp.doLayout();
						}

						// System.out
						// .println(rowSpan + ":" + coreComp.getBounds());

						rowIndex += rowSpan;
					}
					if ((rowIndex > 0) && (rowIndex <= 3)) {
						if (ltr) {
							if (hasLeadingComponent)
								x += gap;
							x += maxWidthInCurrColumn;
						} else {
							if (hasLeadingComponent)
								x -= gap;
							x -= maxWidthInCurrColumn;
						}
						hasLeadingComponent = true;
					}
				} else {
					// The galleries
					for (RibbonElementPriority elementPriority : RibbonElementPriority
							.values()) {
						for (JRibbonGallery gallery : controlPanelGroup
								.getRibbonGalleries(elementPriority)) {
							int pw = gallery.getPreferredWidth(gallery
									.getDisplayPriority(), availableHeight);
							if (ltr) {
								gallery.setBounds(x, ins.top, pw,
										availableHeight);
								if (hasLeadingComponent)
									x += gap;
								x += pw;
							} else {
								gallery.setBounds(x - pw, ins.top, pw,
										availableHeight);
								if (hasLeadingComponent)
									x -= gap;
								x -= pw;
							}
							hasLeadingComponent = true;
						}
					}

					Map<CommandButtonDisplayState, List<AbstractCommandButton>> buttonMap = new HashMap<CommandButtonDisplayState, List<AbstractCommandButton>>();
					for (RibbonElementPriority elementPriority : RibbonElementPriority
							.values()) {
						for (AbstractCommandButton commandButton : controlPanelGroup
								.getRibbonButtons(elementPriority)) {
							CommandButtonDisplayState state = commandButton
									.getDisplayState();
							if (buttonMap.get(state) == null) {
								buttonMap.put(state,
										new ArrayList<AbstractCommandButton>());
							}
							buttonMap.get(state).add(commandButton);
						}
					}

					List<AbstractCommandButton> bigs = buttonMap
							.get(CommandButtonDisplayState.BIG);
					if (bigs != null) {
						for (AbstractCommandButton bigButton : bigs) {
							// Big buttons
							int bigButtonWidth = bigButton.getPreferredSize().width;
							if (hasLeadingComponent) {
								if (ltr) {
									x += gap;
								} else {
									x -= gap;
								}
							}
							if (ltr) {
								bigButton.setBounds(x, ins.top, bigButtonWidth,
										availableHeight);
							} else {
								bigButton.setBounds(x - bigButtonWidth,
										ins.top, bigButtonWidth,
										availableHeight);
							}
							bigButton.putClientProperty(TOP_ROW, Boolean.FALSE);
							bigButton.putClientProperty(MID_ROW, Boolean.FALSE);
							bigButton.putClientProperty(BOTTOM_ROW,
									Boolean.FALSE);
							if (ltr) {
								x += bigButtonWidth;
							} else {
								x -= bigButtonWidth;
							}
							hasLeadingComponent = true;
						}
					}

					// Medium buttons
					int vGap = gap * 3 / 4;
					int medSmallButtonHeight = (availableHeight - 2 * vGap) / 3;

					int index3 = 0;
					int maxWidth3 = 0;
					List<AbstractCommandButton> mediums = buttonMap
							.get(CommandButtonDisplayState.MEDIUM);
					if (mediums != null) {
						for (AbstractCommandButton mediumButton : mediums) {
							int medWidth = mediumButton.getPreferredSize().width;
							maxWidth3 = Math.max(maxWidth3, medWidth);

							if (hasLeadingComponent && (index3 == 0)) {
								if (ltr) {
									x += gap;
								} else {
									x -= gap;
								}
							}
							int buttonTop = (medSmallButtonHeight + vGap)
									* index3;
							int buttonBottom = (medSmallButtonHeight + vGap)
									* (index3 + 1) - vGap;
							if (ltr) {
								mediumButton.setBounds(x, ins.top + buttonTop,
										medWidth, buttonBottom - buttonTop);
							} else {
								mediumButton.setBounds(x - medWidth, ins.top
										+ buttonTop, medWidth, buttonBottom
										- buttonTop);
							}
							mediumButton.putClientProperty(TOP_ROW, Boolean
									.valueOf(index3 == 0));
							mediumButton.putClientProperty(MID_ROW, Boolean
									.valueOf(index3 == 1));
							mediumButton.putClientProperty(BOTTOM_ROW, Boolean
									.valueOf(index3 == 2));

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

					index3 = 0;
					maxWidth3 = 0;
					List<AbstractCommandButton> smalls = buttonMap
							.get(CommandButtonDisplayState.SMALL);
					if (smalls != null) {
						for (AbstractCommandButton smallButton : smalls) {
							int lowWidth = smallButton.getPreferredSize().width;
							maxWidth3 = Math.max(maxWidth3, lowWidth);
							if (hasLeadingComponent && (index3 == 0)) {
								if (ltr) {
									x += gap;
								} else {
									x -= gap;
								}
							}
							int buttonTop = (medSmallButtonHeight + vGap)
									* index3;
							int buttonBottom = (medSmallButtonHeight + vGap)
									* (index3 + 1) - vGap;
							if (ltr) {
								smallButton.setBounds(x, ins.top + buttonTop,
										lowWidth, buttonBottom - buttonTop);
							} else {
								smallButton.setBounds(x - lowWidth, ins.top
										+ buttonTop, lowWidth, buttonBottom
										- buttonTop);
							}
							smallButton.putClientProperty(TOP_ROW, Boolean
									.valueOf(index3 == 0));
							smallButton.putClientProperty(MID_ROW, Boolean
									.valueOf(index3 == 1));
							smallButton.putClientProperty(BOTTOM_ROW, Boolean
									.valueOf(index3 == 2));

							index3++;
							if (index3 == 3) {
								// last button in threesome
								index3 = 0;
								if (ltr) {
									x += maxWidth3;
								} else {
									x -= maxWidth3;
								}
								hasLeadingComponent = true;
								maxWidth3 = 0;
							}
						}
					}
					if ((index3 < 3) && (maxWidth3 > 0)) {
						if (ltr) {
							x += maxWidth3;
						} else {
							x -= maxWidth3;
						}
						hasLeadingComponent = true;
					}
				}
				// space for the separator
				if (ltr) {
					x += gap * 3 / 2;
				} else {
					x -= gap * 3 / 2;
				}
				controlPanelGroupIndex++;
			}
		}
	}
}
