/******************************************************************************* 
 * Copyright (c) 2009 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.common.java;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.jboss.tools.common.core.CommonCorePlugin;
import org.jboss.tools.common.util.EclipseJavaUtil;

public class ParametedTypeFactory { 
	// I S J C F D Z
	static Map<Character,String> primitives = new HashMap<Character, String>();
	static {
		primitives.put(Signature.C_INT, "Ljava.lang.Integer;"); //$NON-NLS-1$
		primitives.put(Signature.C_SHORT, "Ljava.lang.Short;"); //$NON-NLS-1$
		primitives.put(Signature.C_LONG, "Ljava.lang.Long;"); //$NON-NLS-1$
		primitives.put(Signature.C_CHAR, "Ljava.lang.Character;"); //$NON-NLS-1$
		primitives.put(Signature.C_FLOAT, "Ljava.lang.Float;"); //$NON-NLS-1$
		primitives.put(Signature.C_DOUBLE, "Ljava.lang.Double;"); //$NON-NLS-1$
		primitives.put(Signature.C_BOOLEAN, "Ljava.lang.Boolean;"); //$NON-NLS-1$
		primitives.put(Signature.C_BYTE, "Ljava.lang.Boolean;"); //$NON-NLS-1$
	}
	//unresolved Object signature
	public static String OBJECT = "QObject;"; //$NON-NLS-1$
	Map<String, ParametedType> cache = new Hashtable<String, ParametedType>();

	public ParametedType newParametedType(IType type) {
		ParametedType parametedType = new ParametedType();
		if(type != null && !type.isBinary()) {
			ISourceRange r = null;
			try {
				r = type.getNameRange();
			} catch (CoreException e) {
				CommonCorePlugin.getDefault().logError(e);
			}
			if(r != null) {
				parametedType = new TypeDeclaration(parametedType, type.getResource(), r.getOffset(), r.getLength());
			}
		}
		parametedType.setFactory(this);
		parametedType.setType(type);
		if(type != null) parametedType.setSignature(Signature.C_RESOLVED + type.getFullyQualifiedName() + Signature.C_SEMICOLON);
		String[] ps = null;
		try {
			ps = type.getTypeParameterSignatures();
		} catch (JavaModelException e) {
			CommonCorePlugin.getDefault().logError(e);
		}
		if(ps != null && ps.length > 0) {
			for (int i = 0; i < ps.length; i++) {
				try {
					ParametedType p = getParametedTypeForParameter(type, ps[i], null);
					if(p != null) parametedType.addParameter(p);
				} catch (JavaModelException e) {
					CommonCorePlugin.getDefault().logError(e);
				}
			}
		}
		return parametedType;
	}

	public ParametedType getParametedType(IMember context, String typeSignature) throws JavaModelException {
		return getParametedType(context, null, typeSignature);
	}

	public ParametedType getParametedType(IMember context, IParametedType basetype, String typeSignature) throws JavaModelException {
		if(typeSignature == null) return null;
		if(basetype != null) {
			ParametedType param = ((ParametedType)basetype).findParameter(typeSignature);
			if(param != null) return param;
		}
		
		IType contextType = context instanceof IType ? (IType)context : context.getDeclaringType();

		String key = context == null || context.isBinary() || OBJECT.equals(typeSignature) ? typeSignature : contextType.getFullyQualifiedName() + "+" + typeSignature;
		if(cache.containsKey(key)) return cache.get(key);
		ParametedType result = new ParametedType();
		result.setFactory(this);
		result.setSignature(typeSignature);

		typeSignature = typeSignature.substring(result.getArrayIndex());
		
		char c = typeSignature.length() == 0 ? '\0' : typeSignature.charAt(0);
		if(primitives.containsKey(c) && typeSignature.length() == 1) {
			typeSignature = primitives.get(c);
			result.setSignature(result.getArrayPrefix() + typeSignature);
			result.setPrimitive(true);
		} else if(c == Signature.C_EXTENDS) {
			typeSignature = typeSignature.substring(1);
			result.setUpper(true);
		} else if(c == Signature.C_SUPER) {
			typeSignature = typeSignature.substring(1);
			result.setLower(true);
		} else if(c == Signature.C_STAR && typeSignature.length() == 1) {
			result.setUpper(true);
			return result;
		}

		int startToken = typeSignature.indexOf(Signature.C_GENERIC_START);
		if(startToken < 0) {
			String resovedTypeName = EclipseJavaUtil.resolveTypeAsString(contextType, typeSignature);
			if(resovedTypeName == null) return null;
			if(!context.isBinary() || typeSignature.charAt(0) == Signature.C_TYPE_VARIABLE) {
				StringBuffer ns = new StringBuffer();
				ns.append(result.getArrayPrefix());
				if(result.isLower()) ns.append(Signature.C_SUPER);
				if(result.isUpper()) ns.append(Signature.C_EXTENDS);
				ns.append(Signature.C_RESOLVED).append(resovedTypeName).append(Signature.C_SEMICOLON);
				result.setSignature(ns.toString());
			}
			IType type = EclipseJavaUtil.findType(context.getJavaProject(), resovedTypeName);
			if(type != null) {
				result.setType(type);
				cache.put(key, result);
				return result;
			}
			if(context instanceof IMethod) {
				String[] ps = ((IMethod)context).getTypeParameterSignatures();
				for (int i = 0; i < ps.length; i++) {
					ParametedType st = getParametedTypeForParameter(context, ps[i], result);
					if(st != null) {
						if(st.getSignature().indexOf(Signature.C_COLON) >= 0) {
							CommonCorePlugin.getDefault().logWarning("Wrong signature=" + st.getSignature());
						}
						return st;
					}
				}
			}
			String[] ps = contextType.getTypeParameterSignatures();
			for (int i = 0; i < ps.length; i++) {
				ParametedType st = getParametedTypeForParameter(contextType, ps[i], result);
				if(st != null) return st;
			}
		} else {
			int endToken = typeSignature.lastIndexOf(Signature.C_GENERIC_END);
			if(endToken < startToken) return null;
			String typeName = typeSignature.substring(0, startToken) + typeSignature.substring(endToken + 1);
			String resovedTypeName = EclipseJavaUtil.resolveTypeAsString(contextType, typeName);
			if(resovedTypeName == null) return null;
			IType type = EclipseJavaUtil.findType(context.getJavaProject(), resovedTypeName);
			if(type != null) {
				result.setType(type);
				cache.put(key, result);
				StringBuffer newParams = new StringBuffer();
				String[] paramSignatures = null;
				try {
					paramSignatures = Signature.getTypeArguments(typeSignature);
				} catch (IllegalArgumentException e) {
					CommonCorePlugin.getDefault().logError(e);
				}
				if(paramSignatures != null) for (String paramSignature: paramSignatures) {
					ParametedType param = getParametedType(context, basetype, paramSignature);
					if(param == null) {
						param = new ParametedType();
						param.setSignature(paramSignature);
					}
					result.addParameter(param);
					newParams.append(param.getSignature());
				}
				if(!context.isBinary()) {
					StringBuffer ns = new StringBuffer();
					ns.append(result.getArrayPrefix());
					if(result.isLower()) ns.append(Signature.C_SUPER);
					if(result.isUpper()) ns.append(Signature.C_EXTENDS);
					ns.append(Signature.C_RESOLVED)
					  .append(resovedTypeName)
					  .append(Signature.C_GENERIC_START)
					  .append(newParams)
					  .append(Signature.C_GENERIC_END)
					  .append(Signature.C_SEMICOLON);
					result.setSignature(ns.toString());
				}
				return result;
			}
		}
		return null;
	}

	public ParametedType getParametedTypeForParameter(IMember context, String typeParameterSignature, ParametedType result) throws JavaModelException {
		IType contextType = context instanceof IType ? (IType)context : context.getDeclaringType();
		String key = context == null ? typeParameterSignature : contextType.getFullyQualifiedName() + "+" + typeParameterSignature;

		String t = Signature.getTypeVariable(typeParameterSignature);
		String[] bounds = Signature.getTypeParameterBounds(typeParameterSignature);
		
		t = Signature.C_RESOLVED + t + Signature.C_SEMICOLON;
		if(result == null || t.equals(result.getSignature())) {
			if(bounds.length > 0 && bounds[0].length() > 0) {
				ParametedType st = getParametedType(contextType, bounds[0]);
				if(st != null) {
					result = new TypeDeclaration(st, context.getResource(), 0, 0);
					result.setUpper(true);
				}
			} else if(result != null) {
				result.setSignature(t);
			}
			if(result == null) {
				result = new ParametedType();
				result.setFactory(this);
				result.setSignature(t);
			}
			result.setVariable(true);
			cache.put(key, result);
			return result;
		}
		return null;
	}

	public void clean() {
		cache.clear();
	}
}