/*******************************************************************************
 * Copyright (c) 2007, 2010 Symbian Software Systems 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:
 *     Andrew Ferguson (Symbian) - Initial implementation
 *     Markus Schorn (Wind River Systems)
 *******************************************************************************/
package org.eclipse.cdt.internal.core.index.composite.cpp;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IArrayType;
import org.eclipse.cdt.core.dom.ast.IBasicType;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.IEnumerator;
import org.eclipse.cdt.core.dom.ast.IPointerType;
import org.eclipse.cdt.core.dom.ast.IQualifierType;
import org.eclipse.cdt.core.dom.ast.ISemanticProblem;
import org.eclipse.cdt.core.dom.ast.IType;
import org.eclipse.cdt.core.dom.ast.ITypedef;
import org.eclipse.cdt.core.dom.ast.IValue;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBinding;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassScope;
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.ICPPClassTemplatePartialSpecializationSpecialization;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPConstructor;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPEnumeration;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPField;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunction;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionTemplate;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionType;
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.ICPPNamespaceAlias;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPNamespaceScope;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPParameter;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPParameterPackType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPPointerToMemberType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPReferenceType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPSpecialization;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateDefinition;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateInstance;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateNonTypeParameter;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateParameter;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateTemplateParameter;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateTypeParameter;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPUsingDeclaration;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPVariable;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.index.IIndexBinding;
import org.eclipse.cdt.core.index.IIndexMacroContainer;
import org.eclipse.cdt.internal.core.dom.parser.Value;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPArrayType;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPFunctionType;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPParameterPackType;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPPointerToMemberType;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPPointerType;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPQualifierType;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPReferenceType;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPClassSpecializationScope;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPDeferredClassInstance;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPInternalUnknownScope;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPUnknownBinding;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPUnknownClassInstance;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPUnknownClassType;
import org.eclipse.cdt.internal.core.index.CIndex;
import org.eclipse.cdt.internal.core.index.IIndexFragmentBinding;
import org.eclipse.cdt.internal.core.index.IIndexScope;
import org.eclipse.cdt.internal.core.index.composite.AbstractCompositeFactory;
import org.eclipse.cdt.internal.core.index.composite.CompositeMacroContainer;
import org.eclipse.cdt.internal.core.index.composite.CompositingNotImplementedError;
import org.eclipse.cdt.internal.core.index.composite.ICompositesFactory;
import org.eclipse.core.runtime.CoreException;

public class CPPCompositesFactory extends AbstractCompositeFactory {

	public CPPCompositesFactory(IIndex index) {
		super(index);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.internal.core.index.composite.cpp.ICompositesFactory#getCompositeScope(org.eclipse.cdt.core.index.IIndex, org.eclipse.cdt.core.dom.ast.IScope)
	 */
	@Override
	public IIndexScope getCompositeScope(IIndexScope rscope) {
		try {
			if (rscope == null) {
				return null;
			} 
			if (rscope instanceof ICPPClassScope) {
				if (rscope instanceof ICPPClassSpecializationScope) {
					return new CompositeCPPClassSpecializationScope(this, (IIndexFragmentBinding) rscope.getScopeBinding());
				}
				ICPPClassScope classScope = (ICPPClassScope) rscope;
				return new CompositeCPPClassScope(this,	findOneBinding(classScope.getClassType()));
			} 
			if (rscope instanceof ICPPNamespaceScope) {
				ICPPNamespace[] namespaces;
				if (rscope instanceof CompositeCPPNamespace) {
					// avoid duplicating the search
					namespaces = ((CompositeCPPNamespace) rscope).namespaces;
				} else {
					namespaces = getNamespaces(rscope.getScopeBinding());
				}
				return new CompositeCPPNamespaceScope(this, namespaces);
			} 
			if (rscope instanceof ICPPInternalUnknownScope) {
				ICPPInternalUnknownScope uscope= (ICPPInternalUnknownScope) rscope;
				final ICPPBinding binding = uscope.getScopeBinding();
				return new CompositeCPPUnknownScope((CompositeCPPBinding) getCompositeBinding((IIndexFragmentBinding) binding), (IASTName) uscope.getPhysicalNode());
			}
			throw new CompositingNotImplementedError(rscope.getClass().getName());
		} catch(CoreException ce) {
			CCorePlugin.log(ce);
			throw new CompositingNotImplementedError(ce.getMessage());		
		} 
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.internal.core.index.composite.cpp.ICompositesFactory#getCompositeType(org.eclipse.cdt.core.index.IIndex, org.eclipse.cdt.core.dom.ast.IType)
	 */
	@Override
	public IType getCompositeType(IType rtype) {
		if (rtype instanceof IIndexFragmentBinding) {
			return (IType) getCompositeBinding((IIndexFragmentBinding) rtype);
		} 
		if (rtype instanceof ICPPFunctionType) {
			ICPPFunctionType ft= (ICPPFunctionType) rtype;
			IType r= ft.getReturnType();
			IType r2= getCompositeType(r);
			IType[] p= ft.getParameterTypes();
			IType[] p2= getCompositeTypes(p);
			if (r != r2 || p != p2) {
				return new CPPFunctionType(r2, p2, ft.isConst(), ft.isVolatile(), ft.takesVarArgs());
			}
			return ft;
		} 
		if (rtype instanceof ICPPPointerToMemberType) {
			ICPPPointerToMemberType pmt= (ICPPPointerToMemberType) rtype;
			IType ct= pmt.getMemberOfClass();
			IType ct2= getCompositeType(ct);
			IType t= pmt.getType();
			IType t2= getCompositeType(t);
			if (ct != ct2 || t != t2) {
				return new CPPPointerToMemberType(t2, ct2, pmt.isConst(), pmt.isVolatile(), pmt.isRestrict());
			}
			return pmt;
		} 
		if (rtype instanceof IPointerType) {
			IPointerType pt= (IPointerType) rtype;
			IType r= pt.getType();
			IType r2= getCompositeType(r);
			if (r != r2) {
				return new CPPPointerType(r2, pt.isConst(), pt.isVolatile(), pt.isRestrict());
			}
			return pt;
		}
		if (rtype instanceof ICPPReferenceType) {
			ICPPReferenceType rt= (ICPPReferenceType) rtype;
			IType r= rt.getType();
			IType r2= getCompositeType(r);
			if (r != r2) {
				return new CPPReferenceType(r2, rt.isRValueReference());
			}
			return rt;
		}
		if (rtype instanceof ICPPParameterPackType) {
			ICPPParameterPackType rt= (ICPPParameterPackType) rtype;
			IType r= rt.getType();
			IType r2= getCompositeType(r);
			if (r != r2 && r2 != null) {
				return new CPPParameterPackType(r2);
			}
			return rt;
		}
		if (rtype instanceof IQualifierType) {
			IQualifierType qt= (IQualifierType) rtype;
			IType r= qt.getType();
			IType r2= getCompositeType(r);
			if (r != r2) {
				return new CPPQualifierType(r2, qt.isConst(), qt.isVolatile());
			}
			return qt;
		} 
		if (rtype instanceof IArrayType) {
			IArrayType at= (IArrayType) rtype;
			IType r= at.getType();
			IType r2= getCompositeType(r);
			IValue v= at.getSize();
			IValue v2= getCompositeValue(v);
			if (r != r2 || v != v2) {
				return new CPPArrayType(r2, v2);
			}
			return at;
		} 
		if (rtype instanceof IBasicType || rtype == null || rtype instanceof ISemanticProblem) {
			return rtype;
		} 
		
		throw new CompositingNotImplementedError();
	}
	
	@Override
	public IValue getCompositeValue(IValue v) {
		if (v == null)
			return null;
		
		IBinding[] b= v.getUnknownBindings();
		if (b.length == 0)
			return v;
		
		ICPPUnknownBinding[] b2= new ICPPUnknownBinding[b.length];
		for (int i = 0; i < b2.length; i++) {
			b2[i]= (ICPPUnknownBinding) getCompositeBinding((IIndexFragmentBinding) b[i]);
		}
		return Value.fromInternalRepresentation(v.getInternalExpression(), b2);
	}

	private ICPPNamespace[] getNamespaces(IBinding rbinding) throws CoreException {
		CIndex cindex = (CIndex) index;
		IIndexBinding[] ibs = cindex.findEquivalentBindings(rbinding);
		ICPPNamespace[] namespaces = new ICPPNamespace[ibs.length];
		for (int i = 0; i < namespaces.length; i++)
			namespaces[i] = (ICPPNamespace) ibs[i];
		return namespaces;
	}
	
	IIndex getContext() {
		return index;
	}
	
	protected IIndexFragmentBinding findOneBinding(IBinding binding) {
		return findOneBinding(binding, false);
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.cdt.internal.core.index.composite.cpp.ICompositesFactory#getCompositeBinding(org.eclipse.cdt.core.index.IIndex, org.eclipse.cdt.core.dom.ast.IBinding)
	 */
	@Override
	public IIndexBinding getCompositeBinding(IIndexFragmentBinding binding) {
		IIndexBinding result;

		try {
			if (binding == null) {
				result = null;
			} else if (binding instanceof ICPPSpecialization) {
				if (binding instanceof ICPPTemplateInstance) {
					if (binding instanceof ICPPDeferredClassInstance) {
						return new CompositeCPPDeferredClassInstance(this, (ICPPDeferredClassInstance) findOneBinding(binding));
					} else {
						if (binding instanceof ICPPClassType) {
							return new CompositeCPPClassInstance(this, (ICPPClassType) findOneBinding(binding));
						} else if (binding instanceof ICPPConstructor) {
							return new CompositeCPPConstructorInstance(this, (ICPPConstructor) binding);
						} else if (binding instanceof ICPPMethod) {
							return new CompositeCPPMethodInstance(this, (ICPPMethod) binding);
						} else if (binding instanceof ICPPFunction) {
							return new CompositeCPPFunctionInstance(this, (ICPPFunction) binding);
						} else {
							throw new CompositingNotImplementedError("composite binding unavailable for " + binding + " " + binding.getClass()); //$NON-NLS-1$ //$NON-NLS-2$
						}
					}
				} else if (binding instanceof ICPPTemplateDefinition) {
					if (binding instanceof ICPPClassTemplatePartialSpecialization) {
						if (binding instanceof ICPPClassTemplatePartialSpecializationSpecialization)
							return new CompositeCPPClassTemplatePartialSpecializationSpecialization(this, (ICPPClassTemplatePartialSpecializationSpecialization) binding);
						return new CompositeCPPClassTemplatePartialSpecialization(this, (ICPPClassTemplatePartialSpecialization) findOneBinding(binding));
					} else if (binding instanceof ICPPClassType) {
						return new CompositeCPPClassTemplateSpecialization(this, (ICPPClassType) binding);
					} else if (binding instanceof ICPPConstructor) {
						return new CompositeCPPConstructorTemplateSpecialization(this, (ICPPConstructor) binding);
					} else if (binding instanceof ICPPMethod) {
						return new CompositeCPPMethodTemplateSpecialization(this, (ICPPMethod) binding);
					} else if (binding instanceof ICPPFunctionType) {
						return new CompositeCPPFunctionTemplateSpecialization(this, (ICPPFunction) binding);
					} else {
						throw new CompositingNotImplementedError("composite binding unavailable for " + binding + " " + binding.getClass()); //$NON-NLS-1$ //$NON-NLS-2$
					}
				} else {
					if (binding instanceof ICPPClassType) {
						return new CompositeCPPClassSpecialization(this, (ICPPClassType) findOneBinding(binding));
					} else if (binding instanceof ICPPConstructor) {
						return new CompositeCPPConstructorSpecialization(this, (ICPPConstructor) findOneBinding(binding, true));						
					} else if (binding instanceof ICPPMethod) {
						return new CompositeCPPMethodSpecialization(this, (ICPPMethod) findOneBinding(binding, true));
					} else if (binding instanceof ICPPFunction) {
						return new CompositeCPPFunctionSpecialization(this, (ICPPFunction) findOneBinding(binding, true));
					} else if (binding instanceof ICPPField) {
						return new CompositeCPPField(this, (ICPPField) binding);
					} else if (binding instanceof ICPPParameter) {
						return new CompositeCPPParameterSpecialization(this, (ICPPParameter) binding);
					} else if (binding instanceof ITypedef) {
						return new CompositeCPPTypedefSpecialization(this, (ICPPBinding) binding);
					} else if (binding instanceof ICPPUsingDeclaration) {
						return new CompositeCPPUsingDeclarationSpecialization(this, (ICPPUsingDeclaration) binding);
					} else {
						throw new CompositingNotImplementedError("composite binding unavailable for " + binding + " " + binding.getClass()); //$NON-NLS-1$ //$NON-NLS-2$
					}
				}
			} else if(binding instanceof ICPPTemplateParameter) {
				if (binding instanceof ICPPTemplateTypeParameter) {
					result = new CompositeCPPTemplateTypeParameter(this, (ICPPTemplateTypeParameter) binding);
				} else if (binding instanceof ICPPTemplateNonTypeParameter) {
					result = new CompositeCPPTemplateNonTypeParameter(this, (ICPPTemplateNonTypeParameter) binding);
				} else if (binding instanceof ICPPTemplateTemplateParameter) {
					result = new CompositeCPPTemplateTemplateParameter(this, (ICPPTemplateTemplateParameter) binding);
				} else {
					throw new CompositingNotImplementedError("composite binding unavailable for " + binding + " " + binding.getClass()); //$NON-NLS-1$ //$NON-NLS-2$
				}
			} else if (binding instanceof ICPPTemplateDefinition) {
				if (binding instanceof ICPPClassTemplate) {
					ICPPClassType def= (ICPPClassType) findOneBinding(binding);
					return new CompositeCPPClassTemplate(this, def);
				} else if (binding instanceof ICPPConstructor) {
					return new CompositeCPPConstructorTemplate(this, (ICPPConstructor) binding);
				} else if (binding instanceof ICPPMethod) {
					return new CompositeCPPMethodTemplate(this, (ICPPMethod) binding);
				} else if (binding instanceof ICPPFunctionTemplate) {
					return new CompositeCPPFunctionTemplate(this, (ICPPFunction) binding);
				} else {
					throw new CompositingNotImplementedError("composite binding unavailable for " + binding + " " + binding.getClass()); //$NON-NLS-1$ //$NON-NLS-2$
				}
			} else if (binding instanceof ICPPParameter) {
				result = new CompositeCPPParameter(this, (ICPPParameter) binding);
			} else if (binding instanceof ICPPField) {
				result = new CompositeCPPField(this, (ICPPField) binding);
			} else if (binding instanceof ICPPVariable) {
				result = new CompositeCPPVariable(this, (ICPPVariable) binding);
			} else if (binding instanceof ICPPUnknownBinding) {
				if (binding instanceof ICPPUnknownClassInstance) {
					result = new CompositeCPPUnknownClassInstance(this, (ICPPUnknownClassInstance) binding);
				} else if (binding instanceof ICPPUnknownClassType) {
					result = new CompositeCPPUnknownClassType(this, (ICPPUnknownClassType) binding);
				} else {
					result= new CompositeCPPUnknownBinding(this, (ICPPUnknownBinding) binding);
				}
			} else if (binding instanceof ICPPClassType) {
				ICPPClassType def = (ICPPClassType) findOneBinding(binding);
				result = def == null ? null : new CompositeCPPClassType(this, def);
			} else if (binding instanceof ICPPConstructor) {
				result = new CompositeCPPConstructor(this, (ICPPConstructor) binding);
			} else if (binding instanceof ICPPMethod) {
				result = new CompositeCPPMethod(this, (ICPPMethod) binding);
			} else if (binding instanceof ICPPNamespaceAlias) {
				result = new CompositeCPPNamespaceAlias(this, (ICPPNamespaceAlias) binding);
			} else if (binding instanceof ICPPNamespace) {
				ICPPNamespace[] ns = getNamespaces(binding);
				result = ns.length == 0 ? null : new CompositeCPPNamespace(this, ns);
			} else if (binding instanceof ICPPUsingDeclaration) {
				result = new CompositeCPPUsingDeclaration(this, (ICPPUsingDeclaration) binding);
			} else if (binding instanceof ICPPEnumeration) {
				ICPPEnumeration def = (ICPPEnumeration) findOneBinding(binding);
				result = def == null ? null : new CompositeCPPEnumeration(this, def);
			} else if (binding instanceof ICPPFunction) {
				result = new CompositeCPPFunction(this, (ICPPFunction) binding);				
			} else if (binding instanceof IEnumerator) {
				result = new CompositeCPPEnumerator(this, (IEnumerator) binding);
			} else if (binding instanceof ITypedef) {
				result = new CompositeCPPTypedef(this, (ICPPBinding) binding);
			} else if (binding instanceof IIndexMacroContainer) {
				result= new CompositeMacroContainer(this, binding);
			} else {
				throw new CompositingNotImplementedError("composite binding unavailable for " + binding + " " + binding.getClass()); //$NON-NLS-1$ //$NON-NLS-2$
			}
		} catch(CoreException ce) {
			CCorePlugin.log(ce);
			throw new CompositingNotImplementedError(ce.getMessage());			
		}

		return result;
	}

	private static class Key {
		final long i;
		final int j;
		final long k;
		public Key(long id1, int id2, long id3) {
			i= id1;
			j= id2;
			k= id3;
		}
		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + (int) (i ^ (i >>> 32));
			result = prime * result + j;
			result = prime * result + (int) k;
			return result;
		}
		@Override
		public boolean equals(Object obj) {
			if (obj instanceof Key) {
				Key other = (Key) obj;
				return i == other.i && j == other.j && k == other.k;
			}
			return false;
		}
	}
	
	public static Object createInstanceCacheKey(ICompositesFactory cf, IIndexFragmentBinding rbinding) {
		return new Key(Thread.currentThread().getId(), cf.hashCode(), rbinding.getBindingID());
	}
	public static Object createSpecializationKey(ICompositesFactory cf,IIndexFragmentBinding rbinding) {
		return new Key(Thread.currentThread().getId(), cf.hashCode(), rbinding.getBindingID()+1);
	}
}
