/* ###
 * IP: GHIDRA
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ghidra.pcode.exec;

import java.util.List;
import java.util.Map;

import ghidra.app.plugin.processors.sleigh.template.OpTpl;
import ghidra.app.util.pcode.AbstractAppender;
import ghidra.app.util.pcode.AbstractPcodeFormatter;
import ghidra.pcode.emulate.EmulateInstructionStateModifier;
import ghidra.pcode.error.LowlevelError;
import ghidra.program.model.lang.Language;
import ghidra.program.model.pcode.PcodeOp;

/**
 * The executor's internal counter
 * 
 * <p>
 * To distinguish the program counter of a p-code program from the program counter of the machine it
 * models, we address p-code ops by "index." When derived from an instruction, the address and index
 * together form the "sequence number." Because the executor care's not about the derivation of a
 * p-code program, it counts through indices. The frame carries with it the p-code ops comprising
 * its current p-code program.
 * 
 * <p>
 * A p-code emulator feeds p-code to an executor by decoding one instruction at a time. Thus, the
 * "current p-code program" comprises only those ops generated by a single instruction. Or else, it
 * is a user-supplied p-code program, e.g., to evaluate a Sleigh expression. The frame completes the
 * program by falling-through, i.e., stepping past the final op, or by branching externally, i.e.,
 * to a different machine instruction. The emulator must then update its program counter accordingly
 * and proceed to the next instruction.
 */
public class PcodeFrame {
	protected class MyAppender extends AbstractAppender<String> {
		protected final StringBuffer buf = new StringBuffer();
		protected boolean isLineLabel = false;
		protected int i = 0;

		public MyAppender(Language language) {
			super(language, true);
			buf.append("<p-code frame: index=" + index);
			if (branched != -1) {
				buf.append(" branched=" + branched);
			}
			buf.append(" {\n");
		}

		@Override
		protected void appendString(String string) {
			buf.append(string);
		}

		@Override
		public void appendLineLabel(long label) {
			buf.append("  " + stringifyLineLabel(label));
		}

		@Override
		public void appendIndent() {
			if (isLineLabel) {
				buf.append("    ");
			}
			if (i == branched) {
				buf.append(" *> ");
			}
			else if (i == index) {
				buf.append(" -> ");
			}
			else {
				buf.append("    ");
			}
		}

		protected void endLine() {
			if (!isLineLabel) {
				i++;
			}
			buf.append("\n");
		}

		@Override
		protected String stringifyUseropUnchecked(Language language, int id) {
			String name = super.stringifyUseropUnchecked(language, id);
			if (name != null) {
				return name;
			}
			return useropNames.get(id);
		}

		@Override
		public String finish() {
			if (index == code.size()) {
				buf.append(" *> fall-through\n");
			}
			buf.append("}>");
			return buf.toString();
		}
	}

	protected class MyFormatter extends AbstractPcodeFormatter<String, MyAppender> {
		@Override
		protected MyAppender createAppender(Language language, boolean indent) {
			return new MyAppender(language);
		}

		@Override
		protected FormatResult formatOpTemplate(MyAppender appender, OpTpl op) {
			appender.isLineLabel = isLineLabel(op);
			FormatResult result = super.formatOpTemplate(appender, op);
			appender.endLine();
			return result;
		}
	}

	private final Language language;
	private final List<PcodeOp> code;
	private final Map<Integer, String> useropNames;

	private int count = 0;
	private int index = 0;
	private int branched = -1;

	/**
	 * Construct a frame of p-code execution
	 * 
	 * <p>
	 * The passed in code should be an immutable list. It is returned directly by
	 * {@link #getCode()}, which would otherwise allow mutation. The frame does not create its own
	 * immutable copy as a matter of efficiency. Instead, the provider of the code should create an
	 * immutable copy, probably once, e.g., when compiling a {@link PcodeProgram}.
	 * 
	 * @param language the language to which the program applies
	 * @param code the program's p-code
	 * @param useropNames a map of additional sleigh/p-code userops linked to the program
	 */
	public PcodeFrame(Language language, List<PcodeOp> code, Map<Integer, String> useropNames) {
		this.language = language;
		this.code = code;
		this.useropNames = useropNames;
	}

	@Override
	public String toString() {
		return new MyFormatter().formatOps(language, code);
	}

	/**
	 * The number of p-code ops executed
	 * 
	 * <p>
	 * Contrast this to {@link #index()}, which marks the next op to be executed. This counts the
	 * number of ops executed, which will differ from index when an internal branch is taken.
	 * 
	 * @return the count
	 */
	public int count() {
		return count;
	}

	/**
	 * The index of the <em>next</em> p-code op to be executed
	 * 
	 * <p>
	 * If the last p-code op resulted in a branch, this will instead return -1.
	 * 
	 * @see #isBranch()
	 * @see #isFallThrough()
	 * @see #isFinished()
	 * @return the index, i.e, p-code "program counter."
	 */
	public int index() {
		return index;
	}

	/**
	 * Get the op at the current index, and then advance that index
	 * 
	 * <p>
	 * This is used in the execution loop to retrieve each op to execute
	 * 
	 * @return the op to execute
	 */
	public PcodeOp nextOp() {
		return code.get(advance());
	}

	/**
	 * Advance the index
	 * 
	 * @return the value of the index <em>before</em> it was advanced
	 */
	public int advance() {
		count++;
		return index++;
	}

	/**
	 * Step the index back one
	 * 
	 * @return the value of the index <em>before</em> it was stepped back
	 */
	public int stepBack() {
		count--;
		return index--;
	}

	/**
	 * Get the name of the userop for the given number
	 * 
	 * @param userop the userop number, as encoded in the first operand of {@link PcodeOp#CALLOTHER}
	 * @return the name of the userop, as expressed in the Sleigh source
	 */
	public String getUseropName(int userop) {
		return useropNames.get(userop);
	}

	/**
	 * Get the map of userop numbers to names
	 * 
	 * @return the map
	 */
	public Map<Integer, String> getUseropNames() {
		return useropNames;
	}

	/**
	 * Check if the index has advanced past the end of the p-code program
	 * 
	 * <p>
	 * If the index has advanced beyond the program, it implies the program has finished executing.
	 * In the case of instruction emulation, no branch was encountered. The machine should advance
	 * to the fall-through instruction.
	 * 
	 * @see #isBranch()
	 * @see #isFinished()
	 * @return true if the program completed without branching
	 */
	public boolean isFallThrough() {
		return index == code.size();
	}

	/**
	 * Check if the p-code program has executed a branch
	 * 
	 * <p>
	 * Branches can be internal, i.e., within the current program, or external, i.e., to another
	 * machine instructions. This refers strictly to the latter.
	 * 
	 * @see #isFallThrough()
	 * @see #isFinished()
	 * @return true if the program completed with an external branch
	 */
	public boolean isBranch() {
		return index == -1;
	}

	/**
	 * Check if the p-code program is completely executed
	 * 
	 * @see #isFallThrough()
	 * @see #isBranch()
	 * @return true if execution finished, either in fall-through or an external branch
	 */
	public boolean isFinished() {
		return !(0 <= index && index < code.size());
	}

	/**
	 * Perform an internal branch, relative to the <em>current op</em>.
	 * 
	 * <p>
	 * Because index advances before execution of each op, the index is adjusted by an extra -1.
	 * 
	 * @param rel the adjustment to the index
	 */
	public void branch(int rel) {
		index += rel - 1; // -1 because we already advanced
		if (!(0 <= index && index <= code.size())) {
			throw new LowlevelError("Bad p-code branch");
		}
	}

	/**
	 * Complete the p-code program, indicating an external branch
	 */
	public void finishAsBranch() {
		branched = index - 1; // -1 because we already advanced
		index = -1;
	}

	/**
	 * Get all the ops in the current p-code program.
	 * 
	 * @return the list of ops
	 */
	public List<PcodeOp> getCode() {
		return code;
	}

	/**
	 * Copy the frame's code (shallow copy) into a new array
	 * 
	 * @return the array of ops
	 */
	public PcodeOp[] copyCode() {
		return code.toArray(PcodeOp[]::new);
	}

	/**
	 * Get the index of the last (branch) op executed
	 * 
	 * <p>
	 * The behavior here is a bit strange for compatibility with
	 * {@link EmulateInstructionStateModifier}. If the p-code program (likely derived from a machine
	 * instruction) completed with fall-through, then this will return -1. If it completed on a
	 * branch, then this will return the index of that branch.
	 * 
	 * @return
	 */
	public int getBranched() {
		return branched;
	}
}
