package jp.sourceforge.asclipse.as3.adapter;

import java.util.ArrayList;
import java.util.List;

import jp.sourceforge.asclipse.as3.element.AS3Class;
import jp.sourceforge.asclipse.as3.element.AS3ConstProperty;
import jp.sourceforge.asclipse.as3.element.AS3Element;
import jp.sourceforge.asclipse.as3.element.AS3Function;
import jp.sourceforge.asclipse.as3.element.AS3Import;
import jp.sourceforge.asclipse.as3.element.AS3Interface;
import jp.sourceforge.asclipse.as3.element.AS3NamespaceDirective;
import jp.sourceforge.asclipse.as3.element.AS3Package;
import jp.sourceforge.asclipse.as3.element.AS3Property;
import jp.sourceforge.asclipse.as3.element.AS3Root;
import jp.sourceforge.asclipse.as3.element.AS3UseNamespaceDirective;
import jp.sourceforge.asclipse.as3.element.AS3Variable;
import jp.sourceforge.asclipse.as3.internal.element.AbstractAS3Element;

/**
 * アウトラインを表示するための拡張。
 * <p>実際は構造に存在しない要素をOutline要素として返す必要があるため、Outline専用の親子構造を作成する。</p>
 * <ul><li>importが直接ぶらさがるのではなく、import declarations、というリストを差し込む</li>
 * <li>propertyが直接ぶら下がるのではなく、property配下のvariableをぶらさげ、propertyは省く</li></ul>
 * @author shin1ogawa
 */
public class OutlineProvider extends AbstractAS3ElementAdapter {

	/**
	 * {@link AS3Element}のリストを保持するためだけの、実際には要素として存在しないクラス。
	 * <p>AS3Classの配下に存在するAS3ImportをそのままOutlineに表示するのではなく、
	 * Listに格納してAS3Class/Imports/Import...としたいため。</p>
	 * @author shin1ogawa
	 */
	@SuppressWarnings("serial")
	public static class AS3Elements extends AbstractAS3Element {

		private final String label;


		/**
		 * Constructor.
		 * @param label
		 * @param parent
		 * @category constructor
		 */
		public AS3Elements(String label, AS3Element parent) {
			super(-1, -1, -1, -1);
			this.label = label;
			setParent(parent);
		}

		public String getOutlineTitle() {
			return label;
		}

		public String getTitle() {
			return label;
		}
	}


	private final AS3Element outlineParent;

	private List<AS3Element> outlineChildren = new ArrayList<AS3Element>();

	private boolean isLeaf = false;

	private static final List<AS3Element> EMPTY_LIST = new ArrayList<AS3Element>(0);


	/**
	 * Constructor.
	 * @param outlineParent
	 * @category constructor
	 */
	public OutlineProvider(AS3Element outlineParent) {
		super();
		this.outlineParent = outlineParent;
	}

	/**
	 * @return Outline表示用の親要素
	 */
	public AS3Element getOutlineParent() {
		if (!isConnect() || isEnqueued()) {
			return null;
		}
		return outlineParent;
	}

	/**
	 * @return Outline表示用の子要素のリスト
	 */
	public List<AS3Element> getOutlineChildren() {
		if (!isConnect() || isEnqueued()) {
			return EMPTY_LIST;
		}
		return outlineChildren;
	}

	/**
	 * @return Outline表示用の子要素を持つなら{@code true}
	 */
	public boolean isLeaf() {
		return isLeaf;
	}

	@Override
	public void connect(AS3Element element) {
		super.connect(element);
		if (isLeaf(element)) {
			isLeaf = true;
			// 子要素を処理しない。
			return;
		}

		List<AS3Element> children = element.getChildren();
		List<AS3Element> importDecls = new ArrayList<AS3Element>();
		AS3Elements importDeclsElement = null;
		OutlineProvider childAdapter = null;
		for (AS3Element child : children) {
			if (child instanceof AS3Import) {
				importDecls.add(child);
				if (importDeclsElement == null) {
					importDeclsElement = new AS3Elements("Import declarations", element);
					outlineChildren.add(importDeclsElement);
					childAdapter = new OutlineProvider(element);
					importDeclsElement.addAdapter(childAdapter);
				} else {
					childAdapter = importDeclsElement.getAdapter(OutlineProvider.class);
				}
				childAdapter.outlineChildren.add(child);
				child.addAdapter(new OutlineProvider(importDeclsElement));
			} else if (child instanceof AS3ConstProperty || child instanceof AS3Property) {
				// PropertyにぶらさがるVariableを子要素とする。
				List<AS3Variable> variables =
						child instanceof AS3ConstProperty ? ((AS3ConstProperty) child)
							.getVariables() : ((AS3Property) child).getVariables();
				for (AS3Variable variable : variables) {
					childAdapter = new OutlineProvider(element);
					outlineChildren.add(variable);
					variable.addAdapter(childAdapter);
				}
			} else if (child instanceof AS3Package || child instanceof AS3Class
					|| child instanceof AS3Interface || child instanceof AS3Function
					|| child instanceof AS3UseNamespaceDirective
					|| child instanceof AS3NamespaceDirective) {
				outlineChildren.add(child);
				childAdapter = new OutlineProvider(element);
				child.addAdapter(childAdapter);
			}
		}
	}

	private static boolean isLeaf(AS3Element element) {
		return !(element instanceof AS3Root) && !(element instanceof AS3Package)
				&& !(element instanceof AS3Class) && !(element instanceof AS3Interface)
				&& !(element instanceof AS3Elements);
	}
}
