/*******************************************************************************
 * Copyright (c) 2006, 2010 Wind River Systems, Inc. and others.
 * All rights reserved. This program and the accompanying materials
 * are 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:
 *     Markus Schorn - initial API and implementation
 *******************************************************************************/ 
package org.eclipse.cdt.internal.core.model.ext;

import org.eclipse.cdt.core.dom.ast.DOMException;
import org.eclipse.cdt.core.dom.ast.EScopeKind;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.ICompositeType;
import org.eclipse.cdt.core.dom.ast.IEnumeration;
import org.eclipse.cdt.core.dom.ast.IEnumerator;
import org.eclipse.cdt.core.dom.ast.IField;
import org.eclipse.cdt.core.dom.ast.IFunction;
import org.eclipse.cdt.core.dom.ast.IParameter;
import org.eclipse.cdt.core.dom.ast.IScope;
import org.eclipse.cdt.core.dom.ast.ITypedef;
import org.eclipse.cdt.core.dom.ast.IVariable;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassSpecialization;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassTemplate;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassTemplatePartialSpecialization;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPEnumeration;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionTemplate;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPNamespace;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateInstance;
import org.eclipse.cdt.core.index.IIndexMacro;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.text.IRegion;

/**
 * Factory for creating CElement handles. These are a minimal implementation of
 * the ICElement interface and can be used for displaying information about the index.
 * @since 4.0
 */
public class CElementHandleFactory {
	private CElementHandleFactory() {}

	public static ICElementHandle create(ITranslationUnit tu, IBinding binding, boolean isDefinition,
			IRegion region, long timestamp) throws CoreException {
		try {
			return internalCreate(tu, binding, isDefinition, region, timestamp);
		} catch (DOMException e) {
			return null;
		}
	}

	public static ICElementHandle create(ITranslationUnit tu, IIndexMacro macro, 
			IRegion region, long timestamp) throws CoreException {
		CElementHandle element= new MacroHandle(tu, macro);
		if (region != null) {
			element.setRangeOfID(region, timestamp);
		}
		return element;
	}

	public static ICElementHandle internalCreate(ITranslationUnit tu, IBinding binding, boolean definition,
			IRegion region, long timestamp) throws CoreException, DOMException {	
		ICElement parentElement= createParent(tu, binding);
		if (parentElement == null) {
			return null;
		}
		
		CElementHandle element= null;
		if (binding instanceof ICPPMethod) {
			element= definition 
					? new MethodHandle(parentElement, (ICPPMethod) binding)
					: new MethodDeclarationHandle(parentElement, (ICPPMethod) binding);
		} else if (binding instanceof IFunction) {
			if (binding instanceof ICPPTemplateInstance) {
				element= definition 
				? new FunctionTemplateHandle(parentElement, (ICPPTemplateInstance) binding)
				: new FunctionTemplateDeclarationHandle(parentElement, (ICPPTemplateInstance) binding);
			} else if (binding instanceof ICPPFunctionTemplate) {
				element= definition 
				? new FunctionTemplateHandle(parentElement, (ICPPFunctionTemplate) binding)
				: new FunctionTemplateDeclarationHandle(parentElement, (ICPPFunctionTemplate) binding);
			} else {
				element= definition 
				? new FunctionHandle(parentElement, (IFunction) binding)
				: new FunctionDeclarationHandle(parentElement, (IFunction) binding);
			}
		} else if (binding instanceof IField) {
			element= new FieldHandle(parentElement, (IField) binding);
		} else if (binding instanceof IVariable) {
			if (binding instanceof IParameter) {
				return null;
			}
			element= new VariableHandle(parentElement, (IVariable) binding);
		} else if (binding instanceof IEnumeration) {
			element= new EnumerationHandle(parentElement, (IEnumeration) binding);
		} else if (binding instanceof IEnumerator) {
			element= new EnumeratorHandle(parentElement, (IEnumerator) binding);
		} else if (binding instanceof ICompositeType) {
			element= createHandleForComposite(parentElement, (ICompositeType) binding);
		} else if (binding instanceof ICPPNamespace) {
			element= new NamespaceHandle(parentElement, (ICPPNamespace) binding);
		} else if (binding instanceof ITypedef) {
			element= new TypedefHandle(parentElement, (ITypedef) binding);
		}
		if (element != null && region != null) {
			element.setRangeOfID(region, timestamp);
		}
		return element;
	}

	private static ICElement createParent(ITranslationUnit tu, IBinding binding) throws DOMException {
		IBinding parentBinding= binding.getOwner();
		if (parentBinding == null) {
			IScope scope= binding.getScope();
			if (scope != null && scope.getKind() == EScopeKind.eLocal) {
				return null;
			}
			return tu;
		}
		
		if (parentBinding instanceof IEnumeration) {
			ICElement grandParent= createParent(tu, parentBinding);
			if (parentBinding instanceof ICPPEnumeration && parentBinding.getNameCharArray().length > 0) {
				if (grandParent != null) {
					return new EnumerationHandle(grandParent, (ICPPEnumeration) parentBinding);
				}
			} else {
				return grandParent;
			}
		}

		if (parentBinding instanceof ICPPNamespace) {
			char[] scopeName= parentBinding.getNameCharArray();
			// Skip unnamed namespace.
			if (scopeName.length == 0) {
				return createParent(tu, parentBinding);
			} 
			ICElement grandParent= createParent(tu, parentBinding);
			if (grandParent == null) 
				return null;
			return new NamespaceHandle(grandParent, (ICPPNamespace) parentBinding);
		} 
		
		if (parentBinding instanceof ICompositeType) {
			ICElement grandParent= createParent(tu, parentBinding);
			if (grandParent != null) {
				return createHandleForComposite(grandParent, (ICompositeType) parentBinding);
			}
		}
		return null;
	}

	private static CElementHandle createHandleForComposite(ICElement parent, ICompositeType classBinding)
			throws DOMException {
		if (classBinding instanceof ICPPClassTemplatePartialSpecialization) {
			return new StructureTemplateHandle(parent, (ICPPClassTemplatePartialSpecialization) classBinding);
		}
		if (classBinding instanceof ICPPClassTemplate) {
			return new StructureTemplateHandle(parent, (ICPPClassTemplate) classBinding);
		}
		if (classBinding instanceof ICPPClassSpecialization) {
			ICPPClassSpecialization spec= (ICPPClassSpecialization) classBinding;
			ICPPClassType orig= spec.getSpecializedBinding();
			if (orig instanceof ICPPClassTemplate) {
				return new StructureTemplateHandle(parent, (ICPPClassSpecialization) classBinding, (ICPPClassTemplate) orig);
			}
		}
		return new StructureHandle(parent, classBinding);
	}
}
