package org.jumbune.debugger.instrumentation.adapter;

import java.text.MessageFormat;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jumbune.common.job.Config;
import org.jumbune.debugger.instrumentation.utils.EnumJobSubmitMethods;
import org.jumbune.debugger.instrumentation.utils.InstrumentUtil;
import org.jumbune.debugger.instrumentation.utils.InstrumentationMessageLoader;
import org.jumbune.debugger.instrumentation.utils.MessageConstants;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;


/**
 * This adapter is used to add job configuration setting to enable/disable
 * profiling
 */
public class ProfileAdapter extends BaseAdapter {
	private static final Logger LOGGER = LogManager
			.getLogger(ProfileAdapter.class);

	/**
	 * <p>
	 * Create a new instance of ProfileAdapter.
	 * </p>
	 * 
	 * @param cv
	 *            Class visitor
	 */
	public ProfileAdapter(Config config, ClassVisitor cv) {
		super(config, Opcodes.ASM4);
		this.cv = cv;
	}

	@Override
	public void visitEnd() {
		for (Object o : methods) {
			MethodNode mn = (MethodNode) o;
			InsnList insnList = mn.instructions;
			AbstractInsnNode[] insnArr = insnList.toArray();

			/**
			 * Finding the method instruction nodes whose owner is mapreduce.Job
			 * and it submits the job
			 */
			for (AbstractInsnNode abstractInsnNode : insnArr) {
				if (abstractInsnNode instanceof MethodInsnNode) {
					MethodInsnNode min = (MethodInsnNode) abstractInsnNode;

					// finding job submission
					if (InstrumentUtil.isJobSubmissionMethod(min)) {

						LOGGER.debug(MessageFormat.format(
								InstrumentationMessageLoader
										.getMessage(MessageConstants.JOB_SUBMISSION_FOUND),
								getClassName() + "##" + mn.name));

						// validating that the owner of the method call is
						if (InstrumentUtil.isOwnerJob(min)) {
							LOGGER.debug(MessageFormat.format(
									InstrumentationMessageLoader
											.getMessage(MessageConstants.LOG_OWNER_IS_JOB),
									getClassName() + "##" + mn.name));
							AbstractInsnNode ain = min.getPrevious();

							while (!(ain instanceof VarInsnNode)) {
								ain = ain.getPrevious();
							}

/*							VarInsnNode vin = (VarInsnNode) ain;
							int jobVariableIndex = vin.var;
							InsnList il = null;

							// old api check
							if (getOwnerType(min).getInternalName().equals(
									CLASSNAME_JOB_CONF)) {
								il = addProfilingForOldAPI(jobVariableIndex);
							} else {
								il = addProfiling(jobVariableIndex);
							}

							insnList.insertBefore(vin, il);
*/						}
					}
				}
			}
			mn.visitMaxs(0, 0);
		}
		accept(cv);
	}

	/**
	 * <p>
	 * This method finds the owner class of the job
	 * </p>
	 * 
	 * @param min
	 *            MethodInsnNode method being visited
	 * @return class owner Job or JobClient
	 */
	private Type getOwnerType(MethodInsnNode min) {

		Type type = null;

		for (EnumJobSubmitMethods js : EnumJobSubmitMethods.values()) {
			if (min.owner.equals(js.getOwner().getInternalName())) {
				type = js.getOwner();
				break;
			}
		}
		// Since old api needs jobconf type and not jobclient.
		if (type.getInternalName().equals(CLASSNAME_MR_JOBCLIENT)) {
			type = TYPE_JOBCONF;
		}
		return type;
	}

	/**
	 * <p>
	 * This method provides instructions to enable/disable profiling for the job
	 * </p>
	 * 
	 * @param jobVariableIndex
	 *            Index of variable which stores the job
	 * @return Instructions
	 */
/*	private InsnList addProfiling(int jobVariableIndex) {
		JobConfig jobConfig = (JobConfig)getConfig();
		boolean iSHadoopJobProfiling = jobConfig.isHadoopJobProfileEnabled();
//		String hadoopJobProfilingParams = jobConfig.getHadoopJobProfileParams();
		String hadoopJobProfilingMaps = PROFILING_MAPPERS_INSTANCES;
		String hadoopJobProfilingReduces = PROFILING_REDUCER_INSTANCES;

		InsnList il = new InsnList();
		if (iSHadoopJobProfiling) {
			il.add(new LabelNode());
			il.add(new VarInsnNode(Opcodes.ALOAD, jobVariableIndex));
			il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, CLASSNAME_MR_JOB,
					GET_CONFIGURATION, Type.getMethodDescriptor(Type
							.getType(Configuration.class))));

			il.add(new LdcInsnNode(PROFILE_TASK));
			il.add(new LdcInsnNode(Boolean.toString(iSHadoopJobProfiling)));
			il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
					CLASSNAME_HADOOP_CONFIGURATION, SETTER_METHOD_PREFIX, Type
							.getMethodDescriptor(Type.VOID_TYPE, TYPE_STRING,
									TYPE_STRING)));

			// add profile parameters only if it is enabled
			il.add(new LabelNode());
			il.add(new VarInsnNode(Opcodes.ALOAD, jobVariableIndex));
			il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, CLASSNAME_MR_JOB,
					GET_CONFIGURATION, Type.getMethodDescriptor(Type
							.getType(Configuration.class))));

			il.add(new LdcInsnNode(PROFILE_PARAMS));
			il.add(new LdcInsnNode(hadoopJobProfilingParams));
			il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
					CLASSNAME_HADOOP_CONFIGURATION, SETTER_METHOD_PREFIX, Type
							.getMethodDescriptor(Type.VOID_TYPE, TYPE_STRING,
									TYPE_STRING)));

			il.add(new LabelNode());
			il.add(new VarInsnNode(Opcodes.ALOAD, jobVariableIndex));
			il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, CLASSNAME_MR_JOB,
					GET_CONFIGURATION, Type.getMethodDescriptor(Type
							.getType(Configuration.class))));

			il.add(new LdcInsnNode(PROFILE_MAPS));
			il.add(new LdcInsnNode(hadoopJobProfilingMaps));
			il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
					CLASSNAME_HADOOP_CONFIGURATION, SETTER_METHOD_PREFIX, Type
							.getMethodDescriptor(Type.VOID_TYPE, TYPE_STRING,
									TYPE_STRING)));

			il.add(new LabelNode());
			il.add(new VarInsnNode(Opcodes.ALOAD, jobVariableIndex));
			il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, CLASSNAME_MR_JOB,
					GET_CONFIGURATION, Type.getMethodDescriptor(Type
							.getType(Configuration.class))));

			il.add(new LdcInsnNode(PROFILE_REDUCES));
			il.add(new LdcInsnNode(hadoopJobProfilingReduces));
			il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
					CLASSNAME_HADOOP_CONFIGURATION, SETTER_METHOD_PREFIX, Type
							.getMethodDescriptor(Type.VOID_TYPE, TYPE_STRING,
									TYPE_STRING)));

			LOGGER.info(MessageFormat.format(
					InstrumentationMessageLoader
							.getMessage(MessageConstants.CLASS_INSTRUMENTED_FOR_PROFILING),
					getClassName()));
		}

		return il;
	}
*/

/*	*//**
	 * <p>
	 * This method provides instructions to enable/disable profiling for the job
	 * for old hadoop api
	 * </p>
	 * 
	 * @param jobVariableIndex
	 *            Index of variable which stores the job
	 * @return Instructions
	 *//*
	private InsnList addProfilingForOldAPI(int jobVariableIndex) {
		JobConfig jobConfig = (JobConfig)getConfig();
		boolean isHadoopJobProfiling = jobConfig.isHadoopJobProfileEnabled();
//		String hadoopJobProfilingParams = jobConfig.getHadoopJobProfileParams();
		String hadoopJobProfilingMaps = PROFILING_MAPPERS_INSTANCES;
		String hadoopJobProfilingReduces = PROFILING_REDUCER_INSTANCES ;

		InsnList il = new InsnList();
		if (isHadoopJobProfiling) {
			il.add(new LabelNode());
			il.add(new VarInsnNode(Opcodes.ALOAD, jobVariableIndex));
			il.add(new LdcInsnNode(isHadoopJobProfiling));
			il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
					CLASSNAME_JOB_CONF, "setProfileEnabled", Type
							.getMethodDescriptor(Type.VOID_TYPE,
									Type.BOOLEAN_TYPE)));

			// add profile parameters only if it is enabled
			il.add(new LabelNode());
			il.add(new VarInsnNode(Opcodes.ALOAD, jobVariableIndex));
			il.add(new LdcInsnNode(hadoopJobProfilingParams));
			il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
					CLASSNAME_JOB_CONF, "setProfileParams", Type
							.getMethodDescriptor(Type.VOID_TYPE, TYPE_STRING)));

			// for maps
			il.add(new LabelNode());
			il.add(new VarInsnNode(Opcodes.ALOAD, jobVariableIndex));
			il.add(new LdcInsnNode(Boolean.TRUE));
			il.add(new LdcInsnNode(hadoopJobProfilingMaps));
			il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
					CLASSNAME_JOB_CONF, "setProfileTaskRange", Type
							.getMethodDescriptor(Type.VOID_TYPE,
									Type.BOOLEAN_TYPE, TYPE_STRING)));
			// for reduce
			il.add(new LabelNode());
			il.add(new VarInsnNode(Opcodes.ALOAD, jobVariableIndex));
			il.add(new LdcInsnNode(Boolean.FALSE));
			il.add(new LdcInsnNode(hadoopJobProfilingReduces));
			il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
					CLASSNAME_JOB_CONF, "setProfileTaskRange", Type
							.getMethodDescriptor(Type.VOID_TYPE,
									Type.BOOLEAN_TYPE, TYPE_STRING)));

			LOGGER.info(MessageFormat.format(
					InstrumentationMessageLoader
							.getMessage(MessageConstants.CLASS_INSTRUMENTED_FOR_PROFILING),
					getClassName()));

		}
		return il;
	}
*/}