package io.ray.runtime.util.generator;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.FileUtils;

public class ConcurrencyGroupBuilderGenerator extends BaseGenerator {

  /**
   * Build `BaseConcurrencyGroupBuilder::addMethod()` methods with the given number of parameters.
   *
   * @param numParameters the number of parameters.
   * @param hasReturn if true, Build api for functions with return.
   */
  private void buildConcurrencyGroupMethods(int numParameters, boolean hasReturn) {
    // 1) Construct the `genericTypes` part, e.g. `<T0, T1, T2, R>`.
    StringBuilder genericTypes = new StringBuilder();
    for (int i = 0; i < numParameters; i++) {
      genericTypes.append("T").append(i).append(", ");
    }
    // Return generic type.
    if (hasReturn) {
      genericTypes.append("R, ");
    }
    if (genericTypes.length() > 0) {
      // Trim trailing ", ";
      genericTypes = new StringBuilder(genericTypes.substring(0, genericTypes.length() - 2));
      genericTypes = new StringBuilder("<" + genericTypes + ">");
    }
    // 2) Construct the `returnType` part.
    final String returnType =
        hasReturn ? "ConcurrencyGroupBuilder<A>" : "ConcurrencyGroupBuilder<A>";
    // 3) Construct the `argsDeclaration` part.
    String rayFuncGenericTypes = genericTypes.toString();
    if (rayFuncGenericTypes.isEmpty()) {
      rayFuncGenericTypes = "<A>";
    } else {
      rayFuncGenericTypes = rayFuncGenericTypes.replace("<", "<A, ");
    }
    String argsDeclarationPrefix =
        String.format(
            "RayFunc%s%d%s f, ", hasReturn ? "" : "Void", numParameters + 1, rayFuncGenericTypes);
    // Enumerate all combinations of the parameters.
    for (String param : generateParameters(numParameters)) {
      String argsDeclaration = argsDeclarationPrefix + param;
      // Trim trailing ", ";
      argsDeclaration = argsDeclaration.substring(0, argsDeclaration.length() - 2);
      // Print the first line (method signature).
      final String modifiers = "public";
      final String callFunc = "addMethod";
      newLine(
          1,
          String.format(
              "%s%s %s %s(%s) {",
              modifiers,
              (genericTypes.length() == 0) ? "" : " " + genericTypes,
              returnType,
              callFunc,
              argsDeclaration));
      // 5) Construct the third line.
      newLine(2, "return internalAddMethod(f);");
      newLine(1, "}");
      newLine("");
    }
  }

  /** Returns Whole file content of `BaseConcurrencyGroupBuilderGenerator.java`. */
  private String generateActorCallDotJava() {
    sb = new StringBuilder();
    newLine("// Generated by `BaseConcurrencyGroupBuilderGenerator.java`. DO NOT EDIT.");
    newLine("");
    newLine("package io.ray.api.concurrencygroup;");
    newLine("");
    newLine("import io.ray.api.function.RayFunc;");
    for (int i = 1; i <= MAX_PARAMETERS; i++) {
      newLine("import io.ray.api.function.RayFunc" + i + ";");
    }
    for (int i = 1; i <= MAX_PARAMETERS; i++) {
      newLine("import io.ray.api.function.RayFuncVoid" + i + ";");
    }
    newLine("");
    newLine("/**");
    newLine(" * This class provides type-safe interfaces for concurrency groups.");
    newLine(" **/");
    newLine("public abstract class BaseConcurrencyGroupBuilder<A> {");
    newLine("");
    newLine(
        "  protected abstract " + "ConcurrencyGroupBuilder<A> internalAddMethod(RayFunc func);");
    newLine("");
    for (int i = 0; i <= MAX_PARAMETERS - 1; i++) {
      buildConcurrencyGroupMethods(i, true);
      buildConcurrencyGroupMethods(i, false);
    }
    newLine("}");
    return sb.toString();
  }

  private List<String> generateParameters(int numParams) {
    List<String> res = new ArrayList<>();
    dfs(0, numParams, "", res);
    return res;
  }

  private void dfs(int pos, int numParams, String cur, List<String> res) {
    if (pos >= numParams) {
      res.add(cur);
      return;
    }
    String nextParameter = "";
    dfs(pos + 1, numParams, cur + nextParameter, res);
  }

  public static void main(String[] args) throws IOException {
    String path =
        System.getProperty("user.dir")
            + "/api/src/main/java/io/ray/api/"
            + "concurrencyGroup/BaseConcurrencyGroupBuilder.java";
    FileUtils.write(
        new File(path),
        new ConcurrencyGroupBuilderGenerator().generateActorCallDotJava(),
        Charset.defaultCharset());
  }
}
