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

import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Rectangle;

/**
 * The base implementation for text flow figures. A flow figure is used to
 * render a document in which elements are laid out horizontally within a "line"
 * until that line is filled. Layout continues on the next line.
 * 
 * <p>
 * WARNING: This class is not intended to be subclassed by clients. Future
 * versions may contain additional abstract methods.
 * 
 * @author hudsonr
 * @since 2.1
 */
public abstract class FlowFigure extends Figure {

	/**
	 * integer indicating whether selection should be displayed.
	 */
	protected int selectionStart = -1;

	/**
	 * Constructs a new FlowFigure.
	 */
	public FlowFigure() {
		setLayoutManager(createDefaultFlowLayout());
	}

	/**
	 * If the child is a <code>FlowFigure</code>, its FlowContext is passed to
	 * it.
	 * 
	 * @see org.eclipse.draw2d.IFigure#add(IFigure, Object, int)
	 */
	public void add(IFigure child, Object constraint, int index) {
		super.add(child, constraint, index);
		// If this layout manager is a FlowContext, then the child *must* be a
		// FlowFigure
		if (getLayoutManager() instanceof FlowContext)
			((FlowFigure) child)
					.setFlowContext((FlowContext) getLayoutManager());
		revalidateBidi(this);
	}

	/**
	 * Calculates the width of text before the next line-break is encountered.
	 * <p>
	 * Default implementation treats each FlowFigure as a line-break. It adds no
	 * width and returns <code>true</code>. Sub-classes should override as
	 * needed.
	 * 
	 * @param width
	 *            the width before the next line-break (if one's found; all the
	 *            width, otherwise) will be added on to the first int in the
	 *            given array
	 * @return boolean indicating whether or not a line-break was found
	 * @since 3.1
	 */
	public boolean addLeadingWordRequirements(int[] width) {
		return true;
	}

	/**
	 * FlowFigures can contribute text for their block to the given
	 * {@link BidiProcessor}, which will process the contributions to determine
	 * Bidi levels and shaping requirements.
	 * <p>
	 * This method is invoked as part of validating Bidi.
	 * <p>
	 * Sub-classes that cache the BidiInfo and/or the bidi level in ContentBoxes
	 * should clear the cached values when this method is invoked.
	 * 
	 * @param proc
	 *            the BidiProcessor to which contributions should be made
	 * @see BidiProcessor#add(FlowFigure, String)
	 * @since 3.1
	 */
	protected void contributeBidi(BidiProcessor proc) {
		for (Iterator iter = getChildren().iterator(); iter.hasNext();)
			((FlowFigure) iter.next()).contributeBidi(proc);
	}

	/**
	 * Creates the default layout manager
	 * 
	 * @return The default layout
	 */
	protected abstract FlowFigureLayout createDefaultFlowLayout();

	/**
	 * Called after validate has occurred. This is used to update the bounds of
	 * the FlowFigure to encompass its new flow boxed created during validate.
	 */
	public abstract void postValidate();

	/**
	 * Overridden to revalidateBidi when fragments are removed.
	 * 
	 * @see org.eclipse.draw2d.IFigure#remove(org.eclipse.draw2d.IFigure)
	 */
	public void remove(IFigure figure) {
		super.remove(figure);
		revalidateBidi(this);
	}

	/**
	 * This method should be invoked whenever a change that can potentially
	 * affect the Bidi evaluation is made (eg., adding or removing children,
	 * changing text, etc.).
	 * <p>
	 * The default implementation delegates the revalidation task to the parent.
	 * Only {@link BlockFlow#revalidateBidi(IFigure) blocks} perform the actual
	 * revalidation.
	 * <p>
	 * The given IFigure is the one that triggered the revalidation. This can be
	 * used to optimize bidi evaluation.
	 * 
	 * @param origin
	 *            the figure that was revalidated
	 * @since 3.1
	 */
	protected void revalidateBidi(IFigure origin) {
		if (getParent() != null)
			((FlowFigure) getParent()).revalidateBidi(origin);
	}

	/**
	 * Sets the bidi information for this figure. A flow figure contributes bidi
	 * text in {@link #contributeBidi(BidiProcessor)}. If the figure contributes
	 * text associated with it, this method is called back to indicate the bidi
	 * properties for that text within its block.
	 * 
	 * @param info
	 *            the BidiInfo for this figure
	 * @since 3.1
	 */
	public void setBidiInfo(BidiInfo info) {
	}

	/**
	 * FlowFigures override setBounds() to prevent translation of children.
	 * "bounds" is a derived property for FlowFigures, calculated from the
	 * fragments that make up the FlowFigure.
	 * 
	 * @see Figure#setBounds(Rectangle)
	 */
	public void setBounds(Rectangle r) {
		if (bounds.equals(r))
			return;
		if (!r.contains(bounds))
			erase();
		bounds.x = r.x;
		bounds.y = r.y;
		bounds.width = r.width;
		bounds.height = r.height;
		fireFigureMoved();
		if (isCoordinateSystem())
			fireCoordinateSystemChanged();
		repaint();
	}

	/**
	 * Sets the flow context.
	 * 
	 * @param flowContext
	 *            the flow context for this flow figure
	 */
	public void setFlowContext(FlowContext flowContext) {
		((FlowFigureLayout) getLayoutManager()).setFlowContext(flowContext);
	}

	/**
	 * Sets the selection or a range of selection. A start value of -1 is used
	 * to indicate no selection. A start value >=0 indicates show selection. A
	 * start and end value can be used to represent a range of offsets which
	 * should render selection.
	 * 
	 * @param start
	 *            the start offset
	 * @param end
	 *            the end offset
	 * @since 3.1
	 */
	public void setSelection(int start, int end) {
		if (selectionStart == start)
			return;
		selectionStart = start;
		repaint();
	}

}
