package jp.sourceforge.asclipse.as3.adapter;

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

import jp.sourceforge.asclipse.as3.IAS3GlobalContext;
import jp.sourceforge.asclipse.as3.element.AS3ConstProperty;
import jp.sourceforge.asclipse.as3.element.AS3Element;
import jp.sourceforge.asclipse.as3.element.AS3ElementWithModifiers;
import jp.sourceforge.asclipse.as3.element.AS3Function;
import jp.sourceforge.asclipse.as3.element.AS3Interface;
import jp.sourceforge.asclipse.as3.element.AS3Property;
import jp.sourceforge.asclipse.as3.element.AS3Type;
import jp.sourceforge.asclipse.as3.element.AS3Variable;
import jp.sourceforge.asclipse.as3.resolver.AS3TypeRef;

/**
 * 特定の型から使用できるメンバ一覧を取得する拡張クラス。
 * <p>継承関係を判断してメンバを探す。</p>
 * @author shin1ogawa
 */
public class HierarchyMemberProvider extends AbstractAS3ElementAdapter {

	private enum HierarchyScope {
		Private, Protected, Internal, Public;

		public boolean inScope(AS3ElementWithModifiers element) {
			if (this == Private) {
				return true;
			} else if (this == Protected) {
				return element.isProtected() || element.isPublic();
			} else if (this == Internal) {
				return !element.isPrivate();
			} else if (this == Public) {
				return !element.isProtected();
			}
			throw new IllegalStateException(this.toString());
		}
	}


	private final IAS3GlobalContext globalContext;


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

	/**
	 * 特定の型から使用できるメンバ一覧を返す。
	 * @return 特定の型から使用できるメンバ一覧
	 */
	public List<AS3Member> getMembers() {
		List<AS3Member> members = new ArrayList<AS3Member>();
		AS3Element element = getElement();
		if (!hasAS3Type(element)) {
			// 型を持たない
			listupMembers(element, members, HierarchyScope.Private);
		} else {
			// 継承関係を追いかける必要がある。
			if (element instanceof AS3Type) {
				getMembersForType((AS3Type) element, (AS3Type) element, members);
			} else {
				List<AS3Element> children = element.getChildren();
				for (AS3Element child : children) {
					if (child instanceof AS3Type) {
						getMembersForType((AS3Type) child, (AS3Type) child, members);
						break;
					}
				}
			}
		}
		return members;
	}

	private void getMembersForType(AS3Type cardinalType, AS3Type type, List<AS3Member> members) {
		HierarchyScope listupScope = null;
		if (cardinalType == type) {
			listupScope = HierarchyScope.Private;
		} else if (cardinalType.getEnclosurePackage().getIdentifier().equals(
				type.getEnclosurePackage().getIdentifier())) {
			listupScope = HierarchyScope.Internal;
		} else {
			listupScope = HierarchyScope.Protected;
		}
		listupMembers(type, members, listupScope);
		TypeHierarchyProvider adapter = null;
		if (type.hasAdapter(TypeHierarchyProvider.class)) {
			adapter = type.getAdapter(TypeHierarchyProvider.class);
		} else {
			adapter = new TypeHierarchyProvider(globalContext);
			type.addAdapter(adapter);
		}
		List<AS3TypeRef> superTypes = adapter.getSuperTypes();
		for (AS3TypeRef typeRef : superTypes) {
			if (typeRef == AS3TypeRef.OBJECT_TYPE_REF) {
				getMembersForType(cardinalType, globalContext.getTypeElement("Object"), members);
			} else if (typeRef.isResolved()) {
				getMembersForType(cardinalType, typeRef.getResolvedType(), members);
			}
		}
	}

	private void listupMembers(AS3Element element, List<AS3Member> members, HierarchyScope scope) {
		List<AS3Element> children = element.getChildren();
		for (AS3Element child : children) {
			if (child instanceof AS3ElementWithModifiers) {
				if (element instanceof AS3Interface) {
					// 無条件に追加？
					addToMembers(members, element, child);
					continue;
				}
				AS3ElementWithModifiers member = (AS3ElementWithModifiers) child;
				if (member instanceof AS3Function && ((AS3Function) member).isConstructor()) {
					continue;
				} else if (scope.inScope(member)) {
					if (member instanceof AS3ConstProperty) {
						AS3ConstProperty property = (AS3ConstProperty) member;
						for (AS3Variable variable : property.getVariables()) {
							addToMembers(members, element, variable);
						}
					} else if (member instanceof AS3Property) {
						AS3Property property = (AS3Property) member;
						for (AS3Variable variable : property.getVariables()) {
							addToMembers(members, element, variable);
						}
					} else if (member instanceof AS3Function) {
						addToMembers(members, element, (AS3Function) member);
					}
				}
			}
		}
	}

	private void addToMembers(List<AS3Member> members, AS3Element enclosureType, AS3Element element) {
		if (element instanceof AS3Function) {
			for (AS3Member member : members) {
				if (member.getElement() instanceof AS3Function) {
					if (((AS3Function) member.getElement()).getIdentifier().equals(
							((AS3Function) element).getIdentifier())) {
						// overrideされたfunctionは追加しない
						return;
					}
				}
			}
		}
		members.add(new AS3Member(enclosureType, element));
	}


	/**
	 * @author shin1ogawa
	 */
	public static class AS3Member {

		private final AS3Element enclosureType;

		private final AS3Element element;


		/**
		 * @return the enclosureType
		 * @category accessor
		 */
		public AS3Element getEnclosureType() {
			return enclosureType;
		}

		/**
		 * @return the element
		 * @category accessor
		 */
		public AS3Element getElement() {
			return element;
		}

		/**
		 * Constructor.
		 * @param enclosureType
		 * @param element
		 * @category constructor
		 */
		public AS3Member(AS3Element enclosureType, AS3Element element) {
			this.enclosureType = enclosureType;
			this.element = element;
		}

		@Override
		public String toString() {
			if (enclosureType instanceof AS3Type) {
				return element.getOutlineTitle() + ":"
						+ ((AS3Type) enclosureType).getQualifiedName();
			} else {
				return element.getOutlineTitle() + ":MxmlEmbedded";
			}
		}
	}


	private static boolean hasAS3Type(AS3Element element) {
		if (element instanceof AS3Type) {
			return true;
		}
		for (AS3Element child : element.getChildren()) {
			boolean hasAS3Type = hasAS3Type(child);
			if (hasAS3Type) {
				return true;
			}
		}
		return false;
	}
}
