/**
   * Generates a printable string that represents the given ActionScript 3 code.
   *
   * @param resolvedCode The ActionScript 3 code to turn into a string.
   * @param code The raw ActionScript 3 code.
   * @param prefix The prefix to write before every printed line.
   * @return The generated ActionScript 3 code string.
   */
  private static String getCodeText(
      final ResolvedCode resolvedCode, final AS3Code code, final String prefix) {

    if (code.getInstructions().size() == 0) {
      return "";
    }

    final StringBuilder sb = new StringBuilder();

    final AS3Instruction firstInstruction = code.getInstructions().get(0);

    final int firstOffset = firstInstruction.getBitPosition();

    for (final AS3Instruction instruction : code.getInstructions()) {

      final int absoluteOffset = instruction.getBitPosition() / 8;
      final int relativeOffset = absoluteOffset - firstOffset / 8;
      sb.append(prefix);
      sb.append(String.format("%08X %08X  ", absoluteOffset, relativeOffset));

      addInstructionText(sb, instruction, resolvedCode, firstOffset / 8);

      sb.append('\n');
    }

    return sb.toString();
  }
  /**
   * Adds the class header to the output.
   *
   * @param resolvedClass Provides the class information.
   * @param sb The string builder the output is appended to.
   */
  private static void addClassHeader(final ResolvedClass resolvedClass, final StringBuilder sb) {
    sb.append("class ");

    // Add the class name.
    final String className = ActionScript3Helpers.flattenNamespaceName(resolvedClass.getName());
    sb.append(className);

    // Add the opening class parentheses.
    sb.append("\n{\n");
  }
  /**
   * Resolves and adds an unsigned integer value instruction to the output.
   *
   * @param sb The string builder the output is appended to.
   * @param mnemonic The mnemonic of the instruction.
   * @param uintIndex The unsigned integer index.
   * @param code The code the instruction belongs to.
   */
  private static void addUInteger(
      final StringBuilder sb, final String mnemonic, final int uintIndex, final ResolvedCode code) {

    final Long integer = code.resolveUInteger(uintIndex - 1);

    sb.append(mnemonic);
    sb.append(" ");

    if (integer == null) {
      sb.append(String.format("??? [0x%08X]", uintIndex));
    } else {
      sb.append(String.format("%d [0x%08X]", integer, uintIndex));
    }
  }
  /**
   * Resolves and adds a namespace instruction to the output.
   *
   * @param sb The string builder the output is appended to.
   * @param mnemonic The mnemonic of the instruction.
   * @param namespace The namespace index.
   * @param code The code the instruction belongs to.
   */
  private static void addNamespace(
      final StringBuilder sb, final String mnemonic, final int namespace, final ResolvedCode code) {

    final String namespaceString = code.resolveNamespace(namespace);

    sb.append(mnemonic);
    sb.append(" ");

    if (namespaceString == null) {
      sb.append(String.format("??? [0x%08X]", namespace));
    } else {
      sb.append(String.format("\"%s\" [0x%08X]", namespaceString, namespace));
    }
  }
  /**
   * Resolves and adds a double value to the output.
   *
   * @param sb The string builder the output is appended to.
   * @param mnemonic The mnemonic of the instruction.
   * @param doubleIndex The double index.
   * @param code The code the instruction belongs to.
   */
  private static void addDouble(
      final StringBuilder sb,
      final String mnemonic,
      final int doubleIndex,
      final ResolvedCode code) {

    final Double dbl = code.resolveDouble(doubleIndex - 1);

    sb.append(mnemonic);
    sb.append(" ");

    if (dbl == null) {
      sb.append(String.format("??? [0x%08X]", doubleIndex));
    } else {
      sb.append(String.format("%f [0x%08X]", dbl, doubleIndex));
    }
  }
  /**
   * Resolves and adds a string value instruction to the output.
   *
   * @param sb The string builder the output is appended to.
   * @param mnemonic The mnemonic of the instruction.
   * @param stringIndex The string index.
   * @param code The code the instruction belongs to.
   */
  private static void addString(
      final StringBuilder sb,
      final String mnemonic,
      final int stringIndex,
      final ResolvedCode code) {

    final String string = code.resolveString(stringIndex - 1);

    sb.append(mnemonic);
    sb.append(" ");

    if (string == null) {
      sb.append(String.format("??? [0x%08X]", stringIndex));
    } else {
      sb.append(String.format("\"%s\" [0x%08X]", string, stringIndex));
    }
  }
  /**
   * Resolves and adds an multiname instruction to the output.
   *
   * @param sb The string builder the output is appended to.
   * @param mnemonic The mnemonic of the instruction.
   * @param multiName The multiname index.
   * @param code The code the instruction belongs to.
   */
  private static void addMultiname(
      final StringBuilder sb, final String mnemonic, final int multiName, final ResolvedCode code) {

    final String[] multinameString = code.resolveMultiname(multiName);

    sb.append(mnemonic);
    sb.append(" ");

    if (multinameString == null) {
      sb.append(String.format("??? [0x%08X]", multiName));
    } else {
      sb.append(
          String.format(
              "%s [0x%08X]",
              ActionScript3Helpers.flattenNamespaceName(multinameString), multiName));
    }
  }
  /**
   * Generates a printable string that represents all code in a given resolved ActionScript 3 code
   * snippet.
   *
   * @param code The resolved code to display.
   * @return The generated ActionScript 3 code string.
   */
  public static String getCodeText(final ResolvedCode code) {
    final StringBuilder sb = new StringBuilder();

    for (final ResolvedClass resolvedClass : code.getClasses()) {

      // Start out with the class definition.
      addClassHeader(resolvedClass, sb);

      boolean hadCode = false;

      // Start preparing all the methods for printing. Add the instance
      // constructor and the class constructor for printing.
      final ResolvedMethod staticConstructor = resolvedClass.getStaticConstructor();
      final ResolvedMethod constructor = resolvedClass.getConstructor();

      final List<ResolvedMethod> resolvedMethods = resolvedClass.getMethods();

      resolvedMethods.add(0, constructor);

      if (staticConstructor.getCode() != null) {
        // Only add the static constructor if it actually has code.
        resolvedMethods.add(0, staticConstructor);
      }

      final String className = ActionScript3Helpers.flattenNamespaceName(resolvedClass.getName());

      // Print the individual methods now.
      for (final ResolvedMethod resolvedMethod : resolvedMethods) {

        if (resolvedMethod != resolvedMethods.get(0)) {
          if (hadCode || resolvedMethod.getCode() != null) {
            sb.append("\n");
          }
        }

        hadCode = addMethod(code, resolvedMethod, className, sb);
      }

      addClassFooter(sb);
    }

    return sb.toString();
  }
 /**
  * Adds the class footer to the output.
  *
  * @param sb The string builder the output is appended to.
  */
 private static void addClassFooter(final StringBuilder sb) {
   sb.append("}\n\n");
 }
 /**
  * Adds an instruction with two arguments to the output.
  *
  * @param sb The string builder the output is appended to.
  * @param mnemonic The instruction mnemonic.
  * @param value1 The first instruction argument value.
  * @param value2 The second instruction argument value.
  */
 private static void add(
     final StringBuilder sb, final String mnemonic, final long value1, final long value2) {
   sb.append(String.format("%s %d, %d", mnemonic, value1, value2));
 }
 /**
  * Adds a simple one-mnemonic instruction to the output.
  *
  * @param sb The string builder the output is appended to.
  * @param mnemonic The instruction mnemonic.
  */
 private static void add(final StringBuilder sb, final String mnemonic) {
   sb.append(mnemonic);
 }
 /**
  * Adds an if instruction to the output.
  *
  * @param sb The string builder the output is appended to.
  * @param mnemonic The mnemonic of the if instruction.
  * @param value The branch offset value.
  */
 private static void addIf(final StringBuilder sb, final String mnemonic, final long value) {
   sb.append(String.format("%s %08X", mnemonic, value));
 }
  /**
   * Adds a method to the output.
   *
   * @param code The code the method belongs to.
   * @param resolvedMethod The method to add to the output.
   * @param className The name of the enclosing class the method belongs to.
   * @param sb The string builder the output is appended to.
   * @return True, if the method has code. False, if the method is just a declaration.
   */
  private static boolean addMethod(
      final ResolvedCode code,
      final ResolvedMethod resolvedMethod,
      final String className,
      final StringBuilder sb) {
    sb.append("\t");
    final String flattenedReturnType =
        ActionScript3Helpers.flattenNamespaceName(resolvedMethod.getReturnType());
    sb.append(flattenedReturnType);

    if (!flattenedReturnType.isEmpty()) {
      // This path is taken for all methods but constructors.
      sb.append(" ");
    }

    final String functionName = ActionScript3Helpers.flattenNamespaceName(resolvedMethod.getName());

    if (functionName.startsWith(className)) {
      // To make code more readable, strip fully qualified type
      // information.
      sb.append(functionName.substring(className.length() + 1));
    } else {
      sb.append(functionName);
    }

    sb.append("(");

    // Print the method arguments.
    for (final String[] argument : resolvedMethod.getArguments()) {

      if (argument != resolvedMethod.getArguments().get(0)) {
        sb.append(", ");
      }

      sb.append(ActionScript3Helpers.flattenNamespaceName(argument));
    }

    sb.append(")");

    // Print the code.
    final AS3Code methodCode = resolvedMethod.getCode();

    if (methodCode == null) {
      sb.append(";\n");
      return false;
    } else {
      sb.append("\n");
      sb.append("\t{");
      sb.append("\n");
      sb.append(getCodeText(code, methodCode, "\t\t"));
      sb.append("\t}\n");
      return true;
    }
  }