package jp.sourceforge.asclipse.as3.internal.resolver;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jp.sourceforge.asclipse.as3.IAS3Context;
import jp.sourceforge.asclipse.as3.IAS3GlobalContext;
import jp.sourceforge.asclipse.as3.element.AS3Import;
import jp.sourceforge.asclipse.as3.element.AS3Package;
import jp.sourceforge.asclipse.as3.element.AS3Type;
import jp.sourceforge.asclipse.as3.resolver.AS3TypeRef;
import jp.sourceforge.asclipse.as3.resolver.ITypeResolver;

import org.apache.commons.lang.StringUtils;

/**
 * {@link AS3TypeRef}を管理するクラス。
 * @author shin1ogawa
 */
public class TypeResolver implements ITypeResolver {

	private IAS3GlobalContext globalContext;

	private List<AS3TypeRef> typeRefs = new ArrayList<AS3TypeRef>();

	private Map<AS3Type, List<AS3TypeRef>> typeRefMap = new HashMap<AS3Type, List<AS3TypeRef>>();

	/** qualifiedNameをキーに{@AS3Type}をマップする */
	private Map<String, AS3Type> nameTypeMap = new HashMap<String, AS3Type>();


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

	public AS3TypeRef newTypeRef(String typeName) {
		DefaultAS3TypeRef typeRef = new DefaultAS3TypeRef(typeName);
		typeRefs.add(typeRef);
		return typeRef;
	}

	public AS3TypeRef newTypeRef(AS3Type type) {
		AS3TypeRef typeRef;
		List<AS3TypeRef> list = typeRefMap.get(type);
		if (list == null || list.isEmpty()) {
			list = new ArrayList<AS3TypeRef>(1);
			typeRef = new DefaultAS3TypeRef(type);
			list.add(typeRef);
			nameTypeMap.put(type.getQualifiedName(), type);
			typeRefMap.put(type, list);
			typeRefs.add(typeRef);
		} else {
			typeRef = list.get(0);
		}
		return typeRef;
	}

	public void unresolve(AS3Type type) throws NullPointerException {
		if (type == null) {
			throw new NullPointerException();
		}
		List<AS3TypeRef> list = typeRefMap.get(type);
		if (list != null) {
			for (AS3TypeRef typeRef : list) {
				typeRef.unresolve();
			}
		}
	}

	public void updateType(AS3Type oldType, AS3Type newType) throws NullPointerException {
		if (oldType == null || newType == null) {
			throw new NullPointerException();
		}
		List<AS3TypeRef> list = typeRefMap.get(oldType);
		if (list != null) {
			for (AS3TypeRef typeRef : list) {
				typeRef.setResolvedType(newType);
			}
		}
	}

	public boolean resolve(AS3TypeRef typeRef, AS3Type enclosureType) {
		String typeName = typeRef.getTypeName();
		List<IAS3Context> contexts = globalContext.getContexts();
		// そのままの名称で探す。
		for (IAS3Context context : contexts) {
			AS3Type typeElement = context.getTypeElement(typeName);
			if (typeElement != null) {
				typeRef.setResolvedType(typeElement);
				return true;
			}
		}
		if (enclosureType == null) {
			return false;
		}

		// TypeRefが定義された型が保持するimport宣言で直撃するものがないか探す。
		String qualifiedName = null;
		List<AS3Import> importDeclarations = enclosureType.getImportDeclarations();
		for (AS3Import importDecl : importDeclarations) {
			if (!importDecl.isSingleType()) {
				continue;
			}
			String importTypeName = importDecl.getTypeRef().getTypeName();
			if (importTypeName.endsWith("." + typeName)) {
				qualifiedName = importTypeName;
				for (IAS3Context context : contexts) {
					AS3Type typeElement = context.getTypeElement(qualifiedName);
					if (typeElement != null) {
						typeRef.setResolvedType(typeElement);
						return true;
					}
				}
				return false;
			}
		}

		// import ...*を使って探す。
		for (AS3Import importDecl : importDeclarations) {
			if (importDecl.isSingleType()) {
				continue;
			}
			String importTypeName = importDecl.getTypeRef().getTypeName();
			importTypeName = importTypeName.substring(0, importTypeName.length() - 1); // 末尾の*をはずす。
			qualifiedName = importTypeName + typeName;
			for (IAS3Context context : contexts) {
				AS3Type typeElement = context.getTypeElement(qualifiedName);
				if (typeElement != null) {
					typeRef.setResolvedType(typeElement);
					return true;
				}
			}
		}

		// typeRefが定義されたパッケージを基準に探す。
		AS3Package enclosurePackage = enclosureType.getEnclosurePackage();
		String identifier = enclosurePackage.getIdentifier();
		if (!StringUtils.isEmpty(identifier)) {
			qualifiedName = identifier + "." + typeName;
			for (IAS3Context context : contexts) {
				AS3Type typeElement = context.getTypeElement(qualifiedName);
				if (typeElement != null) {
					typeRef.setResolvedType(typeElement);
					return true;
				}
			}
		}
		return false;
	}

	public void removeType(AS3Type type) {
		List<AS3TypeRef> list = typeRefMap.get(type);
		if (list != null) {
			for (AS3TypeRef typeRef : list) {
				typeRef.setResolvedType(null);
			}
		}
	}
}
