/*******************************************************************************
 * Copyright (c) 2004, 2010 IBM Corporation 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 Niefer (IBM Corporation) - initial API and implementation
 *     Markus Schorn (Wind River Systems)
 *******************************************************************************/
package org.eclipse.cdt.internal.core.dom.parser.cpp;

import static org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.SemanticUtil.TDEF;
import static org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.SemanticUtil.getNestedType;

import org.eclipse.cdt.core.dom.ILinkage;
import org.eclipse.cdt.core.dom.ast.ASTTypeUtil;
import org.eclipse.cdt.core.dom.ast.DOMException;
import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTParameterDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTStandardFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IASTTypeId;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.IFunctionType;
import org.eclipse.cdt.core.dom.ast.IProblemBinding;
import org.eclipse.cdt.core.dom.ast.IScope;
import org.eclipse.cdt.core.dom.ast.ISemanticProblem;
import org.eclipse.cdt.core.dom.ast.IType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBlockScope;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassScope;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunction;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPParameter;
import org.eclipse.cdt.core.parser.util.ArrayUtil;
import org.eclipse.cdt.internal.core.dom.Linkage;
import org.eclipse.cdt.internal.core.dom.parser.ASTInternal;
import org.eclipse.cdt.internal.core.dom.parser.ASTNode;
import org.eclipse.cdt.internal.core.dom.parser.ASTQueries;
import org.eclipse.cdt.internal.core.dom.parser.ProblemFunctionType;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.SemanticUtil;
import org.eclipse.core.runtime.PlatformObject;

/**
 * Binding for c++ function
 */
public class CPPFunction extends PlatformObject implements ICPPFunction, ICPPInternalFunction {

	protected IASTDeclarator[] declarations;
	protected ICPPASTFunctionDeclarator definition;
	protected ICPPFunctionType type = null;
	
	private static final int FULLY_RESOLVED         = 1;
	private static final int RESOLUTION_IN_PROGRESS = 1 << 1;
	private int bits = 0;
	
	public CPPFunction(IASTDeclarator declarator) {
	    if (declarator != null) {
			IASTNode parent = ASTQueries.findOutermostDeclarator(declarator).getParent();
			if (parent instanceof IASTFunctionDefinition) {
				if (declarator instanceof ICPPASTFunctionDeclarator) {
					definition = (ICPPASTFunctionDeclarator) declarator;
				}
			} else {
				declarations = new IASTDeclarator[] { declarator };
			}
	    
		    IASTName name= getASTName();
		    name.setBinding(this);
	    }
	}
	
	private void resolveAllDeclarations() {
	    if ((bits & (FULLY_RESOLVED | RESOLUTION_IN_PROGRESS)) == 0) {
	        bits |= RESOLUTION_IN_PROGRESS;
		    IASTTranslationUnit tu = null;
	        if (definition != null) {
	            tu = definition.getTranslationUnit();
	        } else if (declarations != null) {
	            tu = declarations[0].getTranslationUnit();
	        } else {
	            //implicit binding
	            IScope scope = getScope();
                IASTNode node = ASTInternal.getPhysicalNodeOfScope(scope);
				if (node != null) {
					tu = node.getTranslationUnit();
				}
	        }
	        if (tu != null) {
	            CPPVisitor.getDeclarations(tu, this);
	        }
	        declarations = (IASTDeclarator[]) ArrayUtil.trim(IASTDeclarator.class, declarations);
	        bits |= FULLY_RESOLVED;
	        bits &= ~RESOLUTION_IN_PROGRESS;
	    }
	}
	
    public IASTDeclarator[] getDeclarations() {
        return declarations;
    }

    public ICPPASTFunctionDeclarator getDefinition() {
        return definition;
    }
    
	public final void addDefinition(IASTNode node) {
		IASTDeclarator dtor = extractRelevantDtor(node);
		if (dtor instanceof ICPPASTFunctionDeclarator) {
			ICPPASTFunctionDeclarator fdtor= (ICPPASTFunctionDeclarator) dtor;
			updateFunctionParameterBindings(fdtor);
			definition = fdtor;
		}
	}
	
	public final void addDeclaration(IASTNode node) {
		IASTDeclarator dtor = extractRelevantDtor(node);
		if (dtor == null) {
			return;
		}
		
		// function could be declared via a typedef
		if (dtor instanceof ICPPASTFunctionDeclarator) {
			updateFunctionParameterBindings((ICPPASTFunctionDeclarator) dtor);
		}

		if (declarations == null || declarations.length == 0) {
			declarations = new IASTDeclarator[] { dtor };
		} else {
			// Keep the lowest offset declaration in [0]
			if (((ASTNode)node).getOffset() < ((ASTNode)declarations[0]).getOffset()) {
				declarations = ArrayUtil.prepend(declarations, dtor);
			} else {
				declarations = ArrayUtil.append(declarations, dtor);
			}
		}
	}

	private IASTDeclarator extractRelevantDtor(IASTNode node) {
		while (node instanceof IASTName)
			node = node.getParent();
		if (!(node instanceof IASTDeclarator))
			return null;
		return ASTQueries.findTypeRelevantDeclarator((IASTDeclarator) node);
	}

	public ICPPParameter[] getParameters() {
	    IASTStandardFunctionDeclarator dtor = getPreferredDtor();
	    if (dtor == null) {
			return CPPBuiltinParameter.createParameterList(getType());
	    }
		IASTParameterDeclaration[] params = dtor.getParameters();
		int size = params.length;
		ICPPParameter[] result = new ICPPParameter[size];
		if (size > 0) {
			for (int i = 0; i < size; i++) {
				IASTParameterDeclaration p = params[i];
				final IASTName name = getParamName(p);
				final IBinding binding= name.resolveBinding();
				if (binding instanceof ICPPParameter) {
					result[i]= (ICPPParameter) binding;
				} else {
					result[i] = new CPPParameter.CPPParameterProblem(p, IProblemBinding.SEMANTIC_INVALID_TYPE,
							name.toCharArray());
				}
			}
		}
		return result;
	}

	public IScope getFunctionScope() {
	    resolveAllDeclarations();
	    if (definition != null) {
			return definition.getFunctionScope();
	    } 
	        
	    for (IASTDeclarator dtor : declarations) {
			if (dtor instanceof ICPPASTFunctionDeclarator) {
				return ((ICPPASTFunctionDeclarator) dtor).getFunctionScope();
			}
		}
	    
	    // function declaration via typedef
	    return null;
	}

	public String getName() {
	    return getASTName().toString();
	}

	public char[] getNameCharArray() {
		return getASTName().getSimpleID();
	}
	
	protected IASTName getASTName() {
		IASTDeclarator dtor = (definition != null) ? definition : declarations[0];
		dtor= ASTQueries.findInnermostDeclarator(dtor);
	    IASTName name= dtor.getName();
	    if (name instanceof ICPPASTQualifiedName) {
	        IASTName[] ns = ((ICPPASTQualifiedName)name).getNames();
	        name = ns[ns.length - 1];
	    }
	    return name;
	}

	public IScope getScope() {
	    IASTName n = getASTName();
	    IScope scope = CPPVisitor.getContainingScope(n);
	    if (scope instanceof ICPPClassScope) {
	    	ICPPASTDeclSpecifier declSpec = null;
		    if (definition != null) {
		    	IASTNode node = ASTQueries.findOutermostDeclarator(definition).getParent();
		        IASTFunctionDefinition def = (IASTFunctionDefinition) node;
			    declSpec = (ICPPASTDeclSpecifier) def.getDeclSpecifier();    
		    } else {
		    	IASTNode node = ASTQueries.findOutermostDeclarator(declarations[0]).getParent();
		        IASTSimpleDeclaration decl = (IASTSimpleDeclaration)node; 
		        declSpec = (ICPPASTDeclSpecifier) decl.getDeclSpecifier();
		    }
		    if (declSpec.isFriend()) {
		        try {
	                while (scope instanceof ICPPClassScope) {
		                scope = scope.getParent();
	                }
		        } catch (DOMException e) {
	            }
		    }
	    }
		return scope;
	}

    public ICPPFunctionType getType() {
        if (type == null) {
			final IType t = getNestedType(CPPVisitor.createType((definition != null) ? definition : declarations[0]), TDEF);
			if (t instanceof ICPPFunctionType) {
				type = (ICPPFunctionType) t;
			} else if (t instanceof ISemanticProblem){
				type= new ProblemFunctionType(((ISemanticProblem) t).getID());
			} else {
				// This case is unexpected
				type = new ProblemFunctionType(ISemanticProblem.TYPE_UNRESOLVED_NAME);
			}
		}
        return type;
    }

    public IBinding resolveParameter(CPPParameter param) {
		int pos= param.getParameterPosition();
		
    	int tdeclLen= declarations == null ? 0 : declarations.length;
    	for (int i= -1; i < tdeclLen; i++) {
    		ICPPASTFunctionDeclarator tdecl;
    		if (i == -1) {
    			tdecl= definition;
    			if (tdecl == null)
    				continue;
    		} else {
    			final IASTDeclarator dtor= declarations[i];
    			if (!(dtor instanceof ICPPASTFunctionDeclarator)) {
        			if (dtor == null) {
        				break;
        			}
    				continue;
    			}
    			tdecl= (ICPPASTFunctionDeclarator) dtor;
    		}
    		
    		IASTParameterDeclaration[] params = tdecl.getParameters();
    		if (pos < params.length) {
				final IASTName oName = getParamName(params[pos]);
    			return oName.resolvePreBinding();
    		}
    	}
    	return param;
    }

	private IASTName getParamName(final IASTParameterDeclaration paramDecl) {
		return ASTQueries.findInnermostDeclarator(paramDecl.getDeclarator()).getName();
	}
    
    protected final void updateFunctionParameterBindings(ICPPASTFunctionDeclarator fdtor) {
		IASTParameterDeclaration[] updateParams = fdtor.getParameters();

    	int k= 0;
    	int tdeclLen= declarations == null ? 0 : declarations.length;
    	for (int i= -1; i < tdeclLen && k < updateParams.length; i++) {
    		ICPPASTFunctionDeclarator tdecl;
    		if (i == -1) {
    			tdecl= definition;
    			if (tdecl == null)
    				continue;
    		} else {
    			final IASTDeclarator dtor= declarations[i];
    			if (!(dtor instanceof ICPPASTFunctionDeclarator)) {
        			if (dtor == null) {
        				break;
        			}
    				continue;
    			}
    			tdecl= (ICPPASTFunctionDeclarator) dtor;
    		}
    		
    		IASTParameterDeclaration[] params = tdecl.getParameters();
    		int end= Math.min(params.length, updateParams.length);
    		for (; k < end; k++) {
    			final IASTName oName = getParamName(params[k]);
    			IBinding b= oName.resolvePreBinding();
    			IASTName n = getParamName(updateParams[k]);
    			n.setBinding(b);
    			ASTInternal.addDeclaration(b, n);
    		}
    	}
    }

    public final boolean isStatic() {
        return isStatic(true);
    }

    public boolean isStatic(boolean resolveAll) {
        if (resolveAll && (bits & FULLY_RESOLVED) == 0) {
            resolveAllDeclarations();
        }
		return hasStorageClass(this, IASTDeclSpecifier.sc_static);
    }

//	static public boolean isStatic
//        //2 state bits, most significant = whether or not we've figure this out yet
//        //least significant = whether or not we are static
//        int state = (bits & IS_STATIC) >> 2;
//        if (state > 1) return (state % 2 != 0);
//        
//        IASTDeclSpecifier declSpec = null;
//        IASTFunctionDeclarator dtor = (IASTFunctionDeclarator) getDefinition();
//        if (dtor != null) {
//	        declSpec = ((IASTFunctionDefinition)dtor.getParent()).getDeclSpecifier();
//	        if (declSpec.getStorageClass() == IASTDeclSpecifier.sc_static) {
//	            bits |= 3 << 2;
//	            return true;
//	        }
//        }
//        
//        IASTFunctionDeclarator[] dtors = (IASTFunctionDeclarator[]) getDeclarations();
//        if (dtors != null) {
//	        for (int i = 0; i < dtors.length; i++) {
//	            IASTNode parent = dtors[i].getParent();
//	            declSpec = ((IASTSimpleDeclaration)parent).getDeclSpecifier();
//	            if (declSpec.getStorageClass() == IASTDeclSpecifier.sc_static) {
//	                bits |= 3 << 2;
//	                return true;
//	            }
//	        }
//        }
//        bits |= 2 << 2;
//        return false;
//    }
    
    public String[] getQualifiedName() {
        return CPPVisitor.getQualifiedName(this);
    }

    public char[][] getQualifiedNameCharArray() {
        return CPPVisitor.getQualifiedNameCharArray(this);
    }

    public boolean isGloballyQualified() throws DOMException {
        IScope scope = getScope();
        while (scope != null) {
            if (scope instanceof ICPPBlockScope)
                return false;
            scope = scope.getParent();
        }
        return true;
    }

	static public boolean hasStorageClass(ICPPInternalFunction function, int storage) {
	    IASTDeclarator dtor = (IASTDeclarator) function.getDefinition();
	    IASTNode[] ds = function.getDeclarations();

        int i = -1;
        do {
            if (dtor != null) {
                IASTNode parent = dtor.getParent();
	            while (!(parent instanceof IASTDeclaration))
	                parent = parent.getParent();
	            
	            IASTDeclSpecifier declSpec = null;
	            if (parent instanceof IASTSimpleDeclaration) {
	                declSpec = ((IASTSimpleDeclaration)parent).getDeclSpecifier();
	            } else if (parent instanceof IASTFunctionDefinition) {
	                declSpec = ((IASTFunctionDefinition)parent).getDeclSpecifier();
	            }
	            if (declSpec != null && declSpec.getStorageClass() == storage) {
	            	return true;
	            }
            }
            if (ds != null && ++i < ds.length) {
            	dtor = (IASTDeclarator) ds[i];
            } else {
                break;
            }
        } while (dtor != null);
        return false;
	}
	
	public boolean isDeleted() {
		return isDeletedDefinition(getDefinition());
	}

	public static boolean isDeletedDefinition(IASTNode def) {
		while (def != null && !(def instanceof IASTDeclaration)) {
			def= def.getParent();
		}
		if (def instanceof ICPPASTFunctionDefinition) {
			return ((ICPPASTFunctionDefinition) def).isDeleted();
		}
		return false;
	}

	public boolean isMutable() {
        return false;
    }

    public boolean isInline() {
    	IASTDeclarator dtor = getDefinition();
        IASTDeclarator[] ds = getDeclarations();
        int i = -1;
        do {
            if (dtor != null) {
                IASTNode parent = dtor.getParent();
	            while (!(parent instanceof IASTDeclaration))
	                parent = parent.getParent();
	            
	            IASTDeclSpecifier declSpec = null;
	            if (parent instanceof IASTSimpleDeclaration)
	                declSpec = ((IASTSimpleDeclaration)parent).getDeclSpecifier();
	            else if (parent instanceof IASTFunctionDefinition)
	                declSpec = ((IASTFunctionDefinition)parent).getDeclSpecifier();
	            
	            if (declSpec != null && declSpec.isInline())
                    return true;
            }
            if (ds != null && ++i < ds.length)
                dtor = ds[i];
            else
                break;
        } while (dtor != null);
        return false;
    }

    public boolean isExternC() {
	    if (CPPVisitor.isExternC(getDefinition())) {
	    	return true;
	    }
        IASTNode[] ds= getDeclarations();
        if (ds != null) {
        	for (IASTNode element : ds) {
        		if (CPPVisitor.isExternC(element)) {
        			return true;
        		}
			}
        }
        return false;
    }

    public boolean isExtern() {
        return hasStorageClass(this, IASTDeclSpecifier.sc_extern);
    }

    public boolean isAuto() {
        return hasStorageClass(this, IASTDeclSpecifier.sc_auto);
    }

    public boolean isRegister() {
        return hasStorageClass(this, IASTDeclSpecifier.sc_register);
    }

    public boolean takesVarArgs() {
        ICPPASTFunctionDeclarator dtor= getPreferredDtor();
        return dtor != null ? dtor.takesVarArgs() : false;
    }
    
	public ILinkage getLinkage() {
		return Linkage.CPP_LINKAGE;
	}
	
	@Override
	public String toString() {
		StringBuilder result = new StringBuilder();
		result.append(getName());
		IFunctionType t = getType();
		result.append(t != null ? ASTTypeUtil.getParameterTypeString(t) : "()"); //$NON-NLS-1$
		return result.toString();
	}
	
	public IBinding getOwner() {
		return CPPVisitor.findNameOwner(getASTName(), false);
	}

	public IType[] getExceptionSpecification() {
		ICPPASTFunctionDeclarator declarator = getPreferredDtor();
		if (declarator != null) {
			IASTTypeId[] astTypeIds= declarator.getExceptionSpecification();
			if (astTypeIds.equals(ICPPASTFunctionDeclarator.NO_EXCEPTION_SPECIFICATION)) 
				return null;
			
			if (astTypeIds.equals(IASTTypeId.EMPTY_TYPEID_ARRAY)) 
				return IType.EMPTY_TYPE_ARRAY;
			
			IType[] typeIds = new IType[astTypeIds.length];
			for (int i= 0; i < astTypeIds.length; ++i) {
				typeIds[i] = CPPVisitor.createType(astTypeIds[i]);
			}
			return typeIds;
		}
		return null;
	}
	
	protected ICPPASTFunctionDeclarator getPreferredDtor() {
        ICPPASTFunctionDeclarator dtor = getDefinition();
        if (dtor != null) 
        	return dtor;

        IASTDeclarator[] dtors = getDeclarations();
        if (dtors != null) {
        	for (IASTDeclarator declarator : dtors) {
        		if (declarator instanceof ICPPASTFunctionDeclarator) 
        			return (ICPPASTFunctionDeclarator) declarator;
        	}
        }
        return null;
	}

	public int getRequiredArgumentCount() {
		return getRequiredArgumentCount(getParameters());
	}

	public static int getRequiredArgumentCount(ICPPParameter[] pars) {
		int result= pars.length;
		while(result > 0) {
			final ICPPParameter p = pars[result-1];
			if (p.hasDefaultValue() || p.isParameterPack()) {
				result--;
			} else {
				if (pars.length == 1 && SemanticUtil.isVoidType(p.getType())) {
					return 0;
				}
				return result;
			}
		}
		return 0;
	}

	public boolean hasParameterPack() {
		ICPPParameter[] pars= getParameters();
		return pars.length > 0 && pars[pars.length-1].isParameterPack();
	}
}
