/* *******************************************************************
 * Copyright (c) 2005 Contributors.
 * All rights reserved.
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Public License v 2.0
 * which accompanies this distribution and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
 *
 * Contributors:
 *   Adrian Colyer			Initial implementation
 * ******************************************************************/
package org.aspectj.weaver.internal.tools;

import java.io.IOException;
import java.util.Map;

import org.aspectj.util.FuzzyBoolean;
import org.aspectj.weaver.CompressingDataOutputStream;
import org.aspectj.weaver.IntMap;
import org.aspectj.weaver.ReferenceType;
import org.aspectj.weaver.ReferenceTypeDelegate;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.Shadow;
import org.aspectj.weaver.World;
import org.aspectj.weaver.ast.Literal;
import org.aspectj.weaver.ast.Test;
import org.aspectj.weaver.patterns.Bindings;
import org.aspectj.weaver.patterns.ExposedState;
import org.aspectj.weaver.patterns.FastMatchInfo;
import org.aspectj.weaver.patterns.IScope;
import org.aspectj.weaver.patterns.PatternNodeVisitor;
import org.aspectj.weaver.patterns.Pointcut;
import org.aspectj.weaver.reflect.ReflectionBasedReferenceTypeDelegate;
import org.aspectj.weaver.reflect.ReflectionFastMatchInfo;
import org.aspectj.weaver.reflect.ReflectionShadow;
import org.aspectj.weaver.reflect.ReflectionWorld;
import org.aspectj.weaver.tools.ContextBasedMatcher;
import org.aspectj.weaver.tools.MatchingContext;

/**
 * Implementation of Pointcut that is backed by a user-extension pointcut designator handler.
 *
 */
public class PointcutDesignatorHandlerBasedPointcut extends Pointcut {

	private final ContextBasedMatcher matcher;
	private final World world;

	public PointcutDesignatorHandlerBasedPointcut(ContextBasedMatcher expr, World world) {
		this.matcher = expr;
		this.world = world;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.patterns.Pointcut#getPointcutKind()
	 */
	public byte getPointcutKind() {
		return Pointcut.USER_EXTENSION;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.patterns.Pointcut#fastMatch(org.aspectj.weaver.patterns.FastMatchInfo)
	 */
	public FuzzyBoolean fastMatch(FastMatchInfo info) {
		if (info instanceof ReflectionFastMatchInfo) {
			// Really need a reflectionworld here...
			if (!(world instanceof ReflectionWorld)) {
				throw new IllegalStateException("Can only match user-extension pcds with a ReflectionWorld");
			}
			Class<?> clazz = null;
			try {
				clazz = Class.forName(info.getType().getName(), false, ((ReflectionWorld) world).getClassLoader());
			} catch (ClassNotFoundException cnfe) {
				if (info.getType() instanceof ReferenceType) {
					ReferenceTypeDelegate rtd = ((ReferenceType)info.getType()).getDelegate();
					if (rtd instanceof ReflectionBasedReferenceTypeDelegate) {
						clazz = ((ReflectionBasedReferenceTypeDelegate)rtd).getClazz();
					}
				}
			}
			if (clazz == null) {
				return FuzzyBoolean.MAYBE;
			}
			return FuzzyBoolean.fromBoolean(this.matcher.couldMatchJoinPointsInType(clazz, ((ReflectionFastMatchInfo) info).getMatchingContext()));
		}
		throw new IllegalStateException("Can only match user-extension pcds against Reflection FastMatchInfo objects");
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.patterns.Pointcut#couldMatchKinds()
	 */
	public int couldMatchKinds() {
		return Shadow.ALL_SHADOW_KINDS_BITS;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.patterns.Pointcut#matchInternal(org.aspectj.weaver.Shadow)
	 */
	protected FuzzyBoolean matchInternal(Shadow shadow) {
		if (shadow instanceof ReflectionShadow) {
			MatchingContext context = ((ReflectionShadow) shadow).getMatchingContext();
			org.aspectj.weaver.tools.FuzzyBoolean match = this.matcher.matchesStatically(context);
			if (match == org.aspectj.weaver.tools.FuzzyBoolean.MAYBE) {
				return FuzzyBoolean.MAYBE;
			} else if (match == org.aspectj.weaver.tools.FuzzyBoolean.YES) {
				return FuzzyBoolean.YES;
			} else if (match == org.aspectj.weaver.tools.FuzzyBoolean.NO) {
				return FuzzyBoolean.NO;
			}
		}
		throw new IllegalStateException("Can only match user-extension pcds against Reflection shadows (not BCEL)");
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.patterns.Pointcut#resolveBindings(org.aspectj.weaver.patterns.IScope,
	 * org.aspectj.weaver.patterns.Bindings)
	 */
	protected void resolveBindings(IScope scope, Bindings bindings) {
		// no-op
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.patterns.Pointcut#concretize1(org.aspectj.weaver.ResolvedType, org.aspectj.weaver.ResolvedType,
	 * org.aspectj.weaver.IntMap)
	 */
	protected Pointcut concretize1(ResolvedType inAspect, ResolvedType declaringType, IntMap bindings) {
		return this;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.patterns.Pointcut#findResidueInternal(org.aspectj.weaver.Shadow,
	 * org.aspectj.weaver.patterns.ExposedState)
	 */
	protected Test findResidueInternal(Shadow shadow, ExposedState state) {
		if (!this.matcher.mayNeedDynamicTest()) {
			return Literal.TRUE;
		} else {
			// could be more efficient here!
			matchInternal(shadow);
			return new MatchingContextBasedTest(this.matcher);
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.patterns.Pointcut#parameterizeWith(java.util.Map)
	 */
	public Pointcut parameterizeWith(Map typeVariableMap, World w) {
		return this;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.patterns.PatternNode#write(java.io.DataOutputStream)
	 */
	public void write(CompressingDataOutputStream s) throws IOException {
		throw new UnsupportedOperationException("can't write custom pointcut designator expressions to stream");
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.patterns.PatternNode#accept(org.aspectj.weaver.patterns.PatternNodeVisitor, java.lang.Object)
	 */
	public Object accept(PatternNodeVisitor visitor, Object data) {
		// visitor.visit(this);
		// no-op?
		return data;
	}

}
