/*******************************************************************************
 * Copyright (c) 2000, 2010 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.draw2d.text;

import java.util.List;

import org.eclipse.swt.SWT;

import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Rectangle;

/**
 * A <code>FlowFigure</code> represented by a single {@link BlockBox} containing
 * one or more lines. A BlockFlow is a creator of LineBoxes, which its children
 * require during layout. A BlockFlow can be thought of as a foundation for a
 * paragraph.
 * <P>
 * BlockFlows must be parented by a <code>FlowFigure</code>. {@link FlowPage}
 * can be used as a "root" block and can be parented by normal Figures.
 * <P>
 * Only {@link FlowFigure}s can be added to a BlockFlow.
 * <P>
 * WARNING: This class is not intended to be subclassed by clients.
 * 
 * @author hudsonr
 * @since 2.1
 */
public class BlockFlow extends FlowFigure {

	private final BlockBox blockBox;
	private int alignment = PositionConstants.NONE;
	private int orientation = SWT.NONE;
	private boolean bidiValid;

	/**
	 * Constructs a new BlockFlow.
	 */
	public BlockFlow() {
		blockBox = createBlockBox();
	}

	/**
	 * BlockFlows contribute a paragraph separator so as to keep the Bidi state
	 * of the text on either side of this block from affecting each other. Since
	 * each block is like a different paragraph, it does not contribute any
	 * actual text to its containing block.
	 * 
	 * @see org.eclipse.draw2d.text.FlowFigure#contributeBidi(org.eclipse.draw2d.text.BidiProcessor)
	 */
	protected void contributeBidi(BidiProcessor proc) {
		proc.addControlChar(BidiChars.P_SEP);
	}

	BlockBox createBlockBox() {
		return new BlockBox(this);
	}

	/**
	 * @see org.eclipse.draw2d.text.FlowFigure#createDefaultFlowLayout()
	 */
	protected FlowFigureLayout createDefaultFlowLayout() {
		return new BlockFlowLayout(this);
	}

	/**
	 * Returns the BlockBox associated with this.
	 * 
	 * @return This BlockFlow's BlockBox
	 */
	protected BlockBox getBlockBox() {
		return blockBox;
	}

	int getBottomMargin() {
		int margin = 0;
		if (getBorder() instanceof FlowBorder) {
			FlowBorder border = (FlowBorder) getBorder();
			return border.getBottomMargin();
		}
		List children = getChildren();
		int childIndex = children.size() - 1;
		if (childIndex >= 0 && children.get(childIndex) instanceof BlockFlow) {
			margin = Math.max(margin,
					((BlockFlow) children.get(childIndex)).getBottomMargin());
		}
		return margin;
	}

	/**
	 * Returns the effective horizontal alignment. This method will never return
	 * {@link PositionConstants#NONE}. If the value is none, it will return the
	 * inherited alignment. If no alignment was inherited, it will return the
	 * default alignment ({@link PositionConstants#LEFT}).
	 * 
	 * @return the effective alignment
	 */
	public int getHorizontalAligment() {
		if (alignment != PositionConstants.NONE)
			return alignment;
		IFigure parent = getParent();
		while (parent != null && !(parent instanceof BlockFlow))
			parent = parent.getParent();
		if (parent != null)
			return ((BlockFlow) parent).getHorizontalAligment();
		return PositionConstants.LEFT;
	}

	int getLeftMargin() {
		if (getBorder() instanceof FlowBorder)
			return ((FlowBorder) getBorder()).getLeftMargin();
		return 0;
	}

	/**
	 * Returns the orientation set on this block.
	 * 
	 * @return LTR, RTL or NONE
	 * @see #setOrientation(int)
	 * @since 3.1
	 */
	public int getLocalOrientation() {
		return orientation;
	}

	/**
	 * Returns the horizontal alignment set on this block.
	 * 
	 * @return LEFT, RIGHT, ALWAYS_LEFT, ALWAYS_RIGHT, NONE
	 * @see #setHorizontalAligment(int)
	 * @since 3.1
	 */
	public int getLocalHorizontalAlignment() {
		return alignment;
	}

	/**
	 * Returns this block's Bidi orientation. If none was set on this block, it
	 * will inherit the one from its containing block. If there is no containing
	 * block, it will return the default orientation (SWT.RIGHT_TO_LEFT if
	 * mirrored; SWT.LEFT_TO_RIGHT otherwise).
	 * 
	 * @return SWT.RIGHT_TO_LEFT or SWT.LEFT_TO_RIGHT
	 * @see #setOrientation(int)
	 * @since 3.1
	 */
	public int getOrientation() {
		if (orientation != SWT.NONE)
			return orientation;
		IFigure parent = getParent();
		while (parent != null && !(parent instanceof BlockFlow))
			parent = parent.getParent();
		if (parent != null)
			return ((BlockFlow) parent).getOrientation();
		return isMirrored() ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT;
	}

	int getRightMargin() {
		if (getBorder() instanceof FlowBorder)
			return ((FlowBorder) getBorder()).getRightMargin();
		return 0;
	}

	int getTopMargin() {
		int margin = 0;
		if (getBorder() instanceof FlowBorder) {
			FlowBorder border = (FlowBorder) getBorder();
			return border.getTopMargin();
		}
		List children = getChildren();
		if (children.size() > 0 && children.get(0) instanceof BlockFlow) {
			margin = Math.max(margin,
					((BlockFlow) children.get(0)).getTopMargin());
		}
		return margin;
	}

	/**
	 * @see org.eclipse.draw2d.Figure#paintBorder(org.eclipse.draw2d.Graphics)
	 */
	public void paintBorder(Graphics graphics) {
		if (getBorder() instanceof FlowBorder) {
			Rectangle where = getBlockBox().toRectangle();
			where.crop(new Insets(getTopMargin(), getLeftMargin(),
					getBottomMargin(), getRightMargin()));
			((FlowBorder) getBorder()).paint(this, graphics, where, SWT.LEAD
					| SWT.TRAIL);
		} else
			super.paintBorder(graphics);
		if (selectionStart != -1) {
			graphics.restoreState();
			graphics.setXORMode(true);
			graphics.setBackgroundColor(ColorConstants.white);
			graphics.fillRectangle(getBounds());
		}
	}

	/**
	 * @see org.eclipse.draw2d.text.FlowFigure#postValidate()
	 */
	public void postValidate() {
		Rectangle newBounds = getBlockBox().toRectangle();
		newBounds.crop(new Insets(getTopMargin(), getLeftMargin(),
				getBottomMargin(), getRightMargin()));
		setBounds(newBounds);
	}

	/**
	 * @see FlowFigure#revalidate()
	 */
	public void revalidate() {
		BlockFlowLayout layout = (BlockFlowLayout) getLayoutManager();
		layout.blockContentsChanged();
		super.revalidate();
	}

	/**
	 * A Block will invalidate the Bidi state of all its children, so that it is
	 * re-evaluated when this block is next validated.
	 * 
	 * @see org.eclipse.draw2d.text.FlowFigure#revalidateBidi(org.eclipse.draw2d.IFigure)
	 */
	protected void revalidateBidi(IFigure origin) {
		if (bidiValid) {
			bidiValid = false;
			revalidate();
		}
	}

	/**
	 * Sets the horitontal aligment of the block. Valid values are:
	 * <UL>
	 * <LI>{@link PositionConstants#NONE NONE} - (default) Alignment is
	 * inherited from parent. If a parent is not found then LEFT is used.</LI>
	 * <LI>{@link PositionConstants#LEFT} - Alignment is with leading edge</LI>
	 * <LI>{@link PositionConstants#RIGHT} - Alignment is with trailing edge</LI>
	 * <LI>{@link PositionConstants#CENTER}</LI>
	 * <LI>{@link PositionConstants#ALWAYS_LEFT} - Left, irrespective of
	 * orientation</LI>
	 * <LI>{@link PositionConstants#ALWAYS_RIGHT} - Right, irrespective of
	 * orientation</LI>
	 * </UL>
	 * 
	 * @param value
	 *            the aligment
	 * @see #getHorizontalAligment()
	 */
	public void setHorizontalAligment(int value) {
		value &= PositionConstants.LEFT | PositionConstants.CENTER
				| PositionConstants.RIGHT | PositionConstants.ALWAYS_LEFT
				| PositionConstants.ALWAYS_RIGHT;
		if (value == alignment)
			return;
		alignment = value;
		revalidate();
	}

	/**
	 * Sets the orientation for this block. Orientation can be one of:
	 * <UL>
	 * <LI>{@link SWT#LEFT_TO_RIGHT}
	 * <LI>{@link SWT#RIGHT_TO_LEFT}
	 * <LI>{@link SWT#NONE} (default)
	 * </UL>
	 * <code>NONE</code> is used to indicate that orientation should be
	 * inherited from the encompassing block.
	 * 
	 * @param orientation
	 *            LTR, RTL or NONE
	 * @see #getOrientation()
	 * @since 3.1
	 */
	public void setOrientation(int orientation) {
		orientation &= SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
		if (this.orientation == orientation)
			return;
		this.orientation = orientation;
		revalidateBidi(this);
	}

	/**
	 * @see org.eclipse.draw2d.Figure#useLocalCoordinates()
	 */
	protected boolean useLocalCoordinates() {
		return true;
	}

	/**
	 * Re-evaluate the Bidi state of all the fragments if it has been
	 * invalidated.
	 * 
	 * @see org.eclipse.draw2d.IFigure#validate()
	 */
	public void validate() {
		if (!bidiValid) {
			BidiProcessor.INSTANCE.setOrientation(getOrientation());
			if (getOrientation() == SWT.LEFT_TO_RIGHT && isMirrored())
				BidiProcessor.INSTANCE.addControlChar(BidiChars.LRE);
			super.contributeBidi(BidiProcessor.INSTANCE);
			BidiProcessor.INSTANCE.process();
			bidiValid = true;
		}
		super.validate();
	}

}