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

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.aspectj.bridge.ISourceLocation;
import org.aspectj.weaver.Member;
import org.aspectj.weaver.ResolvedMember;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.Shadow;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.World;
import org.aspectj.weaver.ast.Var;
import org.aspectj.weaver.tools.MatchingContext;

/**
 * @author colyer
 *
 */
public class ReflectionShadow extends Shadow {

	private final World world;
	private final ResolvedType enclosingType;
	private final ResolvedMember enclosingMember;
	private final MatchingContext matchContext;
	private Var thisVar = null;
	private Var targetVar = null;
	private Var[] argsVars = null;
	private Var atThisVar = null;
	private Var atTargetVar = null;
	private Map<ResolvedType, Var[]> atArgsVars = new HashMap<>();
	private Map<ResolvedType, Var> withinAnnotationVar = new HashMap<>();
	private Map<ResolvedType, Var> withinCodeAnnotationVar = new HashMap<>();
	private Map<ResolvedType, Var> annotationVar = new HashMap<>();
	private AnnotationFinder annotationFinder;

	public static Shadow makeExecutionShadow(World inWorld, java.lang.reflect.Member forMethod, MatchingContext withContext) {
		Kind kind = (forMethod instanceof Method) ? Shadow.MethodExecution : Shadow.ConstructorExecution;
		Member signature = ReflectionBasedReferenceTypeDelegateFactory.createResolvedMember(forMethod, inWorld);
		ResolvedType enclosingType = signature.getDeclaringType().resolve(inWorld);
		return new ReflectionShadow(inWorld, kind, signature, null, enclosingType, null, withContext);
	}

	public static Shadow makeAdviceExecutionShadow(World inWorld, java.lang.reflect.Method forMethod, MatchingContext withContext) {
		Kind kind = Shadow.AdviceExecution;
		Member signature = ReflectionBasedReferenceTypeDelegateFactory.createResolvedAdviceMember(forMethod, inWorld);
		ResolvedType enclosingType = signature.getDeclaringType().resolve(inWorld);
		return new ReflectionShadow(inWorld, kind, signature, null, enclosingType, null, withContext);
	}

	public static Shadow makeCallShadow(World inWorld, java.lang.reflect.Member aMember, java.lang.reflect.Member withinCode,
			MatchingContext withContext) {
		Shadow enclosingShadow = makeExecutionShadow(inWorld, withinCode, withContext);
		Member signature = ReflectionBasedReferenceTypeDelegateFactory.createResolvedMember(aMember, inWorld);
		ResolvedMember enclosingMember = ReflectionBasedReferenceTypeDelegateFactory.createResolvedMember(withinCode, inWorld);
		ResolvedType enclosingType = enclosingMember.getDeclaringType().resolve(inWorld);
		Kind kind = aMember instanceof Method ? Shadow.MethodCall : Shadow.ConstructorCall;
		return new ReflectionShadow(inWorld, kind, signature, enclosingShadow, enclosingType, enclosingMember, withContext);
	}

	public static Shadow makeCallShadow(World inWorld, java.lang.reflect.Member aMember, Class thisClass,
			MatchingContext withContext) {
		Shadow enclosingShadow = makeStaticInitializationShadow(inWorld, thisClass, withContext);
		Member signature = ReflectionBasedReferenceTypeDelegateFactory.createResolvedMember(aMember, inWorld);
		ResolvedMember enclosingMember = ReflectionBasedReferenceTypeDelegateFactory.createStaticInitMember(thisClass, inWorld);
		ResolvedType enclosingType = enclosingMember.getDeclaringType().resolve(inWorld);
		Kind kind = aMember instanceof Method ? Shadow.MethodCall : Shadow.ConstructorCall;
		return new ReflectionShadow(inWorld, kind, signature, enclosingShadow, enclosingType, enclosingMember, withContext);
	}

	public static Shadow makeStaticInitializationShadow(World inWorld, Class forType, MatchingContext withContext) {
		Member signature = ReflectionBasedReferenceTypeDelegateFactory.createStaticInitMember(forType, inWorld);
		ResolvedType enclosingType = signature.getDeclaringType().resolve(inWorld);
		Kind kind = Shadow.StaticInitialization;
		return new ReflectionShadow(inWorld, kind, signature, null, enclosingType, null, withContext);
	}

	public static Shadow makePreInitializationShadow(World inWorld, Constructor forConstructor, MatchingContext withContext) {
		Kind kind = Shadow.PreInitialization;
		Member signature = ReflectionBasedReferenceTypeDelegateFactory.createResolvedMember(forConstructor, inWorld);
		ResolvedType enclosingType = signature.getDeclaringType().resolve(inWorld);
		return new ReflectionShadow(inWorld, kind, signature, null, enclosingType, null, withContext);
	}

	public static Shadow makeInitializationShadow(World inWorld, Constructor forConstructor, MatchingContext withContext) {
		Kind kind = Shadow.Initialization;
		Member signature = ReflectionBasedReferenceTypeDelegateFactory.createResolvedMember(forConstructor, inWorld);
		ResolvedType enclosingType = signature.getDeclaringType().resolve(inWorld);
		return new ReflectionShadow(inWorld, kind, signature, null, enclosingType, null, withContext);
	}

	public static Shadow makeHandlerShadow(World inWorld, Class exceptionType, Class withinType, MatchingContext withContext) {
		Kind kind = Shadow.ExceptionHandler;
		Shadow enclosingShadow = makeStaticInitializationShadow(inWorld, withinType, withContext);
		Member signature = ReflectionBasedReferenceTypeDelegateFactory.createHandlerMember(exceptionType, withinType, inWorld);
		ResolvedMember enclosingMember = ReflectionBasedReferenceTypeDelegateFactory.createStaticInitMember(withinType, inWorld);
		ResolvedType enclosingType = enclosingMember.getDeclaringType().resolve(inWorld);
		return new ReflectionShadow(inWorld, kind, signature, enclosingShadow, enclosingType, enclosingMember, withContext);
	}

	public static Shadow makeHandlerShadow(World inWorld, Class exceptionType, java.lang.reflect.Member withinCode,
			MatchingContext withContext) {
		Kind kind = Shadow.ExceptionHandler;
		Shadow enclosingShadow = makeExecutionShadow(inWorld, withinCode, withContext);
		Member signature = ReflectionBasedReferenceTypeDelegateFactory.createHandlerMember(exceptionType,
				withinCode.getDeclaringClass(), inWorld);
		ResolvedMember enclosingMember = ReflectionBasedReferenceTypeDelegateFactory.createResolvedMember(withinCode, inWorld);
		ResolvedType enclosingType = enclosingMember.getDeclaringType().resolve(inWorld);
		return new ReflectionShadow(inWorld, kind, signature, enclosingShadow, enclosingType, enclosingMember, withContext);
	}

	public static Shadow makeFieldGetShadow(World inWorld, Field forField, Class callerType, MatchingContext withContext) {
		Shadow enclosingShadow = makeStaticInitializationShadow(inWorld, callerType, withContext);
		Member signature = ReflectionBasedReferenceTypeDelegateFactory.createResolvedField(forField, inWorld);
		ResolvedMember enclosingMember = ReflectionBasedReferenceTypeDelegateFactory.createStaticInitMember(callerType, inWorld);
		ResolvedType enclosingType = enclosingMember.getDeclaringType().resolve(inWorld);
		Kind kind = Shadow.FieldGet;
		return new ReflectionShadow(inWorld, kind, signature, enclosingShadow, enclosingType, enclosingMember, withContext);
	}

	public static Shadow makeFieldGetShadow(World inWorld, Field forField, java.lang.reflect.Member inMember,
			MatchingContext withContext) {
		Shadow enclosingShadow = makeExecutionShadow(inWorld, inMember, withContext);
		Member signature = ReflectionBasedReferenceTypeDelegateFactory.createResolvedField(forField, inWorld);
		ResolvedMember enclosingMember = ReflectionBasedReferenceTypeDelegateFactory.createResolvedMember(inMember, inWorld);
		ResolvedType enclosingType = enclosingMember.getDeclaringType().resolve(inWorld);
		Kind kind = Shadow.FieldGet;
		return new ReflectionShadow(inWorld, kind, signature, enclosingShadow, enclosingType, enclosingMember, withContext);
	}

	public static Shadow makeFieldSetShadow(World inWorld, Field forField, Class callerType, MatchingContext withContext) {
		Shadow enclosingShadow = makeStaticInitializationShadow(inWorld, callerType, withContext);
		Member signature = ReflectionBasedReferenceTypeDelegateFactory.createResolvedField(forField, inWorld);
		ResolvedMember enclosingMember = ReflectionBasedReferenceTypeDelegateFactory.createStaticInitMember(callerType, inWorld);
		ResolvedType enclosingType = enclosingMember.getDeclaringType().resolve(inWorld);
		Kind kind = Shadow.FieldSet;
		return new ReflectionShadow(inWorld, kind, signature, enclosingShadow, enclosingType, enclosingMember, withContext);
	}

	public static Shadow makeFieldSetShadow(World inWorld, Field forField, java.lang.reflect.Member inMember,
			MatchingContext withContext) {
		Shadow enclosingShadow = makeExecutionShadow(inWorld, inMember, withContext);
		Member signature = ReflectionBasedReferenceTypeDelegateFactory.createResolvedField(forField, inWorld);
		ResolvedMember enclosingMember = ReflectionBasedReferenceTypeDelegateFactory.createResolvedMember(inMember, inWorld);
		ResolvedType enclosingType = enclosingMember.getDeclaringType().resolve(inWorld);
		Kind kind = Shadow.FieldSet;
		return new ReflectionShadow(inWorld, kind, signature, enclosingShadow, enclosingType, enclosingMember, withContext);
	}

	public ReflectionShadow(World world, Kind kind, Member signature, Shadow enclosingShadow, ResolvedType enclosingType,
			ResolvedMember enclosingMember, MatchingContext withContext) {
		super(kind, signature, enclosingShadow);
		this.world = world;
		this.enclosingType = enclosingType;
		this.enclosingMember = enclosingMember;
		this.matchContext = withContext;
		if (world instanceof IReflectionWorld) {
			this.annotationFinder = ((IReflectionWorld) world).getAnnotationFinder();
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.Shadow#getIWorld()
	 */
	public World getIWorld() {
		return world;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.Shadow#getThisVar()
	 */
	public Var getThisVar() {
		if (thisVar == null && hasThis()) {
			thisVar = ReflectionVar.createThisVar(getThisType().resolve(world), this.annotationFinder);
		}
		return thisVar;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.Shadow#getTargetVar()
	 */
	public Var getTargetVar() {
		if (targetVar == null && hasTarget()) {
			targetVar = ReflectionVar.createTargetVar(getThisType().resolve(world), this.annotationFinder);
		}
		return targetVar;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.Shadow#getEnclosingType()
	 */
	public UnresolvedType getEnclosingType() {
		return this.enclosingType;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.Shadow#getArgVar(int)
	 */
	public Var getArgVar(int i) {
		if (argsVars == null) {
			this.argsVars = new Var[this.getArgCount()];
			for (int j = 0; j < this.argsVars.length; j++) {
				this.argsVars[j] = ReflectionVar.createArgsVar(getArgType(j).resolve(world), j, this.annotationFinder);
			}
		}
		if (i < argsVars.length) {
			return argsVars[i];
		} else {
			return null;
		}
	}

	public Var getThisJoinPointVar() {
		return null;
	}

	public Var getThisJoinPointStaticPartVar() {
		return null;
	}

	public Var getThisEnclosingJoinPointStaticPartVar() {
		return null;
	}

	public Var getThisAspectInstanceVar(ResolvedType aspectType) {
		return null;
	}

	public Var getKindedAnnotationVar(UnresolvedType forAnnotationType) {
		ResolvedType annType = forAnnotationType.resolve(world);
		if (annotationVar.get(annType) == null) {
			Var v = ReflectionVar.createAtAnnotationVar(annType, this.annotationFinder);
			annotationVar.put(annType, v);
		}
		return annotationVar.get(annType);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.Shadow#getWithinAnnotationVar(org.aspectj.weaver.UnresolvedType)
	 */
	public Var getWithinAnnotationVar(UnresolvedType forAnnotationType) {
		ResolvedType annType = forAnnotationType.resolve(world);
		if (withinAnnotationVar.get(annType) == null) {
			Var v = ReflectionVar.createWithinAnnotationVar(annType, this.annotationFinder);
			withinAnnotationVar.put(annType, v);
		}
		return withinAnnotationVar.get(annType);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.Shadow#getWithinCodeAnnotationVar(org.aspectj.weaver.UnresolvedType)
	 */
	public Var getWithinCodeAnnotationVar(UnresolvedType forAnnotationType) {
		ResolvedType annType = forAnnotationType.resolve(world);
		if (withinCodeAnnotationVar.get(annType) == null) {
			Var v = ReflectionVar.createWithinCodeAnnotationVar(annType, this.annotationFinder);
			withinCodeAnnotationVar.put(annType, v);
		}
		return withinCodeAnnotationVar.get(annType);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.Shadow#getThisAnnotationVar(org.aspectj.weaver.UnresolvedType)
	 */
	public Var getThisAnnotationVar(UnresolvedType forAnnotationType) {
		if (atThisVar == null) {
			atThisVar = ReflectionVar.createThisAnnotationVar(forAnnotationType.resolve(world), this.annotationFinder);
		}
		return atThisVar;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.Shadow#getTargetAnnotationVar(org.aspectj.weaver.UnresolvedType)
	 */
	public Var getTargetAnnotationVar(UnresolvedType forAnnotationType) {
		if (atTargetVar == null) {
			atTargetVar = ReflectionVar.createTargetAnnotationVar(forAnnotationType.resolve(world), this.annotationFinder);
		}
		return atTargetVar;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.Shadow#getArgAnnotationVar(int, org.aspectj.weaver.UnresolvedType)
	 */
	public Var getArgAnnotationVar(int i, UnresolvedType forAnnotationType) {
		ResolvedType annType = forAnnotationType.resolve(world);
		if (atArgsVars.get(annType) == null) {
			Var[] vars = new Var[getArgCount()];
			atArgsVars.put(annType, vars);
		}
		Var[] vars = atArgsVars.get(annType);
		if (i > (vars.length - 1))
			return null;
		if (vars[i] == null) {
			vars[i] = ReflectionVar.createArgsAnnotationVar(annType, i, this.annotationFinder);
		}
		return vars[i];
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.Shadow#getEnclosingCodeSignature()
	 */
	public Member getEnclosingCodeSignature() {
		// XXX this code is copied from BcelShadow with one minor change...
		if (getKind().isEnclosingKind()) {
			return getSignature();
		} else if (getKind() == Shadow.PreInitialization) {
			// PreInit doesn't enclose code but its signature
			// is correctly the signature of the ctor.
			return getSignature();
		} else if (enclosingShadow == null) {
			return this.enclosingMember;
		} else {
			return enclosingShadow.getSignature();
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.Shadow#getSourceLocation()
	 */
	public ISourceLocation getSourceLocation() {
		return null;
	}

	public MatchingContext getMatchingContext() {
		return this.matchContext;
	}
}
