/*******************************************************************************
 * Copyright (c) 2008, 2013 Institute for Software, HSR Hochschule fuer Technik
 * Rapperswil, University of applied sciences 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:
 *     Institute for Software - initial API and implementation
 *     Sergey Prigogin (Google)
 *******************************************************************************/
package org.eclipse.cdt.internal.ui.refactoring;

import org.eclipse.cdt.core.dom.ast.DOMException;
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.IScope;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBase;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;

import org.eclipse.cdt.internal.core.dom.parser.ASTInternal;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPInternalBinding;

/**
 * Represents a function or method and adds some useful helper methods to determine
 * if two methods are in the same class.
 */
public class MethodContext {
	public enum ContextType { NONE, FUNCTION, METHOD }

	private ContextType type;
	private IASTName declarationName;
	private ICPPASTQualifiedName qname;

	public MethodContext() {
		type = ContextType.NONE;
	}

	public ContextType getType() {
		return type;
	}

	public void setType(ContextType type) {
		this.type = type;
	}

	public void setMethodDeclarationName(IASTName name) {
		this.declarationName = name;
	}

	public IASTName getMethodDeclarationName() {
		return declarationName;
	}

	public IASTDeclaration getMethodDeclaration() {
		if (declarationName != null) {
			IASTNode parent = declarationName.getParent().getParent();
			if (parent instanceof IASTDeclaration) {
				return (IASTDeclaration) parent;
			}
		}
		return null;
	}

	public void setMethodQName(ICPPASTQualifiedName qname) {
		this.qname = qname;
	}

	public ICPPASTQualifiedName getMethodQName() {
		return qname;
	}

	public static boolean isSameClass(ICPPASTQualifiedName qname1, ICPPASTQualifiedName qname2) {
		ICPPClassType bind1 = getClassBinding(qname1);
		ICPPClassType bind2 = getClassBinding(qname2);
		return bind1.equals(bind2);
	}

	public static boolean isSameOrSubClass(MethodContext context1, MethodContext contextOfSameOrSubclass) {
		ICPPInternalBinding bind1 = getICPPInternalBinding(context1);
		ICPPInternalBinding subclassBind = getICPPInternalBinding(contextOfSameOrSubclass);
		if (isSameClass(bind1,subclassBind)) {
			return true;
		}
		return isSubclass(bind1,subclassBind);
	}

	private static boolean isSubclass(ICPPInternalBinding bind1, ICPPInternalBinding subclassBind) {
		if (subclassBind instanceof ICPPClassType) {
			ICPPClassType classType = (ICPPClassType) subclassBind;
			ICPPBase[] bases;
			bases = classType.getBases();
			for (ICPPBase base : bases) {
				if (isSameClass(base,bind1)) {
					return true;
				}
			}
		}
		return false;
	}

	public static boolean isSameClass(MethodContext context1, MethodContext context2) {
		ICPPInternalBinding bind1 = getICPPInternalBinding(context1);
		ICPPInternalBinding bind2 = getICPPInternalBinding(context2);
		return isSameClass(bind1,bind2);
	}

	private static boolean isSameClass(ICPPBase base, ICPPInternalBinding bind2) {
		try {
			IBinding bind1 = base.getBaseClass();
			IScope scope1 = bind1.getScope();
			if (scope1 == null)
				return false;
			IASTNode node1 = ASTInternal.getPhysicalNodeOfScope(scope1);

			IScope scope2 = bind2.getScope();
			if (scope2 == null)
				return false;
			IASTNode node2 = ASTInternal.getPhysicalNodeOfScope(scope2);

			if (node1.equals(node2)) {
				if (bind1 instanceof ICPPInternalBinding) {
					ICPPInternalBinding bind1int = (ICPPInternalBinding) bind1;
					return bind1int.getDefinition().equals(bind2.getDefinition());
				}
				return false;
			}
			return false;
		} catch (DOMException e) {
			return false;
		}
	}

	private static boolean isSameClass(ICPPInternalBinding bind1, ICPPInternalBinding bind2) {
		try {
			IScope scope1 = bind1.getScope();
			if (scope1 == null)
				return false;
			IASTNode node1 = ASTInternal.getPhysicalNodeOfScope(scope1);

			IScope scope2 = bind2.getScope();
			if (scope2 == null)
				return false;
			IASTNode node2 = ASTInternal.getPhysicalNodeOfScope(scope2);

			if (node1.equals(node2)) {
				return bind1.getDefinition().equals(bind2.getDefinition());
			}
			return false;
		} catch (DOMException e) {
			return false;
		}
	}

	public static ICPPInternalBinding getICPPInternalBinding(MethodContext context) {
		IASTName decl = context.getMethodDeclarationName();
		IASTNode node = decl;
		while (node != null) {
			if (node instanceof ICPPASTCompositeTypeSpecifier) {
				ICPPASTCompositeTypeSpecifier type = (ICPPASTCompositeTypeSpecifier) node;
				IASTName classname = type.getName();
				return (ICPPInternalBinding)classname.resolveBinding();
			}
			node = node.getParent();
		}

		return null;
	}

	public boolean isInline() {
		return qname == null;
	}

	private static ICPPClassType getClassBinding(ICPPASTQualifiedName qname) {
		IASTName classname = qname.getNames()[qname.getNames().length - 2];
		ICPPClassType bind = (ICPPClassType)classname.resolveBinding();
		return bind;
	}

	public static boolean isSameClass(IASTName declName1, IASTName declName2) {
		ICPPClassType bind1 = getClassBinding(declName1);
		ICPPClassType bind2 = getClassBinding(declName2);
		return bind1.equals(bind2);
	}

	private static ICPPClassType getClassBinding(IASTName declName1) {
		if (declName1.getParent().getParent().getParent() instanceof ICPPASTCompositeTypeSpecifier) {
			ICPPASTCompositeTypeSpecifier compTypeSpec =
					(ICPPASTCompositeTypeSpecifier) declName1.getParent().getParent().getParent();
			return (ICPPClassType) compTypeSpec.getName().resolveBinding();
		}
		return null;
	}
}
