/**
   * 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;
    }
  }
  /**
   * 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 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();
  }