/*******************************************************************************
 * Copyright (c) 2004, 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.ArrayList;
import java.util.List;

import org.eclipse.draw2d.geometry.Rectangle;

/**
 * LineRoot is the top-most container on a line of text displayed in Draw2d.
 * Hence, a LineRoot can tell you of things like the highest ascent or descent
 * on a line, which is required to display selection and such. All
 * {@link org.eclipse.draw2d.text.ContentBox fragments} know of the LineRoot
 * they belong to.
 * 
 * @author Randy Hudson
 * @author Pratik Shah
 * @since 3.1
 */
public class LineRoot extends LineBox {

	private int baseline;
	private boolean isMirrored;

	/**
	 * Constructor
	 * 
	 * @param isMirrored
	 *            <code>true</code> if the line is to be displayed in a mirrored
	 *            control
	 */
	public LineRoot(boolean isMirrored) {
		this.isMirrored = isMirrored;
	}

	/**
	 * @see org.eclipse.draw2d.text.CompositeBox#add(org.eclipse.draw2d.text.FlowBox)
	 */
	public void add(FlowBox child) {
		super.add(child);
		child.setLineRoot(this);
	}

	private void bidiCommit() {
		int xLocation = getX();
		BidiLevelNode root = new BidiLevelNode();
		List branches = new ArrayList();
		// branches does not include this LineRoot; all the non-leaf child
		// fragments of a
		// parent will be listed before the parent itself in this list
		buildBidiTree(this, root, branches);
		List result = new ArrayList();
		root.emit(result);
		int i = isMirrored ? result.size() - 1 : 0;
		while (i >= 0 && i < result.size()) {
			FlowBox box = (FlowBox) result.get(i);
			box.setX(xLocation);
			xLocation += box.getWidth();
			i += isMirrored ? -1 : 1;
		}
		// set the bounds of the composite boxes, and break overlapping ones
		// into two
		layoutNestedLines(branches);
	}

	private void buildBidiTree(FlowBox box, BidiLevelNode node, List branches) {
		if (box instanceof LineBox) {
			List children = ((LineBox) box).getFragments();
			for (int i = 0; i < children.size(); i++)
				buildBidiTree((FlowBox) children.get(i), node, branches);
			if (box != this)
				branches.add(box);
		} else {
			ContentBox leafBox = (ContentBox) box;
			while (leafBox.getBidiLevel() < node.level)
				node = node.pop();
			while (leafBox.getBidiLevel() > node.level)
				node = node.push();
			node.add(leafBox);
		}
	}

	/**
	 * Committing a LineRoot will position its children correctly. All children
	 * boxes are made to have the same baseline, and are laid out according to
	 * the Unicode BiDi Algorithm, or left-to-right if Bidi is not necessary.
	 */
	public void commit() {
		if (requiresBidi())
			bidiCommit();
		else
			contiguousCommit(this, getX());
	}

	/**
	 * A LineRoot cannot be targetted.
	 * 
	 * @see org.eclipse.draw2d.text.FlowBox#containsPoint(int, int)
	 */
	public boolean containsPoint(int x, int y) {
		return false;
	}

	/*
	 * Simply lays out all fragments from left-to-right in the order in which
	 * they're contained.
	 */
	private void contiguousCommit(FlowBox box, int x) {
		box.setX(x);
		if (box instanceof LineBox) {
			List fragments = ((LineBox) box).getFragments();
			int i = isMirrored ? fragments.size() - 1 : 0;
			while (i >= 0 && i < fragments.size()) {
				FlowBox child = (FlowBox) fragments.get(i);
				contiguousCommit(child, x);
				x += child.getWidth();
				i += isMirrored ? -1 : 1;
			}
		}
	}

	private Result findParent(NestedLine line, List branches, int afterIndex) {
		for (int i = afterIndex + 1; i < branches.size(); i++) {
			NestedLine box = (NestedLine) branches.get(i);
			int index = box.getFragments().indexOf(line);
			if (index >= 0)
				return new Result(box, index);
		}
		return new Result(this, getFragments().indexOf(line));
	}

	/**
	 * @see org.eclipse.draw2d.text.FlowBox#getBaseline()
	 */
	public int getBaseline() {
		return baseline;
	}

	LineRoot getLineRoot() {
		return this;
	}

	int getVisibleBottom() {
		return baseline + contentDescent;
	}

	int getVisibleTop() {
		return baseline - contentAscent;
	}

	private void layoutNestedLines(List branches) {
		for (int i = 0; i < branches.size(); i++) {
			NestedLine parent = (NestedLine) branches.get(i);
			FlowBox prevChild = null;
			Rectangle bounds = null;
			List frags = parent.getFragments();
			for (int j = 0; j < frags.size(); j++) {
				FlowBox child = (FlowBox) frags.get(j);
				if (prevChild != null
						&& prevChild.getX() + prevChild.width != child.getX()
						&& child.getX() + child.width != prevChild.getX()) {
					// the boxes are not adjacent, and hence the parent box
					// needs to
					// be broken up
					InlineFlow parentFig = parent.owner;
					// Create and initialize a new line box
					NestedLine newBox = new NestedLine(parentFig);
					newBox.setLineRoot(this);
					// Add all remaining fragments from the current line box to
					// the new one
					for (int k = j; k < frags.size();)
						newBox.fragments.add(frags.remove(k));
					// Add the new line box to the parent box's list of
					// fragments
					Result result = findParent(parent, branches, i);
					result.parent.getFragments().add(result.index + 1, newBox);
					// Add the new line box to the flow figure's list of
					// fragments
					parentFig.fragments.add(
							parentFig.fragments.indexOf(parent) + 1, newBox);
					branches.add(i + 1, newBox);
					break;
				}
				if (bounds == null)
					bounds = new Rectangle(child.getX(), 1, child.getWidth(), 1);
				else
					bounds.union(child.getX(), 1, child.getWidth(), 1);
				prevChild = child;
			}
			parent.setX(bounds.x);
			parent.setWidth(bounds.width);
		}
	}

	/**
	 * Positions the line vertically by settings its baseline.
	 * 
	 * @param baseline
	 *            the baseline
	 */
	public void setBaseline(int baseline) {
		this.baseline = baseline;
	}

	/**
	 * @see org.eclipse.draw2d.text.CompositeBox#setLineTop(int)
	 */
	public void setLineTop(int top) {
		this.baseline = top + getAscent();
	}

	private static class BidiLevelNode extends ArrayList {
		int level;
		final BidiLevelNode parent;

		BidiLevelNode() {
			this(null, 0);
		}

		BidiLevelNode(BidiLevelNode parent, int level) {
			this.parent = parent;
			this.level = level;
		}

		void emit(List list) {
			if (level % 2 == 1) {
				for (int i = size() - 1; i >= 0; i--) {
					Object child = get(i);
					if (child instanceof BidiLevelNode)
						((BidiLevelNode) child).emit(list);
					else
						list.add(child);
				}
			} else {
				for (int i = 0; i < size(); i++) {
					Object child = get(i);
					if (child instanceof BidiLevelNode)
						((BidiLevelNode) child).emit(list);
					else
						list.add(child);
				}
			}
		}

		BidiLevelNode pop() {
			return parent;
		}

		BidiLevelNode push() {
			if (!isEmpty()) {
				Object last = get(size() - 1);
				if (last instanceof BidiLevelNode
						&& ((BidiLevelNode) last).level == level + 1)
					return (BidiLevelNode) last;
			}
			BidiLevelNode child = new BidiLevelNode(this, level + 1);
			add(child);
			return child;
		}
	}

	private static class Result {
		private int index;
		private LineBox parent;

		private Result(LineBox box, int i) {
			parent = box;
			index = i;
		}
	}

}