/* *******************************************************************
 * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
 * All rights reserved. 
 * This program and the accompanying materials are made available 
 * under the terms of the Common Public License v1.0 
 * which accompanies this distribution and is available at 
 * http://www.eclipse.org/legal/cpl-v10.html 
 *  
 * Contributors: 
 *     PARC     initial implementation 
 * ******************************************************************/


package org.aspectj.ajdt.internal.compiler.lookup;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.IMemberFinder;
import org.eclipse.jdt.internal.compiler.lookup.InvocationSite;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemFieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
import org.eclipse.jdt.core.compiler.CharOperation;

public class InterTypeMemberFinder implements IMemberFinder {
	private List interTypeFields = new ArrayList();
	private List interTypeMethods = new ArrayList();
	
	public SourceTypeBinding sourceTypeBinding;

	public FieldBinding getField(
		SourceTypeBinding sourceTypeBinding,
		char[] fieldName,
		InvocationSite site,
		Scope scope)
	{
		FieldBinding retField = sourceTypeBinding.getFieldBase(fieldName, true);  // XXX may need to get the correct value for second parameter in the future (see #55341)
		if (interTypeFields.isEmpty()) return retField;
		int fieldLength = fieldName.length;
		
		for (int i=0, len=interTypeFields.size(); i < len; i++) {
			FieldBinding field = (FieldBinding)interTypeFields.get(i);
			if (field.name.length == fieldLength && CharOperation.prefixEquals(field.name, fieldName)) {
				retField = resolveConflicts(sourceTypeBinding, retField, field, site, scope);
			}
		}
		
		return retField;
	}

	private FieldBinding resolveConflicts(
		SourceTypeBinding sourceTypeBinding,
		FieldBinding retField,
		FieldBinding field,
		InvocationSite site,
		Scope scope)
	{
		if (retField == null) return field;
		if (site != null) {
			if (!field.canBeSeenBy(sourceTypeBinding, site, scope)) return retField;
			if (!retField.canBeSeenBy(sourceTypeBinding, site, scope)) return field;
		}
		//XXX need dominates check on aspects
		return new ProblemFieldBinding(retField.declaringClass, retField.name, ProblemReporter.Ambiguous);
	}


//	private void reportConflicts(SourceTypeBinding sourceTypeBinding,
//		MethodBinding m1, MethodBinding m2)
//	{
//		if (m1 == m2) {
//			System.err.println("odd that we're ecomparing the same: " + m1);
//			return;
//		}
//		
//		if (!m1.areParametersEqual(m2)) return;
		
//		if (m1 instanceof InterTypeMethodBinding) {
//			if (m2 instanceof InterTypeMethodBinding) {
//				reportConflictsBoth(sourceTypeBinding,
//									(InterTypeMethodBinding)m1,
//									(InterTypeMethodBinding)m2);
//			} else {
//				reportConflictsOne(sourceTypeBinding,
//									(InterTypeMethodBinding)m1,
//									m2);
//			}
//		} else if (m2 instanceof InterTypeMethodBinding) {
//			reportConflictsOne(sourceTypeBinding,
//									(InterTypeMethodBinding)m2,
//									m1);
//		} else {
//			reportConflictsNone(sourceTypeBinding,
//								m2,
//								m1);
//		}
//	}
	

//	private void reportConflicts(
//		SourceTypeBinding sourceTypeBinding,
//		MethodBinding m1,
//		MethodBinding m2)
//	{
//		//System.err.println("compare: " + m1 + " with " + m2);
//		
//		if (m1 == m2) {
//			System.err.println("odd that we're ecomparing the same: " + m1);
//			return;
//		}
//		
//		if (!m1.areParametersEqual(m2)) return;
//		
//		//System.err.println("t1: " + getTargetType(m1) + ", " + getTargetType(m2));
//		
//		if (getTargetType(m1) != getTargetType(m2)) return;
//		
//		if (m1.declaringClass == m2.declaringClass) {
//			duplicateMethodBinding(m1, m2);
//			return;
//		}
//		
//		
//		if (m1.isPublic() || m2.isPublic()) {
//			duplicateMethodBinding(m1, m2);
//			return;
//		}
//		
//		// handle the wierd case where the aspect is a subtype of the target
//		if (m2.isProtected()) {
//			if (m2.declaringClass.isSuperclassOf(m1.declaringClass)) {
//				duplicateMethodBinding(m1, m2);
//			}
//			// don't return because we also want to do the package test
//		}
//		
//		if (!m1.isPrivate() || !m2.isPrivate()) {
//			// at least package visible
//			if (m1.declaringClass.getPackage() == m2.declaringClass.getPackage()) {
//				duplicateMethodBinding(m1, m2);
//			}
//			return;
//		}				
//		
//		//XXX think about inner types some day
//	}
////	
	private boolean isVisible(MethodBinding m1, ReferenceBinding s) {
		if (m1.declaringClass == s) return true;

		
		if (m1.isPublic()) return true;

		//don't need to handle protected
		//if (m1.isProtected()) {

		
		if (!m1.isPrivate()) {
			// at least package visible
			return (m1.declaringClass.getPackage() == s.getPackage());
		}
		
		return false;
	}
	
//
//	private void duplicateMethodBinding(MethodBinding m1, MethodBinding m2) {
//		ReferenceBinding t1 = m1.declaringClass;
//		ReferenceBinding t2 = m2.declaringClass;
//		
//		
//		
//		
//		
//		
//		if (!(t1 instanceof SourceTypeBinding) || !(t2 instanceof SourceTypeBinding)) {
//			throw new RuntimeException("unimplemented");
//		}
//		
//		SourceTypeBinding s1 = (SourceTypeBinding)t1;
//		SourceTypeBinding s2 = (SourceTypeBinding)t2;
//
//		if (m1.sourceMethod() != null) {
//			s1.scope.problemReporter().duplicateMethodInType(s2, m1.sourceMethod());
//		}
//		if (m2.sourceMethod() != null) {
//			s2.scope.problemReporter().duplicateMethodInType(s1, m2.sourceMethod());
//		}
//	}
	
//	private void reportConflictsNone(
//		SourceTypeBinding sourceTypeBinding,
//		MethodBinding m2,
//		MethodBinding m1)
//	{
//		throw new RuntimeException("not possible");
//	}



//		ReferenceBinding t1 = getDeclaringClass(m1);
//		//.declaringClass;
//		ReferenceBinding t2 = getDeclaringClass(m2);
//		//.declaringClass;
//		
//		if (t1 == t2) {
//			AbstractMethodDeclaration methodDecl = m2.sourceMethod(); // cannot be retrieved after binding is lost
//			System.err.println("duplicate: " + t1 + ", " + t2);
//			sourceTypeBinding.scope.problemReporter().duplicateMethodInType(sourceTypeBinding, methodDecl);
//			methodDecl.binding = null;
//			//methods[m] = null;  //XXX duplicate problem reports
//			return;
//		}
//		
//		if (!(t1 instanceof SourceTypeBinding) || !(t2 instanceof SourceTypeBinding)) {
//			throw new RuntimeException("unimplemented");
//		}
//		
//		SourceTypeBinding s1 = (SourceTypeBinding)t1;
//		SourceTypeBinding s2 = (SourceTypeBinding)t2;
//		
//		
//		if (m1.canBeSeenBy(s1, null, s2.scope) || m2.canBeSeenBy(s2, null, s1.scope)) {
//			s1.scope.problemReporter().duplicateMethodInType(s2, m1.sourceMethod());
//			s2.scope.problemReporter().duplicateMethodInType(s1, m2.sourceMethod());
//		}
//	}

//	private ReferenceBinding getTargetType(MethodBinding m2) {
//		if (m2 instanceof InterTypeMethodBinding) {
//			return ((InterTypeMethodBinding)m2).getTargetType();
//		}
//		
//		return m2.declaringClass;
//	}


	
	//XXX conflicts
	public MethodBinding[] getMethods(
		SourceTypeBinding sourceTypeBinding,
		char[] selector) {
//		System.err.println("getMethods: " + new String(sourceTypeBinding.signature()) +
//							", " + new String(selector));
			
		MethodBinding[] orig = sourceTypeBinding.getMethodsBase(selector);
		if (interTypeMethods.isEmpty()) return orig;
		
		List ret = new ArrayList(Arrays.asList(orig));
//		System.err.println("declared method: " + ret + " inters = " + interTypeMethods);
		
		for (int i=0, len=interTypeMethods.size(); i < len; i++) {
			MethodBinding method = (MethodBinding)interTypeMethods.get(i);
			
			if (CharOperation.equals(selector, method.selector)) {
				ret.add(method);
			}
		}
		
		if (ret.isEmpty()) return SourceTypeBinding.NoMethods;
		
//		System.err.println("method: " + ret);
		
		// check for conflicts
//		int len = ret.size();
//		if (len > 1) {
//			for (int i=0; i <len; i++) {
//				MethodBinding m1 = (MethodBinding)ret.get(i);
//				for (int j=i+1; j < len; j++) {
//					MethodBinding m2 = (MethodBinding)ret.get(j);
//					//reportConflicts(sourceTypeBinding, m1, m2);
//				}
//			}	
//		}
		
		
		//System.err.println("got methods: " + ret + " on " + sourceTypeBinding);
		
		return (MethodBinding[])ret.toArray(new MethodBinding[ret.size()]);	
	}
	
	public MethodBinding getExactMethod(
		SourceTypeBinding sourceTypeBinding,
		char[] selector,
		TypeBinding[] argumentTypes)
	{
		MethodBinding ret = sourceTypeBinding.getExactMethodBase(selector, argumentTypes);
		
		// An intertype declaration may override an inherited member (Bug#50776)
		for (int i=0, len=interTypeMethods.size(); i < len; i++) {
			MethodBinding im =
				(MethodBinding)interTypeMethods.get(i);
			if (matches(im, selector, argumentTypes)) {
				return im;
			}
		}
		return ret;
	}
//				if (isVisible(im, sourceTypeBinding)) {
//					if (ret == null) {
//						ret = im;
//					} else {
//						ret = resolveOverride(ret, im);
//					}
//				}
//			}
//		}
//		return ret;
//	}

//	private MethodBinding resolveOverride(MethodBinding m1, MethodBinding m2) {
//		ReferenceBinding t1 = getTargetType(m1);
//		ReferenceBinding t2 = getTargetType(m2);
//		if (t1 == t2) {
//			//XXX also need a test for completely matching sigs
//			if (m1.isAbstract()) return m2;
//			else if (m2.isAbstract()) return m1;
//			
//			
//			if (m1 instanceof InterTypeMethodBinding) {
//				//XXX need to handle dominates here
//				EclipseWorld world = EclipseWorld.fromScopeLookupEnvironment(sourceTypeBinding.scope);
//				int cmp = compareAspectPrecedence(world.fromEclipse(m1.declaringClass), 
//									    world.fromEclipse(m2.declaringClass));
//				if (cmp < 0) return m2;
//				else if (cmp > 0) return m1;
//			}
//			
//			duplicateMethodBinding(m1, m2);
//			return null;
//		}
//		if (t1.isSuperclassOf(t2)) {
//			return m2;
//		}
//		if (t2.isSuperclassOf(t1)) {
//			return m1;
//		}
//		
//		duplicateMethodBinding(m1, m2);
//		return null;
//	}


//	private int compareAspectPrecedence(ResolvedTypeX a1, ResolvedTypeX a2) {
//		World world = a1.getWorld();
//		int ret = world.compareByDominates(a1, a2);
//		if (ret == 0) {
//			if (a1.isAssignableFrom(a2)) return -1;
//			if (a2.isAssignableFrom(a1)) return +1;
//		}
//		return ret;
//	}

	
	//
	private boolean matches(MethodBinding m1, MethodBinding m2) {
		return matches(m1, m2.selector, m2.parameters);
		// && 
		//	(isVisible(m1, m2.declaringClass) || isVisible(m2, m1.declaringClass));
	}
	
	private boolean matches(MethodBinding method, char[] selector, TypeBinding[] argumentTypes) {
		if (!CharOperation.equals(selector, method.selector)) return false;
		int argCount = argumentTypes.length;
		if (method.parameters.length != argCount) return false;
		
		TypeBinding[] toMatch = method.parameters;
		for (int p = 0; p < argCount; p++) {
			if (toMatch[p] != argumentTypes[p]) return false;
		}
		return true;
	}
	
	
	

	public void addInterTypeField(FieldBinding binding) {
		//System.err.println("adding: " + binding + " to " + this);
		interTypeFields.add(binding);
	}
	
	public void addInterTypeMethod(MethodBinding binding) {
		// check for conflicts with existing methods, should really check type as well...
		//System.err.println("adding: " + binding + " to " + sourceTypeBinding);
		if (isVisible(binding, sourceTypeBinding)) {
			MethodBinding[] baseMethods = sourceTypeBinding.methods;
			for (int i=0, len=baseMethods.length; i < len; i++) {
				MethodBinding b = baseMethods[i];
				if (matches(binding, b)) {
					// this always means we should remove the existing method
					if (b.sourceMethod() != null) {
						b.sourceMethod().binding = null; 
					}
					sourceTypeBinding.removeMethod(i);
					//System.err.println("    left: " + Arrays.asList(sourceTypeBinding.methods));
					break;
				}
			}
		}

		interTypeMethods.add(binding);
	}

}
