/*
 * 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.common;

import java.awt.*;
import java.awt.font.*;
import java.awt.geom.AffineTransform;
import java.text.AttributedString;
import java.util.ArrayList;

import javax.swing.*;
import javax.swing.border.*;
import javax.swing.plaf.*;

import org.pushingpixels.flamingo.api.common.RichTooltip;
import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;

/**
 * Basic UI for rich tooltip panel {@link JRichTooltipPanel}.
 * 
 * @author Kirill Grouchnikov
 */
public class BasicRichTooltipPanelUI extends RichTooltipPanelUI {
	/**
	 * The associated tooltip panel.
	 */
	protected JRichTooltipPanel richTooltipPanel;

	protected java.util.List<JLabel> titleLabels;

	protected java.util.List<JLabel> descriptionLabels;

	protected JLabel mainImageLabel;

	protected JSeparator footerSeparator;

	protected JLabel footerImageLabel;

	protected java.util.List<JLabel> footerLabels;

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

	public BasicRichTooltipPanelUI() {
		this.titleLabels = new ArrayList<JLabel>();
		this.descriptionLabels = new ArrayList<JLabel>();
		this.footerLabels = new ArrayList<JLabel>();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
	 */
	@Override
	public void installUI(JComponent c) {
		this.richTooltipPanel = (JRichTooltipPanel) c;
		super.installUI(this.richTooltipPanel);
		installDefaults();
		installComponents();
		installListeners();

		this.richTooltipPanel.setLayout(createLayoutManager());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
	 */
	@Override
	public void uninstallUI(JComponent c) {
		uninstallListeners();
		uninstallComponents();
		uninstallDefaults();
		super.uninstallUI(this.richTooltipPanel);
	}

	/**
	 * Installs default settings for the associated rich tooltip panel.
	 */
	protected void installDefaults() {
		Border b = this.richTooltipPanel.getBorder();
		if (b == null || b instanceof UIResource) {
			Border toSet = UIManager.getBorder("RichTooltipPanel.border");
			if (toSet == null)
				toSet = new BorderUIResource.CompoundBorderUIResource(
						new LineBorder(FlamingoUtilities.getBorderColor()),
						new EmptyBorder(2, 4, 3, 4));
			this.richTooltipPanel.setBorder(toSet);
		}
		LookAndFeel.installProperty(this.richTooltipPanel, "opaque",
				Boolean.TRUE);
	}

	/**
	 * Installs listeners on the associated rich tooltip panel.
	 */
	protected void installListeners() {
	}

	/**
	 * Installs components on the associated rich tooltip panel.
	 */
	protected void installComponents() {
	}

	/**
	 * Uninstalls default settings from the associated rich tooltip panel.
	 */
	protected void uninstallDefaults() {
		LookAndFeel.uninstallBorder(this.richTooltipPanel);
	}

	/**
	 * Uninstalls listeners from the associated rich tooltip panel.
	 */
	protected void uninstallListeners() {
	}

	/**
	 * Uninstalls subcomponents from the associated rich tooltip panel.
	 */
	protected void uninstallComponents() {
		this.removeExistingComponents();
	}

	@Override
	public void update(Graphics g, JComponent c) {
		this.paintBackground(g);
		this.paint(g, c);
	}

	protected void paintBackground(Graphics g) {
		Color main = FlamingoUtilities.getColor(Color.gray,
				"Label.disabledForeground").brighter();
		Graphics2D g2d = (Graphics2D) g.create();
		g2d.setPaint(new GradientPaint(0, 0, FlamingoUtilities.getLighterColor(
				main, 0.9), 0, this.richTooltipPanel.getHeight(),
				FlamingoUtilities.getLighterColor(main, 0.4)));
		g2d.fillRect(0, 0, this.richTooltipPanel.getWidth(),
				this.richTooltipPanel.getHeight());
		g2d.setFont(FlamingoUtilities.getFont(this.richTooltipPanel,
				"Ribbon.font", "Button.font", "Panel.font"));
		g2d.dispose();
	}

	@Override
	public void paint(Graphics g, JComponent c) {
	}

	protected LayoutManager createLayoutManager() {
		return new RichTooltipPanelLayout();
	}

	protected class RichTooltipPanelLayout implements LayoutManager {
		@Override
		public void addLayoutComponent(String name, Component comp) {
		}

		@Override
		public void removeLayoutComponent(Component comp) {
		}

		@Override
		public Dimension minimumLayoutSize(Container parent) {
			return this.preferredLayoutSize(parent);
		}

		@Override
		public Dimension preferredLayoutSize(Container parent) {
			Insets ins = parent.getInsets();
			int gap = getLayoutGap();
			Font font = FlamingoUtilities.getFont(parent, "Ribbon.font",
					"Button.font", "Panel.font");
			Font titleFont = font.deriveFont(Font.BOLD);

			// the main text gets 200 pixels. The width is defined
			// by this and the presence of the main text.
			// The height is defined based on the width and the
			// text broken into multiline paragraphs

			int descTextWidth = getDescriptionTextWidth();
			int width = ins.left + 2 * gap + descTextWidth + ins.right;
			RichTooltip tooltipInfo = richTooltipPanel.getTooltipInfo();
			FontRenderContext frc = new FontRenderContext(
					new AffineTransform(), true, false);
			if (tooltipInfo.getMainImage() != null) {
				width += tooltipInfo.getMainImage().getWidth(null);
			}

			int fontHeight = parent.getFontMetrics(font).getHeight();

			int height = ins.top;

			// The title label
			int titleTextHeight = 0;
			AttributedString titleAttributedDescription = new AttributedString(
					tooltipInfo.getTitle());
			titleAttributedDescription.addAttribute(TextAttribute.FONT,
					titleFont);
			LineBreakMeasurer titleLineBreakMeasurer = new LineBreakMeasurer(
					titleAttributedDescription.getIterator(), frc);
			int maxTitleLineWidth = 0;
			while (true) {
				TextLayout tl = titleLineBreakMeasurer
						.nextLayout(descTextWidth);
				if (tl == null)
					break;
				titleTextHeight += fontHeight;
				int lineWidth = (int) Math.ceil(tl.getBounds().getWidth());
				maxTitleLineWidth = Math.max(maxTitleLineWidth, lineWidth);
			}
			height += titleTextHeight;

			// The description text
			int descriptionTextHeight = 0;
			for (String descText : tooltipInfo.getDescriptionSections()) {
				AttributedString descAttributedDescription = new AttributedString(
						descText);
				descAttributedDescription
						.addAttribute(TextAttribute.FONT, font);
				LineBreakMeasurer descLineBreakMeasurer = new LineBreakMeasurer(
						descAttributedDescription.getIterator(), frc);
				while (true) {
					TextLayout tl = descLineBreakMeasurer
							.nextLayout(descTextWidth);
					if (tl == null)
						break;
					descriptionTextHeight += fontHeight;
				}
				// add an empty line after the paragraph
				descriptionTextHeight += fontHeight;
			}
			if (!tooltipInfo.getDescriptionSections().isEmpty()) {
				// remove the empty line after the last paragraph
				descriptionTextHeight -= fontHeight;
				// add gap between the title and the description
				descriptionTextHeight += gap;
			}

			if (tooltipInfo.getMainImage() != null) {
				height += Math.max(descriptionTextHeight, new JLabel(
						new ImageIcon(tooltipInfo.getMainImage()))
						.getPreferredSize().height);
			} else {
				height += descriptionTextHeight;
			}

			if ((tooltipInfo.getFooterImage() != null)
					|| (tooltipInfo.getFooterSections().size() > 0)) {
				height += gap;
				// The footer separator
				height += new JSeparator(JSeparator.HORIZONTAL)
						.getPreferredSize().height;

				height += gap;

				int footerTextHeight = 0;
				int availableWidth = descTextWidth;
				if (tooltipInfo.getFooterImage() != null) {
					availableWidth -= tooltipInfo.getFooterImage().getWidth(
							null);
				}
				if (tooltipInfo.getMainImage() != null) {
					availableWidth += tooltipInfo.getMainImage().getWidth(null);
				}
				for (String footerText : tooltipInfo.getFooterSections()) {
					AttributedString footerAttributedDescription = new AttributedString(
							footerText);
					footerAttributedDescription.addAttribute(
							TextAttribute.FONT, font);
					LineBreakMeasurer footerLineBreakMeasurer = new LineBreakMeasurer(
							footerAttributedDescription.getIterator(), frc);
					while (true) {
						TextLayout tl = footerLineBreakMeasurer
								.nextLayout(availableWidth);
						if (tl == null)
							break;
						footerTextHeight += fontHeight;
					}
					// add an empty line after the paragraph
					footerTextHeight += fontHeight;
				}
				// remove the empty line after the last paragraph
				footerTextHeight -= fontHeight;

				if (tooltipInfo.getFooterImage() != null) {
					height += Math.max(footerTextHeight, new JLabel(
							new ImageIcon(tooltipInfo.getFooterImage()))
							.getPreferredSize().height);
				} else {
					height += footerTextHeight;
				}
			}

			height += ins.bottom;

			// special case for rich tooltips that only have titles
			if (tooltipInfo.getDescriptionSections().isEmpty()
					&& (tooltipInfo.getMainImage() == null)
					&& tooltipInfo.getFooterSections().isEmpty()
					&& (tooltipInfo.getFooterImage() == null)) {
				width = maxTitleLineWidth + 1 + ins.left + ins.right;
			}

			return new Dimension(width, height);
		}

		@Override
		public void layoutContainer(Container parent) {
			removeExistingComponents();

			Font font = FlamingoUtilities.getFont(parent, "Ribbon.font",
					"Button.font", "Panel.font");
			Insets ins = richTooltipPanel.getInsets();
			int y = ins.top;
			RichTooltip tooltipInfo = richTooltipPanel.getTooltipInfo();
			FontRenderContext frc = new FontRenderContext(
					new AffineTransform(), true, false);
			int gap = getLayoutGap();

			int fontHeight = parent.getFontMetrics(font).getHeight();
			Font titleFont = font.deriveFont(Font.BOLD);

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

			// The title label
			int titleLabelWidth = parent.getWidth() - ins.left - ins.right;
			AttributedString titleAtributedDescription = new AttributedString(
					tooltipInfo.getTitle());
			titleAtributedDescription.addAttribute(TextAttribute.FONT,
					titleFont);
			LineBreakMeasurer titleLineBreakMeasurer = new LineBreakMeasurer(
					titleAtributedDescription.getIterator(), frc);
			int titleCurrOffset = 0;
			while (true) {
				TextLayout tl = titleLineBreakMeasurer
						.nextLayout(titleLabelWidth);
				if (tl == null)
					break;
				int charCount = tl.getCharacterCount();
				String line = tooltipInfo.getTitle().substring(titleCurrOffset,
						titleCurrOffset + charCount);

				JLabel titleLabel = new JLabel(line);
				titleLabel.setFont(titleFont);
				titleLabels.add(titleLabel);
				richTooltipPanel.add(titleLabel);
				int currLabelWidth = titleLabel.getPreferredSize().width;
				if (ltr) {
					titleLabel.setBounds(ins.left, y, currLabelWidth,
							fontHeight);
				} else {
					titleLabel.setBounds(parent.getWidth() - ins.right
							- currLabelWidth, y, currLabelWidth, fontHeight);
				}
				y += titleLabel.getHeight();

				titleCurrOffset += charCount;
			}
			y += gap;

			// The main image
			int x = ltr ? ins.left : parent.getWidth() - ins.right;
			if (tooltipInfo.getMainImage() != null) {
				mainImageLabel = new JLabel(new ImageIcon(tooltipInfo
						.getMainImage()));
				richTooltipPanel.add(mainImageLabel);
				int mainImageWidth = mainImageLabel.getPreferredSize().width;
				if (ltr) {
					mainImageLabel.setBounds(x, y, mainImageWidth,
							mainImageLabel.getPreferredSize().height);
					x += mainImageWidth;
				} else {
					mainImageLabel.setBounds(x - mainImageWidth, y,
							mainImageWidth,
							mainImageLabel.getPreferredSize().height);
					x -= mainImageWidth;
				}
			}
			if (ltr) {
				x += 2 * gap;
			} else {
				x -= 2 * gap;
			}

			// The description text
			int descLabelWidth = ltr ? parent.getWidth() - x - ins.right : x
					- ins.left;
            // enforce a minimal 200 pixel width
            if (descLabelWidth < 200) {
                descLabelWidth = 200;
            }
			for (String descText : tooltipInfo.getDescriptionSections()) {
				AttributedString attributedDescription = new AttributedString(
						descText);
				attributedDescription.addAttribute(TextAttribute.FONT, font);
				LineBreakMeasurer lineBreakMeasurer = new LineBreakMeasurer(
						attributedDescription.getIterator(), frc);
				int currOffset = 0;
				while (true) {
					TextLayout tl = lineBreakMeasurer
							.nextLayout(descLabelWidth);
					if (tl == null)
						break;
					int charCount = tl.getCharacterCount();
					String line = descText.substring(currOffset, currOffset
							+ charCount);

					JLabel descLabel = new JLabel(line);
					descriptionLabels.add(descLabel);
					richTooltipPanel.add(descLabel);
					int currDescWidth = descLabel.getPreferredSize().width;
					if (ltr) {
						descLabel.setBounds(x, y, currDescWidth, fontHeight);
					} else {
						descLabel.setBounds(x - currDescWidth, y,
								currDescWidth, fontHeight);
					}
					y += descLabel.getHeight();

					currOffset += charCount;
				}
				// add an empty line after the paragraph
				y += fontHeight;
			}
			// remove the empty line after the last paragraph
			y -= fontHeight;

			if (mainImageLabel != null) {
				y = Math.max(y, mainImageLabel.getY()
						+ mainImageLabel.getHeight());
			}

			if ((tooltipInfo.getFooterImage() != null)
					|| (tooltipInfo.getFooterSections().size() > 0)) {
				y += gap;
				// The footer separator
				footerSeparator = new JSeparator(JSeparator.HORIZONTAL);
				richTooltipPanel.add(footerSeparator);
				footerSeparator.setBounds(ins.left, y, parent.getWidth()
						- ins.left - ins.right, footerSeparator
						.getPreferredSize().height);

				y += footerSeparator.getHeight() + gap;

				// The footer image
				x = ltr ? ins.left : parent.getWidth() - ins.right;
				if (tooltipInfo.getFooterImage() != null) {
					footerImageLabel = new JLabel(new ImageIcon(tooltipInfo
							.getFooterImage()));
					richTooltipPanel.add(footerImageLabel);
					int footerImageWidth = footerImageLabel.getPreferredSize().width;
					if (ltr) {
						footerImageLabel.setBounds(x, y, footerImageWidth,
								footerImageLabel.getPreferredSize().height);
						x += footerImageWidth + 2 * gap;
					} else {
						footerImageLabel.setBounds(x - footerImageWidth, y,
								footerImageWidth, footerImageLabel
										.getPreferredSize().height);
						x -= (footerImageWidth + 2 * gap);
					}
				}

				// The footer text
				int footerLabelWidth = ltr ? parent.getWidth() - x - ins.right
						: x - ins.left;
				for (String footerText : tooltipInfo.getFooterSections()) {
					AttributedString attributedDescription = new AttributedString(
							footerText);
					attributedDescription
							.addAttribute(TextAttribute.FONT, font);
					LineBreakMeasurer lineBreakMeasurer = new LineBreakMeasurer(
							attributedDescription.getIterator(), frc);
					int currOffset = 0;
					while (true) {
						TextLayout tl = lineBreakMeasurer
								.nextLayout(footerLabelWidth);
						if (tl == null)
							break;
						int charCount = tl.getCharacterCount();
						String line = footerText.substring(currOffset,
								currOffset + charCount);

						JLabel footerLabel = new JLabel(line);
						footerLabels.add(footerLabel);
						richTooltipPanel.add(footerLabel);
						int currLabelWidth = footerLabel.getPreferredSize().width;
						if (ltr) {
							footerLabel.setBounds(x, y, currLabelWidth,
									fontHeight);
						} else {
							footerLabel.setBounds(x - currLabelWidth, y,
									currLabelWidth, fontHeight);
						}
						y += footerLabel.getHeight();

						currOffset += charCount;
					}
					// add an empty line after the paragraph
					y += fontHeight;
				}
				// remove the empty line after the last paragraph
				y -= fontHeight;
			}
		}
	}

	protected int getDescriptionTextWidth() {
		return 200;
	}

	protected int getLayoutGap() {
		return 4;
	}

	protected void removeExistingComponents() {
		for (JLabel label : this.titleLabels)
			this.richTooltipPanel.remove(label);

		if (this.mainImageLabel != null) {
			this.richTooltipPanel.remove(this.mainImageLabel);
		}

		for (JLabel label : this.descriptionLabels)
			this.richTooltipPanel.remove(label);

		if (this.footerSeparator != null) {
			this.richTooltipPanel.remove(this.footerSeparator);
		}

		if (this.footerImageLabel != null) {
			this.richTooltipPanel.remove(this.footerImageLabel);
		}

		for (JLabel label : this.footerLabels)
			this.richTooltipPanel.remove(label);
	}
}
