/*
Copyright 2011-2016 Google Inc. All Rights Reserved.

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 com.google.security.zynamics.reil.translators.x86;

import com.google.security.zynamics.reil.OperandSize;
import com.google.security.zynamics.reil.ReilHelpers;
import com.google.security.zynamics.reil.ReilInstruction;
import com.google.security.zynamics.reil.translators.IInstructionTranslator;
import com.google.security.zynamics.reil.translators.ITranslationEnvironment;
import com.google.security.zynamics.reil.translators.InternalTranslationException;
import com.google.security.zynamics.reil.translators.TranslationHelpers;
import com.google.security.zynamics.reil.translators.TranslationResult;
import com.google.security.zynamics.reil.translators.TranslationResultType;
import com.google.security.zynamics.zylib.disassembly.IInstruction;
import com.google.security.zynamics.zylib.disassembly.IOperandTree;

import java.util.List;

/**
 * Translates SHRD instructions to REIL code.
 */
public class ShrdTranslator implements IInstructionTranslator {

  private String getAllButMask(final OperandSize extendedSize, final OperandSize combinedSize) {
    if (extendedSize == OperandSize.QWORD) {
      return String.valueOf(0xFFFFFFFF00000000L);
    } else {
      return String.valueOf(TranslationHelpers.getAllButMask(extendedSize, combinedSize));
    }
  }

  /**
   * Translates a SHRD instruction to REIL code.
   *
   * @param environment A valid translation environment.
   * @param instruction The SHRD instruction to translate.
   * @param instructions The generated REIL code will be added to this list
   *
   * @throws InternalTranslationException if any of the arguments are null the passed instruction is
   *         not an SHRD instruction
   */
  @Override
  public void translate(final ITranslationEnvironment environment, final IInstruction instruction,
      final List<ReilInstruction> instructions) throws InternalTranslationException {
    TranslationHelpers.checkTranslationArguments(environment, instruction, instructions, "shrd");
    if (instruction.getOperands().size() != 3) {
      throw new InternalTranslationException(
          "Error: Argument instruction is not a shrd instruction (invalid number of operands)");
    }

    // Distinct behaviour:
    // 1. Shift value is 0
    // 2. Shift value is 1
    // 3. Shift value is too large
    // 4. Shift value has right size

    final long baseOffset = instruction.getAddress().toLong() * 0x100;
    long offset = baseOffset;

    final List<? extends IOperandTree> operands = instruction.getOperands();

    // Load target value
    final TranslationResult firstResult =
        Helpers.translateOperand(environment, offset, operands.get(0), true);
    instructions.addAll(firstResult.getInstructions());

    offset = baseOffset + instructions.size();

    final TranslationResult secondResult =
        Helpers.translateOperand(environment, offset, operands.get(1), true);
    instructions.addAll(secondResult.getInstructions());

    offset = baseOffset + instructions.size();

    // Load shift value
    final TranslationResult thirdResult =
        Helpers.translateOperand(environment, offset, operands.get(2), true);
    instructions.addAll(thirdResult.getInstructions());

    final String truncatedShiftValue = environment.getNextVariableString();
    final String truncatedShiftValueZero = environment.getNextVariableString();

    offset = baseOffset + instructions.size();

    // Test whether the shift value is zero (leads to no operation)

    instructions.add(ReilHelpers.createAnd(offset++, thirdResult.getSize(),
        thirdResult.getRegister(), OperandSize.BYTE, "31", OperandSize.BYTE, truncatedShiftValue));
    instructions.add(ReilHelpers.createBisz(offset++, OperandSize.BYTE, truncatedShiftValue,
        OperandSize.BYTE, truncatedShiftValueZero));

    final int jumpInsertIndex1 = (int) (offset - baseOffset);
    offset++;
    // Placeholder for jcc
    // instructions.add(ReilHelpers.createJcc(offset++, OperandSize.BYTE, truncatedShiftValue,
    // OperandSize.ADDRESS, endAddress));

    // If a shift happens, AF is undefined
    instructions.add(ReilHelpers.createUndef(offset++, OperandSize.BYTE, Helpers.AUXILIARY_FLAG));

    // Test whether the shift value is too large (leads to an undefined result)

    final String sizeSubtractionResult = environment.getNextVariableString();
    final String sizeMaskingResult = environment.getNextVariableString();

    instructions.add(ReilHelpers.createSub(offset++, OperandSize.BYTE,
        String.valueOf(firstResult.getSize().getBitSize()), OperandSize.BYTE, truncatedShiftValue,
        OperandSize.WORD, sizeSubtractionResult));
    instructions.add(ReilHelpers.createAnd(offset++, OperandSize.WORD, sizeSubtractionResult,
        OperandSize.WORD, String.valueOf(0x8000), OperandSize.WORD, sizeMaskingResult));
    final int jumpInsertIndex2 = (int) (offset - baseOffset);
    offset++;
    // Placeholder for createJcc(offset++, OperandSize.WORD, sizeMaskingResult, OperandSize.ADDRESS,
    // largeValueHandler));

    // From here on, we know that the shift value is valid

    final String shiftedSecondInput = environment.getNextVariableString();
    final String shiftMask = String.valueOf(firstResult.getSize().getBitSize());
    final OperandSize combinedSize = TranslationHelpers.getNextSize(firstResult.getSize());
    final OperandSize extendedSize = TranslationHelpers.getNextSize(combinedSize);
    final String combinedSource = environment.getNextVariableString();
    final String shiftedResult = environment.getNextVariableString();

    // Combine the operands into one operand

    instructions.add(ReilHelpers.createBsh(offset++, secondResult.getSize(),
        secondResult.getRegister(), secondResult.getSize(), shiftMask, combinedSize,
        shiftedSecondInput));
    instructions.add(ReilHelpers.createOr(offset++, combinedSize, shiftedSecondInput,
        firstResult.getSize(), firstResult.getRegister(), combinedSize, combinedSource));

    // Negate the shift-mask

    final String negatedShiftMask = environment.getNextVariableString();
    final String truncatedNegatedShiftMask = environment.getNextVariableString();

    instructions.add(ReilHelpers.createSub(offset++, OperandSize.BYTE, "0", OperandSize.BYTE,
        truncatedShiftValue, OperandSize.WORD, negatedShiftMask));
    instructions.add(ReilHelpers.createAnd(offset++, OperandSize.WORD, negatedShiftMask,
        OperandSize.BYTE, "255", OperandSize.BYTE, truncatedNegatedShiftMask));

    // Less-1 shift to determine the CF

    final String incShiftMaskNeg = environment.getNextVariableString();
    final String decResult = environment.getNextVariableString();

    instructions.add(ReilHelpers.createAdd(offset++, OperandSize.BYTE, truncatedNegatedShiftMask,
        OperandSize.BYTE, "1", OperandSize.WORD, incShiftMaskNeg));
    instructions.add(ReilHelpers.createBsh(offset++, combinedSize, combinedSource,
        OperandSize.WORD, incShiftMaskNeg, combinedSize, decResult));
    instructions.add(ReilHelpers.createAnd(offset++, combinedSize, decResult, OperandSize.BYTE,
        "1", OperandSize.BYTE, Helpers.CARRY_FLAG));

    // Do the shift

    instructions.add(ReilHelpers.createBsh(offset++, combinedSize, combinedSource,
        OperandSize.BYTE, truncatedNegatedShiftMask, extendedSize, shiftedResult));

    // Isolate the result

    final String isolationMask = getAllButMask(combinedSize, firstResult.getSize());
    final String isolationResult = environment.getNextVariableString();
    final String shiftedIsolationResult = environment.getNextVariableString();

    instructions.add(ReilHelpers.createAnd(offset++, extendedSize, shiftedResult, combinedSize,
        isolationMask, combinedSize, isolationResult));
    instructions.add(ReilHelpers.createBsh(offset++, combinedSize, isolationResult,
        OperandSize.BYTE, "-" + shiftMask, firstResult.getSize(), shiftedIsolationResult));

    // Set the flags (TODO: Parity)
    instructions.add(ReilHelpers.createBisz(offset++, firstResult.getSize(),
        shiftedIsolationResult, OperandSize.BYTE, Helpers.ZERO_FLAG));
    Helpers.generateSignFlagCode(environment, offset, shiftedIsolationResult,
        firstResult.getSize(), instructions);

    offset = baseOffset + instructions.size() + 2; /** JCC placeholder **/

    // final String tempCf = environment.getNextVariableString();

    // Store the original input value in a temp register for OF calculation

    final String tempInput = environment.getNextVariableString();

    instructions.add(ReilHelpers.createStr(offset++, firstResult.getSize(),
        firstResult.getRegister(), firstResult.getSize(), tempInput));

    // Write the result back
    Helpers.writeBack(environment, offset, operands.get(0), shiftedIsolationResult,
        firstResult.getSize(), firstResult.getAddress(), firstResult.getType(), instructions);

    offset = baseOffset + instructions.size() + 2; /** JCC placeholder **/

    // Test whether the shift value is 1

    final String shiftValueOne = environment.getNextVariableString();

    instructions.add(ReilHelpers.createSub(offset++, OperandSize.BYTE, truncatedShiftValue,
        OperandSize.BYTE, "1", OperandSize.WORD, shiftValueOne));

    final int jumpInsertIndex3 = (int) (offset - baseOffset);
    offset++;
    // Placeholder for createJcc(offset++, OperandSize.WORD, shiftValueOne, OperandSize.ADDRESS,
    // notOneHandler));

    // The shift-value was 1 => Calculate the OF

    final String xoredMsb = environment.getNextVariableString();
    final String maskedMsb = environment.getNextVariableString();
    final long msbMask = TranslationHelpers.getMsbMask(firstResult.getSize());
    final long msbShift = TranslationHelpers.getShiftMsbLsbMask(firstResult.getSize());

    instructions.add(ReilHelpers.createXor(offset++, firstResult.getSize(), tempInput,
        firstResult.getSize(), shiftedIsolationResult, firstResult.getSize(), xoredMsb));
    instructions.add(ReilHelpers.createAnd(offset++, firstResult.getSize(), xoredMsb,
        OperandSize.BYTE, String.valueOf(msbMask), OperandSize.BYTE, maskedMsb));
    instructions.add(ReilHelpers.createBsh(offset++, firstResult.getSize(), maskedMsb,
        OperandSize.BYTE, String.valueOf(msbShift), OperandSize.BYTE, Helpers.OVERFLOW_FLAG));

    final int jumpInsertIndex4 = (int) (offset - baseOffset);
    offset++;
    // Placeholder for createJcc(offset++, OperandSize.BYTE, "1", OperandSize.ADDRESS, endAddress));

    // Handle mod-value too large

    final String largeValueHandler =
        String.format("%d.%d", instruction.getAddress().toLong(), offset - baseOffset);

    final IOperandTree inputOperand = operands.get(0);

    if (firstResult.getType() == TranslationResultType.REGISTER) {
      final String operand = Helpers.getLeafValue(inputOperand.getRootNode());

      final String undefRegister =
          Helpers.getOperandSize(inputOperand) == environment.getArchitectureSize() ? operand
              : Helpers.getParentRegister(operand);

      instructions.add(ReilHelpers.createUndef(offset++, environment.getArchitectureSize(),
          undefRegister));
    }

    instructions.add(ReilHelpers.createUndef(offset++, OperandSize.BYTE, Helpers.CARRY_FLAG));
    instructions.add(ReilHelpers.createUndef(offset++, OperandSize.BYTE, Helpers.SIGN_FLAG));
    instructions.add(ReilHelpers.createUndef(offset++, OperandSize.BYTE, Helpers.ZERO_FLAG));
    instructions.add(ReilHelpers.createUndef(offset++, OperandSize.BYTE, Helpers.PARITY_FLAG));

    // Handle shift value > 1 || shift value too large

    final String notOneHandler =
        String.format("%d.%d", instruction.getAddress().toLong(), offset - baseOffset);
    instructions.add(ReilHelpers.createUndef(offset++, OperandSize.BYTE, Helpers.OVERFLOW_FLAG));

    // The End

    final String jmpGoalEnd =
        String.format("%d.%d", instruction.getAddress().toLong(), offset - baseOffset);

    instructions.add(ReilHelpers.createNop(offset++));

    instructions.add(jumpInsertIndex1, ReilHelpers.createJcc(baseOffset + jumpInsertIndex1,
        OperandSize.BYTE, truncatedShiftValueZero, OperandSize.ADDRESS, jmpGoalEnd));
    instructions.add(jumpInsertIndex2, ReilHelpers.createJcc(baseOffset + jumpInsertIndex2,
        OperandSize.WORD, sizeMaskingResult, OperandSize.ADDRESS, largeValueHandler));
    instructions.add(jumpInsertIndex3, ReilHelpers.createJcc(baseOffset + jumpInsertIndex3,
        OperandSize.WORD, shiftValueOne, OperandSize.ADDRESS, notOneHandler));
    instructions.add(jumpInsertIndex4, ReilHelpers.createJcc(baseOffset + jumpInsertIndex4,
        OperandSize.BYTE, "1", OperandSize.ADDRESS, jmpGoalEnd));
  }
}
