package jp.sourceforge.asclipse.as3.adapter;

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

import jp.sourceforge.asclipse.as3.IAS3GlobalContext;
import jp.sourceforge.asclipse.as3.element.AS3Class;
import jp.sourceforge.asclipse.as3.element.AS3Element;
import jp.sourceforge.asclipse.as3.element.AS3Interface;
import jp.sourceforge.asclipse.as3.element.AS3Type;
import jp.sourceforge.asclipse.as3.resolver.AS3TypeRef;

/**
 * 型階層を表示するための拡張。
 * @author shin1ogawa
 */
public class TypeHierarchyProvider extends AbstractAS3ElementAdapter {

	private final IAS3GlobalContext globalContext;

	private AS3Type typeElement;


	/**
	 * Constructor.
	 * @param globalContext 
	 * @category constructor
	 */
	public TypeHierarchyProvider(IAS3GlobalContext globalContext) {
		super();
		this.globalContext = globalContext;
	}


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

	private String qualifiedName;


	/**
	 * サブクラスや実装クラスのリストを返す。
	 * TODO キャッシュとかせずに毎回コレをやると時間がかかると思う！
	 * @return サブクラスや実装クラスのリスト
	 */
	public List<AS3TypeRef> getSubTypes() {
		if (!isConnect() || isEnqueued()) {
			return EMPTY_LIST;
		}
		if (typeElement instanceof AS3Class) {
			return getClassSubTypes(globalContext.getAllTypes());
		} else if (typeElement instanceof AS3Interface) {
			return getInterfaceSubTypes(globalContext.getAllTypes());
		}
		throw new IllegalStateException("unexpected AS3Type.");
	}

	private List<AS3TypeRef> getClassSubTypes(Collection<AS3Type> allTypes) {
		List<AS3TypeRef> subTypes = new ArrayList<AS3TypeRef>();
		for (AS3Type type : allTypes) {
			if (type instanceof AS3Class) {
				if (isSubType(((AS3Class) type).getExtendsTypeRef(), type)) {
					subTypes.add(globalContext.getTypeResolver().newTypeRef(type));
				}
			}
		}
		return subTypes;
	}

	private List<AS3TypeRef> getInterfaceSubTypes(Collection<AS3Type> allTypes) {
		List<AS3TypeRef> subTypes = new ArrayList<AS3TypeRef>();
		for (AS3Type type : allTypes) {
			List<AS3TypeRef> typeRefs = null;
			if (type instanceof AS3Class) {
				typeRefs = ((AS3Class) type).getImplementsTypeRefs();
			} else if (type instanceof AS3Interface) {
				typeRefs = ((AS3Interface) type).getExtendsTypeRefs();
			}
			for (AS3TypeRef typeRef : typeRefs) {
				if (isSubType(typeRef, type)) {
					subTypes.add(globalContext.getTypeResolver().newTypeRef(type));
					break;
				}
			}
		}
		return subTypes;
	}

	private boolean isSubType(AS3TypeRef typeRef, AS3Type enclosureType) {
		if (typeRef == null || typeRef == AS3TypeRef.NULL_TYPE_REF) {
			return false;
		}
		if (!typeRef.isResolved()) {
			globalContext.resolve(typeRef, enclosureType);
		}
		if (typeRef.isResolved()) {
			if (typeRef.getQualifiedName().equals(qualifiedName)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 親クラスや実装しているインターフェースのリストを返す。
	 * <p>{@literal Show the Supertype hierarchy}で使用する。</p>
	 * <p>extends, implements...の順で返す。</p>
	 * @return 親クラスや実装しているインターフェースのリスト
	 */
	public List<AS3TypeRef> getSuperTypes() {
		if (!isConnect() || isEnqueued() || typeElement == null) {
			return EMPTY_LIST;
		}
		List<AS3TypeRef> superTypes = new ArrayList<AS3TypeRef>(1);
		if (typeElement instanceof AS3Class) {

			AS3TypeRef extendsTypeRef = ((AS3Class) typeElement).getExtendsTypeRef();
			resolveTypeRef(extendsTypeRef);
			superTypes.add(extendsTypeRef);

			List<AS3TypeRef> implementsTypeRefs = ((AS3Class) typeElement).getImplementsTypeRefs();
			for (AS3TypeRef typeRef : implementsTypeRefs) {
				resolveTypeRef(typeRef);
				superTypes.add(typeRef);
			}
		} else if (typeElement instanceof AS3Interface) {
			List<AS3TypeRef> extendsTypeRefs = ((AS3Interface) typeElement).getExtendsTypeRefs();
			for (AS3TypeRef typeRef : extendsTypeRefs) {
				resolveTypeRef(typeRef);
				superTypes.add(typeRef);
			}
		} else {
			throw new IllegalStateException("unexpected AS3Type.");
		}
		return superTypes;
	}

	private void resolveTypeRef(AS3TypeRef typeRef) {
		globalContext.resolve(typeRef, typeElement);
	}

	/**
	 * 親クラスへの参照オブジェクトを返す。
	 * <p>interfaceの場合は{@code null}を返す。</p>
	 * <p>classの場合でextendsが指定されていない場合は{@link AS3TypeRef#OBJECT_TYPE_REF}を返す。</p>
	 * @return 親クラスへの参照オブジェクト。ルートの場合は{@code null}。
	 */
	public AS3TypeRef getSuperType() {
		if (!isConnect() || isEnqueued() || typeElement == null) {
			return null;
		}
		if (typeElement instanceof AS3Interface) {
			return null;
		}
		if (!(typeElement instanceof AS3Class)) {
			throw new IllegalStateException("unexpected AS3Type.");
		}
		AS3TypeRef extendsTypeRef = ((AS3Class) typeElement).getExtendsTypeRef();
		if (extendsTypeRef == AS3TypeRef.NULL_TYPE_REF) {
			return AS3TypeRef.OBJECT_TYPE_REF;
		}
		resolveTypeRef(extendsTypeRef);
		return extendsTypeRef;
	}

	@Override
	public void connect(AS3Element element) {
		super.connect(element);
		typeElement = getTypeElement(element);
		qualifiedName = typeElement.getQualifiedName();
	}

	private static AS3Type getTypeElement(AS3Element element) {
		if (AS3Type.class.isAssignableFrom(element.getClass())) {
			return (AS3Type) element;
		}
		List<AS3Element> children = element.getChildren();
		for (AS3Element child : children) {
			AS3Type firstElement = getTypeElement(child);
			if (firstElement != null) {
				return firstElement;
			}
		}
		return null;
	}
}
