/* *******************************************************************
 * 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.List;

import org.aspectj.bridge.IMessage;
import org.aspectj.bridge.IMessageHandler;
import org.aspectj.bridge.ISourceLocation;
import org.aspectj.bridge.Message;
import org.aspectj.weaver.IHasPosition;
import org.aspectj.weaver.ResolvedTypeX;
import org.aspectj.weaver.TypeX;
import org.aspectj.weaver.World;
import org.aspectj.weaver.patterns.*;
import org.aspectj.weaver.patterns.FormalBinding;
import org.aspectj.weaver.patterns.IScope;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
import org.eclipse.jdt.internal.compiler.lookup.ImportBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.core.compiler.CharOperation;

/**
 * Adaptor from org.eclipse.jdt.internal.compiler.lookup.Scope to org.aspectj.weaver.IScope
 * 
 * @author Jim Hugunin
 */
public class EclipseScope implements IScope {
	private Scope scope;
	private EclipseFactory world;
	private ResolvedTypeX enclosingType;
	private FormalBinding[] bindings;
	
	private String[] importedPrefixes = null;
	private String[] importedNames = null;


	public EclipseScope(FormalBinding[] bindings, Scope scope) {
		this.bindings = bindings;
		
		this.scope = scope;
		this.world = EclipseFactory.fromScopeLookupEnvironment(scope);
		
		this.enclosingType = world.fromEclipse(scope.enclosingSourceType());
	}
	
	
	public TypeX lookupType(String name, IHasPosition location) {
		TypeBinding b = scope.getType(WildTypePattern.splitNames(name));
		//FIXME need reasonable error handling...
		if (!b.isValidBinding()) {
			return ResolvedTypeX.MISSING;
		}
		
		//System.err.println("binding: " + b);
		//  Binding(tokens, bits & RestrictiveFlagMASK, this)
		return EclipseFactory.fromBinding(b);
		
		/*
		computeImports();
		
//		System.out.println("lookup: " + name + " in " + 
//			Arrays.asList(importedPrefixes));
		
		ResolvedTypeX ret = null;
		String dotName = "." + name;
		for (int i=0; i<importedNames.length; i++) {
			String importedName = importedNames[i];
			//??? can this be right
			if (importedName.endsWith(name) && 
				((importedName.length() == name.length()) ||
				 (importedName.endsWith(dotName))))
			{
				ResolvedTypeX found = resolveVisible(importedName);
				if (found == ResolvedTypeX.MISSING) continue;
				if (ret != null) {
					message(IMessage.ERROR, location, 
						"ambiguous type reference, both " + ret.getName() + " and " + importedName);
					return ResolvedTypeX.MISSING;
				} else {
					ret = found;
				}
			}
		}
		
		if (ret != null) return ret;
		
		//XXX need to handle ambiguous references here
		for (int i=0; i<importedPrefixes.length; i++) {
			String importedPrefix = importedPrefixes[i];
			ResolvedTypeX tryType = resolveVisible(importedPrefix + name);
			if (tryType != ResolvedTypeX.MISSING) {
				return tryType;
			}
		}

		return resolveVisible(name);
		*/
	}
	
	
//	private ResolvedTypeX resolveVisible(String name) {
//		ResolvedTypeX found = world.getWorld().resolve(TypeX.forName(name), true);
//		if (found == ResolvedTypeX.MISSING) return found;
//		if (ResolvedTypeX.isVisible(found.getModifiers(), found, enclosingType)) return found;
//		return ResolvedTypeX.MISSING; 
//	}
	

//	public TypeX lookupType(String name, IHasPosition location) {
//		char[][] namePieces = CharOperation.splitOn('.', name.toCharArray());
//		TypeBinding binding;
//		if (namePieces.length == 1) {
//			binding = scope.getType(namePieces[0]);
//		} else {
//			binding = scope.getType(namePieces);
//		}
//		
//		
//		if (!binding.isValidBinding()) {
//			//XXX do we do this always or sometimes
//			System.err.println("error: " + binding);
//			scope.problemReporter().invalidType(EclipseWorld.astForLocation(location), binding);
//			return ResolvedTypeX.MISSING;
//		}
//		//??? do we want this too
////		if (AstNode.isTypeUseDeprecated(binding, scope))
////			scope.problemReporter().deprecatedType(binding, EclipseWorld.astForLocation(location));
//		
//		return EclipseWorld.fromBinding(binding);
//	}
	
	
	
	private void computeImports() {
		if (importedNames != null) return;
		
		List importedNamesList = new ArrayList();
		List importedPrefixesList = new ArrayList();
		
		
		Scope currentScope = scope;
		//add any enclosing types to this list
		while (!(currentScope instanceof CompilationUnitScope)) {
			if (currentScope == null) {
				throw new RuntimeException("unimplemented");
			}
			if (currentScope instanceof ClassScope) {
				addClassAndParentsToPrefixes(((ClassScope)currentScope).referenceType().binding, importedPrefixesList);
			}
			currentScope = currentScope.parent;
		}
		
		CompilationUnitScope cuScope = (CompilationUnitScope)currentScope;

		String packageName = 
			new String(CharOperation.concatWith(cuScope.currentPackageName, '.'));
		//System.err.println("package: " + packageName);
		if (packageName.length() > 0) {
			importedPrefixesList.add(packageName + ".");
		}
		

		ImportBinding[] imports = cuScope.imports;
		for (int i = 0; i < imports.length; i++) {
			ImportBinding importBinding = imports[i];
			String importName =
			  new String(CharOperation.concatWith(importBinding.compoundName, '.'));
			
			//XXX wrong behavior for java.util.Map.*
			if (importBinding.onDemand) {
				importedPrefixesList.add(importName + ".");
			} else {
				importedNamesList.add(importName);
			}
		}
		
		TypeBinding[] topTypes = cuScope.topLevelTypes;
		for (int i = 0; i < topTypes.length; i++) {
			importedNamesList.add(EclipseFactory.fromBinding(topTypes[i]).getName());
		}
		
		importedNames =
			(String[])importedNamesList.toArray(new String[importedNamesList.size()]);
		
		importedPrefixes =
			(String[])importedPrefixesList.toArray(new String[importedPrefixesList.size()]);
	}

	private void addClassAndParentsToPrefixes(
		ReferenceBinding binding,
		List importedPrefixesList)
	{
		if (binding == null) return;
		importedPrefixesList.add(EclipseFactory.fromBinding(binding).getName()+"$");
		
		addClassAndParentsToPrefixes(binding.superclass(), importedPrefixesList);
		ReferenceBinding[] superinterfaces = binding.superInterfaces();
		if (superinterfaces != null) {
			for (int i = 0; i < superinterfaces.length; i++) {
				addClassAndParentsToPrefixes(superinterfaces[i], importedPrefixesList);
			}
		}
	}
	

	public String[] getImportedNames() {
		computeImports();
		return importedNames;
	}

	public String[] getImportedPrefixes() {
		computeImports();
		//System.err.println("prefixes: " + Arrays.asList(importedPrefixes));
		return importedPrefixes;
	}


	//XXX add good errors when would bind to extra parameters
    public FormalBinding lookupFormal(String name) {
        for (int i = 0, len = bindings.length; i < len; i++) {
            if (bindings[i].getName().equals(name)) return bindings[i];
        }
        return null;
    }


	public FormalBinding getFormal(int i) {
		return bindings[i];
	}

    public int getFormalCount() {
    	return bindings.length;
    }

	public ISourceLocation makeSourceLocation(IHasPosition location) {
		return new EclipseSourceLocation(scope.problemReporter().referenceContext.compilationResult(),
						location.getStart(), location.getEnd());
	}

	public IMessageHandler getMessageHandler() {
		return world.getWorld().getMessageHandler();
	}


	public void message(
		IMessage.Kind kind,
		IHasPosition location1,
		IHasPosition location2,
		String message) {
			message(kind, location1, message);
			message(kind, location2, message);
	}

	public void message(
		IMessage.Kind kind,
		IHasPosition location,
		String message) {
			//System.out.println("message: " + message + " loc: " + makeSourceLocation(location));
			getMessageHandler()
			.handleMessage(new Message(message, kind, null, makeSourceLocation(location)));

	}
	public World getWorld() {
		return world.getWorld();
	}

	public ResolvedTypeX getEnclosingType() {
		return enclosingType;
	}

}
