/**
  * In case the method is still under-call then put the record of the method fields where get was
  * used in a seperate map. When a method any next instruction is called again then the record is
  * pop from the map and corresponding instructions are made dirty.
  *
  * <p>This method create that record of method fields.
  *
  * @param methodCallInfo
  * @param instrDataSettingInstr
  */
 private void handlePutRecalls(
     MethodCallInfo methodCallInfo, GCInstruction instrDataSettingInstr, OperandStack stack) {
   if (true) return;
   try {
     /**
      * 1. Firstly get from input instructions the uniqueId for corresponding data reterival
      * instructions. 2. Subsequently, add those instructions in the two different maps depending
      * upon if the method is still under call or not.
      */
     ControllerForFieldInstr contr = ControllerForFieldInstr.getInstanceOf();
     HashSet<Integer> uniqueIdForDataRetrivalInstr = new HashSet<Integer>();
     if (instrDataSettingInstr.getOpCode() != JavaInstructionsOpcodes.AASTORE) {
       int cpIndex = instrDataSettingInstr.getOperandsData().intValueUnsigned();
       uniqueIdForDataRetrivalInstr.add(cpIndex);
     } else {
       uniqueIdForDataRetrivalInstr = getAAStoreNewIds(stack);
     }
     /**
      * following are the all getField or getStatic instruction for the given constant pool index.
      */
     HashSet<FieldRecord> getFieldOrStateRecordSet =
         contr.getRecords(
             uniqueIdForDataRetrivalInstr,
             instrDataSettingInstr.getOpCode() == JavaInstructionsOpcodes.AASTORE);
     /**
      * Now we iterate from all of those instructions and put them in two different maps
      * depenending up if their method is still under call or not.
      */
     Iterator<FieldRecord> fieldRectIt = getFieldOrStateRecordSet.iterator();
     while (fieldRectIt.hasNext()) {
       FieldRecord fieldRec = fieldRectIt.next();
       HashSet<FunctionStateKey> allTheMethodCallsForTheField = fieldRec.getStateKeys();
       Iterator<FunctionStateKey> methodCallIt = allTheMethodCallsForTheField.iterator();
       while (methodCallIt.hasNext()) {
         FunctionStateKey stateKey = methodCallIt.next();
         GCDataFlowAnalyzer dataFlowAnalyzer = GCDataFlowAnalyzer.getInstanceOf();
         HashSet<FunctionStateKey> allMethodInExecution = dataFlowAnalyzer.getMethodsInExecution();
         String methodString = oracle.getMethodOrFieldString(stateKey.getMethod());
         long instrId = fieldRec.getInstr().getInstructionId();
         if (allMethodInExecution.contains(stateKey)) {
           putInMap(fieldsToSetDirtyInUnderCallMethod, stateKey, fieldRec);
         } else {
           putInMap(fieldsToSetDirtyAfterMethodCalled, stateKey, fieldRec);
         }
       }
     }
   } catch (Exception d) {
     d.printStackTrace();
     Miscellaneous.exit();
   }
 }
  /**
   * Call it BEFORE executing an instruction virtually.
   *
   * @param instr
   * @param stack
   * @param localVar
   */
  private void addRecord(
      FunctionStateKey stateKey,
      GCInstruction instr,
      GCOperandStack stack,
      GCLocalVariables localVar) {

    MethodInfo method = instr.getMethod();
    String methodStr = oracle.getMethodOrFieldString(method);
    if (methodStr.contains("jvmTestCases.Main.fooGetOnly")) {}
    debugPrint("\n\n", "***** adding record *****");
    debugPrint("method Str=", methodStr);
    debugPrint("instr =", instr);
    HashSet<Integer> unqiueForField = new HashSet<Integer>();
    if (instr.getOpCode() != JavaInstructionsOpcodes.AALOAD) {
      int cpIndex = instr.getOperandsData().intValueUnsigned();
      unqiueForField.add(cpIndex);
    } else {
      unqiueForField = getAALOADNewIds(stack);
    }
    Iterator<Integer> it = unqiueForField.iterator();
    while (it.hasNext()) {
      contrForField.addRecord(stateKey, instr, it.next());
    }
  }
/**
 * Title:
 *
 * <p>Description:
 *
 * @author Faisal Aslam
 * @version 1.0
 */
public class OrderManagerForFieldInstrs implements FSKAsHashKeyInterface {

  private static final OrderManagerForFieldInstrs myObj = new OrderManagerForFieldInstrs();
  private ControllerForFieldInstr contrForField = ControllerForFieldInstr.getInstanceOf();
  /** value = HashSet<FieldRecord> */
  private HashMap<FunctionStateKey, FunctionStateKeyMapValue> fieldsToSetDirtyInUnderCallMethod =
      new HashMap<FunctionStateKey, FunctionStateKeyMapValue>();

  private HashMap<FunctionStateKey, FunctionStateKeyMapValue> fieldsToSetDirtyAfterMethodCalled =
      new HashMap<FunctionStateKey, FunctionStateKeyMapValue>();
  private Oracle oracle = Oracle.getInstanceOf();
  private static boolean shouldDebugPrint = false;

  public static void debugPrint(Object obj1, Object obj2) {
    if (shouldDebugPrint) {
      System.out.println(obj1 + "" + obj2);
    }
  }

  private OrderManagerForFieldInstrs() {}

  public void updateHashMapUsingFSK() {
    FunctionStateKeyMapValue.update(fieldsToSetDirtyInUnderCallMethod);
    FunctionStateKeyMapValue.update(fieldsToSetDirtyAfterMethodCalled);
  }

  public static OrderManagerForFieldInstrs getInstanceOf() {
    return myObj;
  }

  /**
   * This method is called when GetStatic or GetField or AALoad. It adds record of those
   * instructions so that they can be made dirty when putfield or putstatic is used for the same
   * field.
   *
   * @param method
   * @param callingParams
   * @param instr
   * @param stack
   * @param localVar
   */
  public void addRecord(
      MethodInfo method,
      Vector callingParams,
      GCInstruction instr,
      GCOperandStack stack,
      GCLocalVariables localVar) {
    if (!isFieldIsObjectBased(instr)
        || (instr.getOpCode() != JavaInstructionsOpcodes.GETSTATIC
            && instr.getOpCode() != JavaInstructionsOpcodes.GETFIELD
            && instr.getOpCode() != JavaInstructionsOpcodes.AALOAD)) {
      return;
    }
    addRecord(new FunctionStateKey(method, callingParams), instr, stack, localVar);
  }

  /**
   * Should be called Before virtual execution of instructions putfield and putstatic so that all
   * corresponding getFields are set dirty. Similarly should also be called before virtual execution
   * of instructions AASTORE so that corresponding AALOAD are set dirty.
   *
   * @param method
   * @param callingParms
   * @param instr
   * @param stack
   * @param localVariables
   */
  public void fixOrderRelatedInstrs(
      MethodInfo method,
      Vector callingParms,
      GCInstruction instr,
      GCOperandStack stack,
      GCLocalVariables localVariables) {
    if (!isFieldIsObjectBased(instr)) {
      return;
    }
    String methodStr = oracle.getMethodOrFieldString(method);
    if (instr.getOpCode() == JavaInstructionsOpcodes.PUTSTATIC
        || instr.getOpCode() == JavaInstructionsOpcodes.PUTFIELD
        || instr.getOpCode() == JavaInstructionsOpcodes.AASTORE) {
      GCType type = (GCType) stack.peep();
      if (!type.isReference()) {
        return;
      }
      HashSet<TTReference> refSet = type.getReferences();
      if (refSet.size() == 1) {
        TTReference ref = refSet.iterator().next();
        if (ref.getClassThisPointer() == Type.NULL || ref.getNewId() < 0) {
          return;
        }
      }
      debugPrint("\n\n++++++++++ ", "fix-Order");
      debugPrint("method Str =", oracle.getMethodOrFieldString(method));
      debugPrint("instr =", instr);
      handlePutRecalls(new MethodCallInfo(method, callingParms), instr, stack);
    }
  }

  private void putInMap(
      HashMap<FunctionStateKey, FunctionStateKeyMapValue> map,
      FunctionStateKey callInfo,
      FieldRecord getFieldRecord) {

    FunctionStateKeyMapValue specialValue = map.get(callInfo);
    HashSet<FieldRecord> value = null;
    if (specialValue == null) {
      value = new HashSet<FieldRecord>();
      specialValue = new FunctionStateKeyMapValue(callInfo, value);
      map.put(callInfo, specialValue);
      value.add(getFieldRecord);
    } else {
      value = (HashSet<FieldRecord>) specialValue.getValue();
      value.add(getFieldRecord);
    }
  }

  public void removeForMapForDirtyGetFieldInstr(FunctionStateKey methodCallInfo) {
    fieldsToSetDirtyAfterMethodCalled.remove(methodCallInfo);
  }

  public HashMap<FunctionStateKey, FunctionStateKeyMapValue>
      getAfterCallMethodWithDirtyGetFieldInstr() {
    return fieldsToSetDirtyAfterMethodCalled;
  }

  public HashSet<FieldRecord> getAndRemoveUnderExecMethodWithDirtyGetFieldInstr(
      FunctionStateKey methodCallInfo) {
    FunctionStateKeyMapValue value = fieldsToSetDirtyInUnderCallMethod.remove(methodCallInfo);
    if (value == null) {
      return null;
    }
    return (HashSet<FieldRecord>) value.getValue();
  }

  /**
   * In case the method is still under-call then put the record of the method fields where get was
   * used in a seperate map. When a method any next instruction is called again then the record is
   * pop from the map and corresponding instructions are made dirty.
   *
   * <p>This method create that record of method fields.
   *
   * @param methodCallInfo
   * @param instrDataSettingInstr
   */
  private void handlePutRecalls(
      MethodCallInfo methodCallInfo, GCInstruction instrDataSettingInstr, OperandStack stack) {
    if (true) return;
    try {
      /**
       * 1. Firstly get from input instructions the uniqueId for corresponding data reterival
       * instructions. 2. Subsequently, add those instructions in the two different maps depending
       * upon if the method is still under call or not.
       */
      ControllerForFieldInstr contr = ControllerForFieldInstr.getInstanceOf();
      HashSet<Integer> uniqueIdForDataRetrivalInstr = new HashSet<Integer>();
      if (instrDataSettingInstr.getOpCode() != JavaInstructionsOpcodes.AASTORE) {
        int cpIndex = instrDataSettingInstr.getOperandsData().intValueUnsigned();
        uniqueIdForDataRetrivalInstr.add(cpIndex);
      } else {
        uniqueIdForDataRetrivalInstr = getAAStoreNewIds(stack);
      }
      /**
       * following are the all getField or getStatic instruction for the given constant pool index.
       */
      HashSet<FieldRecord> getFieldOrStateRecordSet =
          contr.getRecords(
              uniqueIdForDataRetrivalInstr,
              instrDataSettingInstr.getOpCode() == JavaInstructionsOpcodes.AASTORE);
      /**
       * Now we iterate from all of those instructions and put them in two different maps
       * depenending up if their method is still under call or not.
       */
      Iterator<FieldRecord> fieldRectIt = getFieldOrStateRecordSet.iterator();
      while (fieldRectIt.hasNext()) {
        FieldRecord fieldRec = fieldRectIt.next();
        HashSet<FunctionStateKey> allTheMethodCallsForTheField = fieldRec.getStateKeys();
        Iterator<FunctionStateKey> methodCallIt = allTheMethodCallsForTheField.iterator();
        while (methodCallIt.hasNext()) {
          FunctionStateKey stateKey = methodCallIt.next();
          GCDataFlowAnalyzer dataFlowAnalyzer = GCDataFlowAnalyzer.getInstanceOf();
          HashSet<FunctionStateKey> allMethodInExecution = dataFlowAnalyzer.getMethodsInExecution();
          String methodString = oracle.getMethodOrFieldString(stateKey.getMethod());
          long instrId = fieldRec.getInstr().getInstructionId();
          if (allMethodInExecution.contains(stateKey)) {
            putInMap(fieldsToSetDirtyInUnderCallMethod, stateKey, fieldRec);
          } else {
            putInMap(fieldsToSetDirtyAfterMethodCalled, stateKey, fieldRec);
          }
        }
      }
    } catch (Exception d) {
      d.printStackTrace();
      Miscellaneous.exit();
    }
  }

  /**
   * Call it BEFORE executing an instruction virtually.
   *
   * @param instr
   * @param stack
   * @param localVar
   */
  private void addRecord(
      FunctionStateKey stateKey,
      GCInstruction instr,
      GCOperandStack stack,
      GCLocalVariables localVar) {

    MethodInfo method = instr.getMethod();
    String methodStr = oracle.getMethodOrFieldString(method);
    if (methodStr.contains("jvmTestCases.Main.fooGetOnly")) {}
    debugPrint("\n\n", "***** adding record *****");
    debugPrint("method Str=", methodStr);
    debugPrint("instr =", instr);
    HashSet<Integer> unqiueForField = new HashSet<Integer>();
    if (instr.getOpCode() != JavaInstructionsOpcodes.AALOAD) {
      int cpIndex = instr.getOperandsData().intValueUnsigned();
      unqiueForField.add(cpIndex);
    } else {
      unqiueForField = getAALOADNewIds(stack);
    }
    Iterator<Integer> it = unqiueForField.iterator();
    while (it.hasNext()) {
      contrForField.addRecord(stateKey, instr, it.next());
    }
  }

  private HashSet<Integer> getAAStoreNewIds(OperandStack stack) {
    int currentSizeOfStack = stack.getCurrentSize();
    GCType type = (GCType) stack.get(currentSizeOfStack - 3);
    HashSet<Integer> toRet = type.getAllNewIds();
    toRet.remove(-1);
    return toRet;
  }

  private HashSet<Integer> getAALOADNewIds(OperandStack stack) {
    HashSet<Integer> toRet = new HashSet<Integer>();
    try {
      int currentSizeOfStack = stack.getNumberOfTypesInStack();
      GCType type = (GCType) stack.get(currentSizeOfStack - 2);
      toRet = type.getAllNewIds();
      toRet.remove(-1);
    } catch (Exception d) {
      d.printStackTrace();
      System.exit(1);
    }
    return toRet;
  }

  /**
   * check if the field saves an object.
   *
   * @param instr
   * @return
   */
  private boolean isFieldIsObjectBased(GCInstruction instr) {
    if (instr.getOpCode() == JavaInstructionsOpcodes.AALOAD
        || instr.getOpCode() == JavaInstructionsOpcodes.AASTORE) {
      return true;
    }
    if (instr.getOpCode() != JavaInstructionsOpcodes.PUTFIELD
        && instr.getOpCode() != JavaInstructionsOpcodes.GETFIELD
        && instr.getOpCode() != JavaInstructionsOpcodes.GETSTATIC
        && instr.getOpCode() != JavaInstructionsOpcodes.PUTSTATIC) {
      return false;
    }
    GlobalConstantPool pOne = GlobalConstantPool.getInstanceOf();

    int index = instr.getOperandsData().intValueUnsigned();
    FieldRefInfo fInfo = (FieldRefInfo) pOne.get(index, TagValues.CONSTANT_Fieldref);
    int nameAndTypeIndex = fInfo.getNameAndTypeIndex().intValueUnsigned();
    NameAndTypeInfo nAtInfo =
        (NameAndTypeInfo) pOne.get(nameAndTypeIndex, TagValues.CONSTANT_NameAndType);

    String description =
        ((UTF8Info)
                pOne.get(nAtInfo.getDescriptorIndex().intValueUnsigned(), TagValues.CONSTANT_Utf8))
            .convertBytes();
    VerificationFrameFactory frameFactory = VerificationPlaceHolder.getInstanceOf().getFactory();
    Type fieldType = frameFactory.createType();
    InitializeFirstInstruction.getType(description, 0, fieldType);
    return fieldType.isReference();
  }
}