package org.revenj.database.postgres.jinq.transform;

import java.lang.reflect.Field;

import org.jinq.rebased.org.objectweb.asm.Type;

import com.user00.thunk.SerializedLambda;

/**
 * Holds surface information about a lambda such as the parameters to the
 * lambda, the name of the method where the lambda comes from, etc.
 */
public class LambdaInfo {
	protected Object Lambda;
	protected SerializedLambda serializedLambda;
	protected int numCapturedArgs;
	protected int numLambdaArgs;
	/**
	 * JPAQueryComposer stores the lambdas that are chained together to create a query in a list.
	 * The lambdaIndex refers to the index of this lambda in the list of lambdas used to create
	 * the resulting query.
	 */
	protected int lambdaIndex;

	public static LambdaInfo analyze(Object lambda, int lambdaIndex, boolean throwExceptionOnFailure) {
		SerializedLambda s;
		try {
			s = SerializedLambda.extractLambda(lambda);
		} catch (Exception e) {
			if (throwExceptionOnFailure)
				throw new IllegalArgumentException("Could not extract code from lambda. This error sometimes occurs because your lambda references objects that aren't Serializable.", e);
			return null;
		}
		if (s == null) {
			if (throwExceptionOnFailure)
				throw new IllegalArgumentException("Could not extract code from lambda. This error sometimes occurs because your lambda references objects that aren't Serializable.");
			return null;
		}
		return new LambdaInfo(lambda, s, lambdaIndex);
	}

	LambdaInfo(Object lambda, SerializedLambda serializedLambda, int lambdaIndex) {
		this.Lambda = lambda;
		this.serializedLambda = serializedLambda;
		this.lambdaIndex = lambdaIndex;
		this.numCapturedArgs = serializedLambda.capturedArgs.length;
		this.numLambdaArgs = Type.getArgumentTypes(serializedLambda.implMethodSignature).length;
	}

	protected LambdaInfo(int lambdaIndex, int numCapturedArgs, int numLambdaArgs) {
		this.Lambda = null;
		this.serializedLambda = null;
		this.lambdaIndex = lambdaIndex;
		this.numCapturedArgs = numCapturedArgs;
		this.numLambdaArgs = numLambdaArgs;
	}

	public int getNumCapturedArgs() {
		return numCapturedArgs;
	}

	public int getNumLambdaArgs() {
		return numLambdaArgs;
	}

	public boolean hasLambdaObject() {
		return serializedLambda != null;
	}

	public Object getCapturedArg(int argIndex) {
		return serializedLambda.capturedArgs[argIndex];
	}

	// Is the lambda referring to a static method call
	public boolean isInvokeStatic() {
		return serializedLambda.implMethodKind == 6;
	}

	// Is the lambda referring to a virtual method call
	public boolean isInvokeVirtual() {
		return serializedLambda.implMethodKind == 5;
	}

	public Object getField(String fieldName) {
		try {
			Field field = Lambda.getClass().getDeclaredField(fieldName);
			field.setAccessible(true);
			return field.get(Lambda);
		} catch (SecurityException e) {
			throw new IllegalArgumentException("Cannot read field " + fieldName + " of lambda", e);
		} catch (Exception e) {
			try {
				return Lambda.getClass().getField(fieldName).get(Lambda);
			} catch (Exception e1) {
				throw new IllegalArgumentException("Cannot read field " + fieldName + " of lambda", e1);
			}
		}
	}

	public LambdaAnalysis fullyAnalyze(
			MetamodelUtil metamodel,
			ClassLoader alternateClassLoader,
			boolean isObjectEqualsSafe,
			boolean isAllEqualsSafe,
			boolean isCollectionContainsSafe,
			boolean throwExceptionOnFailure) {
		return LambdaAnalysis.fullyAnalyzeLambda(this, metamodel, alternateClassLoader, isObjectEqualsSafe, isAllEqualsSafe, isCollectionContainsSafe, throwExceptionOnFailure);
	}

	/**
	 * @return a string describing the source for the code of the lambda
	 */
	public String getLambdaSourceString() {
		return serializedLambda.implClass + "#" + serializedLambda.implMethodName + serializedLambda.implMethodSignature;
	}
}