/* ###
 * 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.
 */
/*
 * PseudoInstruction.java
 * 
 * Created on Aug 29, 2003
 */
package ghidra.app.util;

import java.math.BigInteger;
import java.util.List;
import java.util.OptionalInt;

import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.scalar.Scalar;
import ghidra.program.model.symbol.*;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.AssertException;

/**
 * Pseudo (i.e., fake) instruction that is generated by the Disassembler. This form of has some
 * limitation over an instruction which is obtained from a program listing. The instruction will
 * completely cache all bytes corresponding to the prototype length at the specified address.
 * Additional bytes will be cached for delay-slotted instructions to facilitate pcode generation and
 * obtaining general pcode related attributes.
 */
public class PseudoInstruction extends PseudoCodeUnit implements Instruction, InstructionContext {

	private final static Address[] EMPTY_ADDR_ARRAY = new Address[0];

	private AddressFactory addrFactory;

	private InstructionBlock block; // may be null
	private InstructionPrototype instrProto;
	private ProcessorContext procContext;
	private ParserContext parserContext;
	private Address fallThroughOverride = null; // NO_ADDRESS indicate fall-through removed
	private FlowOverride flowOverride = FlowOverride.NONE;

	/**
	 * Construct a new PseudoInstruction within a program.
	 * 
	 * @param program is the given Program
	 * @param addr address of the instruction
	 * @param prototype prototype of the instruction
	 * @param memBuffer buffer containing the bytes for the instruction
	 * @param procContext processor state information during disassembly
	 * @throws AddressOverflowException if code unit length causes wrap within space
	 */
	public PseudoInstruction(Program program, Address addr, InstructionPrototype prototype,
			MemBuffer memBuffer, ProcessorContext procContext) throws AddressOverflowException {
		super(program, addr, prototype.getLength(), getByteCacheSize(prototype), memBuffer);
		this.instrProto = prototype;
		this.procContext = procContext;
		if (program != null) {
			addrFactory = program.getAddressFactory();
		}
	}

	/**
	 * Construct a new PseudoInstruction within a program.
	 * 
	 * @param addrFactory program/language address factory
	 * @param addr address of the instruction
	 * @param prototype prototype of the instruction
	 * @param memBuffer buffer containing the bytes for the instruction
	 * @param procContext processor state information during disassembly
	 * @throws AddressOverflowException if code unit length causes wrap within space
	 */
	public PseudoInstruction(AddressFactory addrFactory, Address addr,
			InstructionPrototype prototype, MemBuffer memBuffer, ProcessorContext procContext)
			throws AddressOverflowException {
		super(null, addr, prototype.getLength(), getByteCacheSize(prototype), memBuffer);
		this.instrProto = prototype;
		this.procContext = procContext;
		this.addrFactory = addrFactory;
	}

	/**
	 * Construct a new PseudoInstruction without a program (flow override not supported).
	 * 
	 * @param addr address of the instruction
	 * @param prototype prototype of the instruction
	 * @param memBuffer buffer containing the bytes for the instruction
	 * @param procContext processor state information during disassembly
	 * @throws AddressOverflowException if code unit length causes wrap within space
	 */
	public PseudoInstruction(Address addr, InstructionPrototype prototype, MemBuffer memBuffer,
			ProcessorContext procContext) throws AddressOverflowException {
		super(addr, prototype.getLength(), getByteCacheSize(prototype), memBuffer);
		this.instrProto = prototype;
		this.procContext = procContext;
	}

	private static int getByteCacheSize(InstructionPrototype prototype) {
		// NOTE: in certain cases this may not cache enough if slot size was
		// specified as a minimum and not actual
		int length = prototype.getLength();

		int delaySlotByteCount = prototype.getDelaySlotByteCount();
		if (delaySlotByteCount == 1) {
			// Assume this is a minimum delay slot size and cache enough for one 
			// more instruction of the same size.
			delaySlotByteCount = length;
		}
		length += delaySlotByteCount;

		// Factor in optional inst_next2 use
		int next2Length = 0;
		if (prototype.hasNext2Dependency()) {
			Language language = prototype.getLanguage();
			OptionalInt maxNextInstructionLength = language.getMaximumInstructionLength();
			if (maxNextInstructionLength.isPresent()) {
				// next instruction length based upon language specified property
				next2Length = maxNextInstructionLength.getAsInt();
			}
			else {
				// next instruction length assumed to be the same as the current instruction 
				next2Length = length;
			}
		}
		length += next2Length;

		// Sleigh utilizes 4-byte (int) chunks when evaluating patterns
		// make sure we have enough bytes to give out for any valid offset
		// within the instruction
		return length + 3;
	}

	/**
	 * Return the byte value repeated for all bytes within this instruction or null if byte values
	 * vary.
	 * 
	 * @return repeated byte value or null if bytes vary
	 */
	public synchronized Byte getRepeatedByte() {
		refreshIfNeeded();
		byte b0 = bytes[0];
		if (length == 1) {
			return b0;
		}
		for (int i = 1; i < length; i++) {
			if (bytes[i] != b0) {
				return null;
			}
		}
		return b0;
	}

	@Override
	public int getParsedLength() {
		// length-override is not supported
		return getLength();
	}

	@Override
	public byte[] getParsedBytes() throws MemoryAccessException {
		// length-override is not supported
		return getBytes();
	}

	@Override
	public Register getBaseContextRegister() {
		return procContext.getBaseContextRegister();
	}

	@Override
	public Reference[] getOperandReferences(int opIndex) {
		Address toAddr = instrProto.getAddress(opIndex, this);
		if (toAddr == null) {
			return emptyMemRefs;
		}
		Reference ref = new MemReferenceImpl(address, toAddr, getOperandRefType(opIndex),
			SourceType.DEFAULT, opIndex, false);
		Reference refArray[] = new Reference[1];
		refArray[0] = ref;
		return refArray;
	}

	@Override
	public boolean equals(Object obj) {

		if (obj == null) {
			return false;
		}
		if (obj == this) {
			return true;
		}

		if (getClass() != obj.getClass()) {
			return false;
		}

		PseudoInstruction cu = (PseudoInstruction) obj;
		if (hash != cu.hash) {
			return false;
		}

		if (!address.equals(cu.address)) {
			return false;
		}

		return instrProto.equals(obj);
	}

	@Override
	public InstructionPrototype getPrototype() {
		return instrProto;
	}

	@Override
	public String getMnemonicString() {
		return instrProto.getMnemonic(this);
	}

	@Override
	public int getNumOperands() {
		return instrProto.getNumOperands();
	}

	@Override
	public Address getAddress(int opIndex) {
		if (opIndex < 0) {
			return null;
		}
		int opType = instrProto.getOpType(opIndex, this);

		if (OperandType.isAddress(opType)) {
			return instrProto.getAddress(opIndex, this);
		}

		return null;
	}

	@Override
	public Scalar getScalar(int opIndex) {
		if (opIndex < 0) {
			return null;
		}
		return instrProto.getScalar(opIndex, this);
	}

	@Override
	public Register getRegister(int opIndex) {
		if (opIndex < 0) {
			return null;
		}
		return instrProto.getRegister(opIndex, this);
	}

	@Override
	public Object[] getOpObjects(int opIndex) {
		if (opIndex < 0) {
			return new Object[0];
		}
		return instrProto.getOpObjects(opIndex, this);
	}

	@Override
	public Object[] getInputObjects() {
		return instrProto.getInputObjects(this);
	}

	@Override
	public Object[] getResultObjects() {
		return instrProto.getResultObjects(this);
	}

	@Override
	public String getDefaultOperandRepresentation(int opIndex) {
		List<Object> opList = getDefaultOperandRepresentationList(opIndex);
		if (opList == null) {
			return "<UNSUPPORTED>";
		}
		StringBuffer strBuf = new StringBuffer();
		for (Object opElem : opList) {
			if (opElem instanceof Address) {
				Address opAddr = (Address) opElem;
				strBuf.append("0x");
				strBuf.append(opAddr.toString(false));
			}
			else {
				strBuf.append(opElem.toString());
			}
		}
		return strBuf.toString();
	}

	@Override
	public List<Object> getDefaultOperandRepresentationList(int opIndex) {
		return instrProto.getOpRepresentationList(opIndex, this);
	}

	@Override
	public int getOperandType(int opIndex) {
		int optype = instrProto.getOpType(opIndex, this);
		return optype;
	}

	@Override
	public RefType getOperandRefType(int opIndex) {
		// The operand Ref Type now depends on the flow Type of
		//  the instruction.  For instructions with flow,
		//  only those operands that refer to code should be given
		//  the flow type of the instruction.  Any other memory
		//  reference should be given a DataRef type.

		int opType = instrProto.getOpType(opIndex, this);

		if (OperandType.isDataReference(opType)) {
			if (getFlowType().isComputed()) {
				if (OperandType.isIndirect(opType)) {
					return RefType.INDIRECTION;
				}
			}
			return RefType.DATA;
		}

		// code references get
		if (OperandType.isCodeReference(opType)) {
			return instrProto.getFlowType(this);
		}

		return RefType.DATA;
	}

	@Override
	public Address getFallThrough() {
		if (fallThroughOverride == null) {
			return getDefaultFallThrough();
		}
		if (fallThroughOverride != Address.NO_ADDRESS) {
			return fallThroughOverride;
		}
		return null;
	}

	@Override
	public int getDefaultFallThroughOffset() {
		return instrProto.getFallThroughOffset(this);
	}

	@Override
	public Address getDefaultFallThrough() {
		// TODO: This used to be in proto.  We need to override the proto's flowtype.
		//       This could be pushed back into the proto if we could override the flowType there.
		FlowType myFlowType = getFlowType();
		if (myFlowType.hasFallthrough()) {
			try {
				return address.addNoWrap(instrProto.getFallThroughOffset(this));
			}
			catch (AddressOverflowException e) {
				// ignore
			}
		}
		return null;
	}

	@Override
	public Address getFallFrom() {
		throw new UnsupportedOperationException("Not supported by pseduo instruction");
//		// check if the instruction before this is in a delay slot
//		// If it is then back up until hit an instruction that claims
//		// to be a delay slot instruction that is not in a delay slot
//		// itself.
//		Instruction instr = this;
//		do {
//			// skip past delay slot instructions
//			instr = program.getListing().getInstructionContaining(instr.getMinAddress().previous());
//		}
//		while (instr != null && instr.isInDelaySlot());
//		if (instr == null) {
//			return null;
//		}
//
//		// If this instruction is in a delay slot,
//		// it is assumed to always fall from the delay-slotted
//		// instruction regardless of its fall-through
//		if (this.isInDelaySlot()) {
//			return instr.getMinAddress();
//		}
//
//		// No delay slot, but check if the instruction falls into this one.
//		Address fallAddr = instr.getFallThrough();
//		if (fallAddr != null && fallAddr.equals(address)) {
//			return instr.getMinAddress();
//		}
//		return null;
	}

	@Override
	public Address[] getFlows() {
		return getDefaultFlows();
	}

	@Override
	public Address[] getDefaultFlows() {
		Address[] flows = instrProto.getFlows(this);
		if (flowOverride == FlowOverride.RETURN && flows.length == 1) {
			return EMPTY_ADDR_ARRAY;
		}
		return flows;
	}

	@Override
	public FlowType getFlowType() {
		return FlowOverride.getModifiedFlowType(instrProto.getFlowType(this), flowOverride);
	}

	@Override
	public PcodeOp[] getPcode() {
		return instrProto.getPcode(this, null);
	}

	@Override
	public PcodeOp[] getPcode(boolean includeOverrides) {
		if (!includeOverrides || addrFactory == null) {
			return instrProto.getPcode(this, null);
		}
		return instrProto.getPcode(this, new InstructionPcodeOverride(this));
	}

	@Override
	public PcodeOp[] getPcode(int opIndex) {
		return instrProto.getPcode(this, opIndex);
	}

	@Override
	public int getDelaySlotDepth() {
		return instrProto.getDelaySlotDepth(this);
	}

	@Override
	public boolean isInDelaySlot() {
		return instrProto.isInDelaySlot();
	}

	@Override
	public Instruction getNext() {
		if (program == null) {
			return null;
		}
		return program.getListing().getInstructionAfter(address);
	}

	@Override
	public Instruction getPrevious() {
		if (block != null && !block.getStartAddress().equals(address)) {
			// Attempt to get previous instruction if block available
			// There is no guarantee that getPrevious will work.
			// This is done to support the method for certain languages:
			// ParallelInstructionLanguageHelper.executesInParallelWithPreviousInstruction
			// Such languages are responsible for keeping parallel instructions
			// within a single disassembly block.
			Address addr = address.previous();
			if (addr != null) {
				Instruction instr = block.findFirstIntersectingInstruction(addr, addr);
				if (instr != null) {
					return instr;
				}
			}
		}
		if (program == null) {
			return null;
		}
		return program.getListing().getInstructionBefore(address);
	}

	@Override
	public String toString() {
		StringBuffer stringBuffer = new StringBuffer();
		stringBuffer.append(getMnemonicString());

		int n = getNumOperands();
		String sep = getSeparator(0);
		if (sep != null || n != 0) {
			stringBuffer.append(' ');
		}
		if (sep != null) {
			stringBuffer.append(sep);
		}

		for (int i = 0; i < n; i++) {
			stringBuffer.append(getDefaultOperandRepresentation(i));
			sep = getSeparator(i + 1);
			if (sep != null) {
				stringBuffer.append(sep);
			}
		}
		return stringBuffer.toString();
	}

	@Override
	public void clearFallThroughOverride() {
		fallThroughOverride = null;
	}

	@Override
	public void setFallThrough(Address addr) {
		if (SystemUtilities.isEqual(addr, getDefaultFallThrough())) {
			fallThroughOverride = null;
		}
		else if (addr == null) {
			fallThroughOverride = Address.NO_ADDRESS;
		}
		else {
			fallThroughOverride = addr;
		}
	}

	@Override
	public FlowOverride getFlowOverride() {
		return flowOverride;
	}

	@Override
	public void setFlowOverride(FlowOverride flowOverride) {
		this.flowOverride = flowOverride != null ? flowOverride : FlowOverride.NONE;
	}

	@Override
	public void setLengthOverride(int length) {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean isLengthOverridden() {
		return false;
	}

	@Override
	public boolean isFallThroughOverridden() {
		return fallThroughOverride != null;
	}

	@Override
	public boolean hasFallthrough() {
		if (isFallThroughOverridden()) {
			return getFallThrough() != null; // fall-through destination stored as reference
		}
		return getFlowType().hasFallthrough();
	}

	@Override
	public boolean isFallthrough() {
		if (!getFlowType().isFallthrough()) {
			return false;
		}
		return hasFallthrough();
	}

	@Override
	public String getSeparator(int opIndex) {
		return instrProto.getSeparator(opIndex);
	}

	@Override
	public boolean hasValue(Register register) {
		return procContext.hasValue(register);
	}

	@Override
	public BigInteger getValue(Register register, boolean signed) {
		return procContext.getValue(register, signed);
	}

	@Override
	public RegisterValue getRegisterValue(Register register) {
		return procContext.getRegisterValue(register);
	}

	@Override
	public Register getRegister(String name) {
		return procContext.getRegister(name);
	}

	@Override
	public List<Register> getRegisters() {
		return procContext.getRegisters();
	}

	@Override
	public void setValue(Register register, BigInteger value) throws ContextChangeException {
		procContext.setValue(register, value);
	}

	@Override
	public void setRegisterValue(RegisterValue value) throws ContextChangeException {
		procContext.setRegisterValue(value);
	}

	@Override
	public void clearRegister(Register register) throws ContextChangeException {
		procContext.clearRegister(register);
	}

	@Override
	public ProcessorContextView getProcessorContext() {
		return this;
	}

	@Override
	public MemBuffer getMemBuffer() {
		return this;
	}

	@Override
	public ParserContext getParserContext() throws MemoryAccessException {
		if (parserContext == null) {
			parserContext = instrProto.getParserContext(this, this);
		}
		return parserContext;
	}

	@Override
	public ParserContext getParserContext(Address instructionAddress)
			throws UnknownContextException, MemoryAccessException {
		if (instructionAddress.equals(address)) {
			return getParserContext();
		}
		if (block == null) {
			try {
				return instrProto.getPseudoParserContext(instructionAddress, getMemBuffer(),
					procContext);
			}
			catch (InsufficientBytesException e) {
				throw new UnknownContextException(
					"Insufficient bytes when generating pseudo-ParserContext for instruction at: " +
						instructionAddress);
			}
			catch (UnknownInstructionException e) {
				throw new UnknownContextException(
					"Could not generate pseudo-ParserContext because of unknown instruction at: " +
						instructionAddress);
			}
		}
		Instruction instr = block.getInstructionAt(instructionAddress);
		if (instr instanceof PseudoInstruction) {
			return ((PseudoInstruction) instr).getParserContext();
		}
		if (instr == null) {
			throw new UnknownContextException("Block does not contain cross-build instruction: " +
				getAddress() + " -> " + instructionAddress);
		}
		throw new AssertException(
			"Unexpected instruction class contained within block: " + instr.getClass().getName());
	}

	@Override
	public InstructionContext getInstructionContext() {
		return this;
	}

	public void setInstructionBlock(InstructionBlock bl) {
		block = bl;
	}
}
