/*******************************************************************************
 * Copyright (c) 2009, 2020 Mountainminds GmbH & Co. KG and Contributors
 * This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Marc R. Hoffmann - initial API and implementation
 *
 *******************************************************************************/
package org.jacoco.core.internal.flow;

import org.jacoco.core.internal.analysis.Instruction;
import org.objectweb.asm.Label;

/**
 * Data container that is attached to {@link Label#info} objects to store flow
 * and instrumentation specific information. The information is only valid
 * locally in specific contexts.
 */
public final class LabelInfo {

	/**
	 * Reserved ID for "no probe".
	 */
	public static final int NO_PROBE = -1;

	private boolean target = false;

	private boolean multiTarget = false;

	private boolean successor = false;

	private boolean methodInvocationLine = false;

	private boolean done = false;

	private int probeid = NO_PROBE;

	private Label intermediate = null;

	private Instruction instruction = null;

	// instances are only created within this class
	private LabelInfo() {
	}

	/**
	 * Defines that the given label is a jump target.
	 *
	 * @param label
	 *            label to define
	 */
	public static void setTarget(final Label label) {
		final LabelInfo info = create(label);
		if (info.target || info.successor) {
			info.multiTarget = true;
		} else {
			info.target = true;
		}
	}

	/**
	 * Defines that the given label is the possible successor of the previous
	 * instruction in the method.
	 *
	 * @param label
	 *            label to define
	 */
	public static void setSuccessor(final Label label) {
		final LabelInfo info = create(label);
		info.successor = true;
		if (info.target) {
			info.multiTarget = true;
		}
	}

	/**
	 * Checks whether multiple control paths lead to a label. Control flow path
	 * to a certain label are: jump targets, exception handlers and normal
	 * control flow from its predecessor instruction (unless this is an
	 * unconditional jump or method exit).
	 *
	 * @param label
	 *            label to check
	 * @return <code>true</code> if the given multiple control paths lead to the
	 *         given label
	 */
	public static boolean isMultiTarget(final Label label) {
		final LabelInfo info = get(label);
		return info == null ? false : info.multiTarget;
	}

	/**
	 * Checks whether this label is the possible successor of the previous
	 * instruction in the method. This is the case if the predecessor isn't a
	 * unconditional jump or method exit instruction.
	 *
	 * @param label
	 *            label to check
	 * @return <code>true</code> if the label is a possible instruction
	 *         successor
	 */
	public static boolean isSuccessor(final Label label) {
		final LabelInfo info = get(label);
		return info == null ? false : info.successor;
	}

	/**
	 * Mark a given label as the beginning of a line with method invocations.
	 *
	 * @param label
	 *            label to mark
	 */
	public static void setMethodInvocationLine(final Label label) {
		create(label).methodInvocationLine = true;
	}

	/**
	 * Checks whether the a given label has been marked as a line with method
	 * invocations.
	 *
	 * @param label
	 *            label to check
	 * @return <code>true</code> if the label represents a line with method
	 *         invocations
	 */
	public static boolean isMethodInvocationLine(final Label label) {
		final LabelInfo info = get(label);
		return info == null ? false : info.methodInvocationLine;
	}

	/**
	 * Determines whether the given label needs a probe to be inserted before.
	 *
	 * @param label
	 *            label to test
	 * @return <code>true</code> if a probe should be inserted before
	 */
	public static boolean needsProbe(final Label label) {
		final LabelInfo info = get(label);
		return info != null && info.successor
				&& (info.multiTarget || info.methodInvocationLine);
	}

	/**
	 * Mark a given label as done.
	 *
	 * @param label
	 *            label to mark
	 */
	public static void setDone(final Label label) {
		create(label).done = true;
	}

	/**
	 * Resets the "done" status of a given label.
	 *
	 * @param label
	 *            label to reset
	 */
	public static void resetDone(final Label label) {
		final LabelInfo info = get(label);
		if (info != null) {
			info.done = false;
		}
	}

	/**
	 * Resets the "done" status of all given labels.
	 *
	 * @param labels
	 *            labels to reset
	 */
	public static void resetDone(final Label[] labels) {
		for (final Label label : labels) {
			resetDone(label);
		}
	}

	/**
	 * Checks whether this label is marked as done.
	 *
	 * @param label
	 *            label to check
	 * @return <code>true</code> if this label is marked as done
	 */
	public static boolean isDone(final Label label) {
		final LabelInfo info = get(label);
		return info == null ? false : info.done;
	}

	/**
	 * Sets the given probe id to the given label.
	 *
	 * @param label
	 *            label to assign a probe to
	 * @param id
	 *            id of the probe
	 */
	public static void setProbeId(final Label label, final int id) {
		create(label).probeid = id;
	}

	/**
	 * Returns the assigned probe id.
	 *
	 * @param label
	 *            label to check
	 * @return probe id or {@link #NO_PROBE} if no probe is assigned to the
	 *         label
	 */
	public static int getProbeId(final Label label) {
		final LabelInfo info = get(label);
		return info == null ? NO_PROBE : info.probeid;
	}

	/**
	 * Defines an intermediate label for the given label. Such intermediate
	 * labels are required during instrumentation to add probes to jump targets.
	 *
	 * @param label
	 *            label to define for
	 * @param intermediate
	 *            intermediate label
	 */
	public static void setIntermediateLabel(final Label label,
			final Label intermediate) {
		create(label).intermediate = intermediate;
	}

	/**
	 * Returns the intermediate label for the given label if one has been
	 * defined.
	 *
	 * @param label
	 *            label to look for
	 * @return intermediate label or <code>null</code>
	 */
	public static Label getIntermediateLabel(final Label label) {
		final LabelInfo info = get(label);
		return info == null ? null : info.intermediate;
	}

	/**
	 * Sets the instruction corresponding to this label.
	 *
	 * @param label
	 *            label to set the instruction for
	 * @param instruction
	 *            corresponding instruction
	 */
	public static void setInstruction(final Label label,
			final Instruction instruction) {
		create(label).instruction = instruction;
	}

	/**
	 * Returns the corresponding instruction for the given label if one has been
	 * defined.
	 *
	 * @param label
	 *            label to look for
	 * @return corresponding instruction or <code>null</code>
	 */
	public static Instruction getInstruction(final Label label) {
		final LabelInfo info = get(label);
		return info == null ? null : info.instruction;
	}

	private static LabelInfo get(final Label label) {
		final Object info = label.info;
		return info instanceof LabelInfo ? (LabelInfo) info : null;
	}

	private static LabelInfo create(final Label label) {
		LabelInfo info = get(label);
		if (info == null) {
			info = new LabelInfo();
			label.info = info;
		}
		return info;
	}

}
