/******************************************************************************* 
 * Copyright (c) 2011 Red Hat, Inc. 
 * Distributed under license by Red Hat, Inc. All rights reserved. 
 * This program is 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: 
 * Red Hat, Inc. - initial API and implementation 
 ******************************************************************************/
package org.jboss.tools.cdi.seam.config.core.util;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.jboss.tools.cdi.core.CDICoreNature;
import org.jboss.tools.cdi.core.IRootDefinitionContext;
import org.jboss.tools.cdi.seam.config.core.CDISeamConfigConstants;
import org.jboss.tools.cdi.seam.config.core.definition.SeamMethodDefinition;
import org.jboss.tools.cdi.seam.config.core.definition.SeamParameterDefinition;
import org.jboss.tools.cdi.seam.config.core.xml.SAXElement;
import org.jboss.tools.cdi.seam.config.core.xml.SAXText;
import org.jboss.tools.common.java.IParametedType;

/**
 *   
 * @author Viacheslav Kabanovich
 *
 */
public class Util implements CDISeamConfigConstants {
	public static Map<String, String> EE_TYPES = new HashMap<String, String>();
	public static Map<String, String> EE_TYPES_30 = new HashMap<String, String>();
	
	static {
		EE_TYPES.put("int", "java.lang.Integer");
		EE_TYPES.put("short", "java.lang.Short");
		EE_TYPES.put("long", "java.lang.Long");
		EE_TYPES.put("char", "java.lang.Character");
		EE_TYPES.put("byte", "java.lang.Byte");
		EE_TYPES.put("boolean", "java.lang.Boolean");
		EE_TYPES.put("double", "java.lang.Double");
		EE_TYPES.put("float", "java.lang.Float");
		
		String[] JAVA_LANG = {"Integer", "Short", "Long", "String", "Character", "Byte", "Boolean", "Double", "Float"};
		for (String s: JAVA_LANG) EE_TYPES.put(s, "java.lang." + s);

		String[] JAVA_UTIL = {"List", "Map", "Set"};
		for (String s: JAVA_UTIL) EE_TYPES.put(s, "java.util." + s);
		
		String[] JAVAX_ANNOTATION = {"Generated", "PostConstruct", "PreDestroy", "Resource", "Resources"};
		for (String s: JAVAX_ANNOTATION) EE_TYPES.put(s, "javax.annotation." + s);
		
		String[] JAVAX_INJECT = {"Inject", "Named", "Provider", "Qualifier", "Scope", "Singleton"};
		for (String s: JAVAX_INJECT) EE_TYPES.put(s, "javax.inject." + s);
		
		String[] JAVAX_ENTERPRISE_INJECT = {"Alternative", "AmbiguousResolutionException", "Any", "CreationException", 
				"Default", "Disposes", "IllegalProductException", "InjectionException", "Instance", "Model", "New", 
				"Produces", "ResolutionException", "Specializes", "Stereotype", "Typed",
				"UnproxyableResolutionException", "UnsatisfiedResolutionException"};
		for (String s: JAVAX_ENTERPRISE_INJECT) EE_TYPES.put(s, "javax.enterprise.inject." + s);

		String[] JAVAX_ENTERPRISE_CONTEXT = {"ApplicationScoped", "BusyConversationException", "ContextException", 
				"ContextNotActiveException", "Conversation", "ConversationScoped", "Dependent",
				"NonexistentConversationException", "NormalScope", "RequestScoped", "SessionScoped"};
		for (String s: JAVAX_ENTERPRISE_CONTEXT) EE_TYPES.put(s, "javax.enterprise.context." + s);
		
		String[] JAVAX_ENTERPRISE_EVENT = {"Event", "ObserverException", "Observes", "Reception", "TransactionPhase"};
		for (String s: JAVAX_ENTERPRISE_EVENT) EE_TYPES.put(s, "javax.enterprise.event." + s);
		
		String[] JAVAX_DECORAROR = {"Decorator", "Delegate"};
		for (String s: JAVAX_DECORAROR) EE_TYPES.put(s, "javax.decorator." + s);
		
		String[] JAVAX_INTERCEPTOR = {"AroundInvoke", "AroundTimeout", "ExcludeClassInterceptors", "ExcludeDefaultInterceptors",
				"Interceptor", "InterceptorBinding", "Interceptors", "InvocationContext"};
		for (String s: JAVAX_INTERCEPTOR) EE_TYPES.put(s, "javax.interceptor." + s);

		//It is not clear.
		// In Seam3 doc, item 6.7. Overriding the type of an injection point is devoted to @Exact,
		// but item 6.1 does not mentions its package in namespace urn:java:ee.
		String[] SEAM_SOLDER = {"Exact"};
		for (String s: SEAM_SOLDER) {
			EE_TYPES.put(s, "org.jboss.solder.core." + s);
			EE_TYPES_30.put(s, "org.jboss.seam.solder.core." + s);
		}
		
	}

	public static boolean isConfigRelevant(SAXElement element) {
		String uri = element.getURI();
		return (uri != null && uri.startsWith(CDISeamConfigConstants.URI_PREFIX));
	}

	public static IType resolveType(SAXElement element, CDICoreNature project) {
		return resolveType(element.getLocalName(), element.getURI(), project);
	}

	public static IType resolveType(String name, String uri, CDICoreNature project) {
		if(uri == null || !uri.startsWith(CDISeamConfigConstants.URI_PREFIX)) {
			return null;
		}
		String[] packages = getPackages(uri);
		for (String pkg: packages) {
			if(pkg.length() == 0) continue;
			IType type = null;
			if(pkg.equals(PACKAGE_EE)) {
				type = project.getType(Util.EE_TYPES.get(name));
				if(type == null) {
					type = project.getType(Util.EE_TYPES_30.get(name));
				}
			} else {
				type = project.getType(pkg + "." + name);
			}
			if(type != null) {
				return type;
			}
		}
		return null;
	}

	/**
	 * Computes possible type names that could resolve type for the element, 
	 * if one of these types existed.
	 * Returns empty set if a) element has no prefix, b) uri is not urn:java:,
	 * c) package is 'ee'.
	 * 
	 * @param element
	 * @return
	 */
	public static Set<String> getPossibleTypeNames(SAXElement element) {
		Set<String> result = new HashSet<String>();
		String name = element.getLocalName();
		String uri = element.getURI();
		if(uri != null && uri.startsWith(CDISeamConfigConstants.URI_PREFIX)) {
			String[] packages = getPackages(uri);
			for (String pkg: packages) {
				if(pkg.length() > 0 && !pkg.equals(PACKAGE_EE)) {
					result.add(pkg + "." + name);
				}
			}
		}
		return result;
	}

	public static IMember resolveMember(IType type, SAXElement element) throws JavaModelException {
		String name = element.getLocalName();
		IField f = type.getField(name);
		if(f != null && f.exists() && !hasParametersOrArrayChild(element)) {
			return f;
		}
		IMethod[] ms = type.getMethods();
		for (IMethod m: ms) {
			if(name.equals(m.getElementName())) {
				//that is only a preliminary resolving. Exact method will be found on loading parameters.
				return m;
			}
		}
		
		return null;
	}

	public static String[] getPackages(String uri) {
		if(uri == null || !uri.startsWith(CDISeamConfigConstants.URI_PREFIX)) {
			return new String[0];
		}
		uri = uri.substring(CDISeamConfigConstants.URI_PREFIX.length());
		return uri.split(":");
	}

	public static boolean containsEEPackage(SAXElement element) {
		return containsEEPackage(element.getURI());
	}

	public static boolean containsEEPackage(String uri) {
		String[] ps = getPackages(uri);
		for (String p: ps) if(CDISeamConfigConstants.PACKAGE_EE.equals(p)) {
			return true;
		}
		return false;
	}

	public static boolean isArray(SAXElement element) {
		return isKeyword(element, KEYWORD_ARRAY);
	}

	public static boolean isEntry(SAXElement element) {
		return isKeyword(element, KEYWORD_ENTRY, KEYWORD_E);
	}

	public static boolean isKey(SAXElement element) {
		return isKeyword(element, KEYWORD_KEY, KEYWORD_K);
	}

	public static boolean isValue(SAXElement element) {
		return isKeyword(element, KEYWORD_VALUE, KEYWORD_V);
	}

	public static boolean isParameters(SAXElement element) {
		return isKeyword(element, KEYWORD_PARAMETERS);
	}

	public static boolean isKeyword(SAXElement element, String keyword) {
		return keyword.equals(element.getLocalName()) && containsEEPackage(element);
	}

	public static boolean isKeyword(SAXElement element, String keyword1, String keyword2) {
		String n = element.getLocalName();
		return (keyword1.equals(n) || keyword2.equals(n)) && containsEEPackage(element);
	}

	public static boolean hasText(SAXElement element) {
		SAXText t = element.getTextNode();
		return t != null && t.getValue() != null && t.getValue().trim().length() > 0;
	}

	public static boolean hasProducesChild(SAXElement element) {
		List<SAXElement> cs = element.getChildElements();
		for (SAXElement c: cs) {
			if(containsEEPackage(c) && "Produces".equals(c.getLocalName())) {
				return true;
			}
		}
		return false;
	}

	public static boolean hasParametersOrArrayChild(SAXElement element) {
		List<SAXElement> cs = element.getChildElements();
		for (SAXElement c: cs) {
			if(isParameters(c) || isArray(c)) return true;
		}
		return false;
	}

	/**
	 * 
	 * @param def
	 * @param type
	 * @param name method name or null for constructor
	 * @return
	 */
	public static IMethod findMethod(SeamMethodDefinition def, IType type, String name, IRootDefinitionContext context) throws JavaModelException {
		IMethod[] ms = type.getMethods();
		for (IMethod m: ms) {
			if((name == null && m.isConstructor()) || (name != null && name.equals(m.getElementName()))) {
				if(sameParameterTypes(def, m, context)) return m;
			}
		}
		return null;
	}

	static boolean sameParameterTypes(SeamMethodDefinition def, IMethod m, IRootDefinitionContext context) throws JavaModelException {
		String[] paramTypes = m.getParameterTypes();
		if(paramTypes.length != def.getParameters().size()) return false;
		if(paramTypes.length == 0) return true;
		for (int i = 0; i < paramTypes.length; i++) {
			String paramType = paramTypes[i];
			SeamParameterDefinition p = def.getParameters().get(i);
			if(p.getDimensions() != Signature.getArrayCount(paramType)) {
				return false;
			}
			IParametedType pt = context.getProject().getTypeFactory().getParametedType(m, paramType);
			if(pt == null || p.getType() == null) return false;
			if(!pt.getType().getFullyQualifiedName().equals(p.getType().getFullyQualifiedName())) return false; 
		}
		return true;
	}

}
