//===- offload-tblgen/APIGen.cpp - Tablegen backend for Offload header ----===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This is a Tablegen backend that produces the contents of the Offload API
// header. The generated comments are Doxygen compatible.
//
//===----------------------------------------------------------------------===//

#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/TableGen/Record.h"
#include "llvm/TableGen/TableGenBackend.h"

#include "GenCommon.hpp"
#include "RecordTypes.hpp"

using namespace llvm;
using namespace offload::tblgen;

// Produce a possibly multi-line comment from the input string
static std::string MakeComment(StringRef in) {
  std::string out = "";
  size_t LineStart = 0;
  size_t LineBreak = 0;
  while (LineBreak < in.size()) {
    LineBreak = in.find_first_of("\n", LineStart);
    if (LineBreak - LineStart <= 1) {
      break;
    }
    out += std::string("/// ") +
           in.substr(LineStart, LineBreak - LineStart).str() + "\n";
    LineStart = LineBreak + 1;
  }

  return out;
}

static void ProcessHandle(const HandleRec &H, raw_ostream &OS) {
  OS << CommentsHeader;
  OS << formatv("/// @brief {0}\n", H.getDesc());
  OS << formatv("typedef struct {0}_ *{0};\n", H.getName());
}

static void ProcessTypedef(const TypedefRec &T, raw_ostream &OS) {
  OS << CommentsHeader;
  OS << formatv("/// @brief {0}\n", T.getDesc());
  OS << formatv("typedef {0} {1};\n", T.getValue(), T.getName());
}

static void ProcessMacro(const MacroRec &M, raw_ostream &OS) {
  OS << CommentsHeader;
  OS << formatv("#ifndef {0}\n", M.getName());
  if (auto Condition = M.getCondition()) {
    OS << formatv("#if {0}\n", *Condition);
  }
  OS << "/// @brief " << M.getDesc() << "\n";
  OS << formatv("#define {0} {1}\n", M.getNameWithArgs(), M.getValue());
  if (auto AltValue = M.getAltValue()) {
    OS << "#else\n";
    OS << formatv("#define {0} {1}\n", M.getNameWithArgs(), *AltValue);
  }
  if (auto Condition = M.getCondition()) {
    OS << formatv("#endif // {0}\n", *Condition);
  }
  OS << formatv("#endif // {0}\n", M.getName());
}

static void ProcessFunction(const FunctionRec &F, raw_ostream &OS) {
  OS << CommentsHeader;
  OS << formatv("/// @brief {0}\n", F.getDesc());
  OS << CommentsBreak;

  OS << "/// @details\n";
  for (auto &Detail : F.getDetails()) {
    OS << formatv("///    - {0}\n", Detail);
  }
  OS << CommentsBreak;

  // Emit analogue remarks
  auto Analogues = F.getAnalogues();
  if (!Analogues.empty()) {
    OS << "/// @remarks\n///  _Analogues_\n";
    for (auto &Analogue : Analogues) {
      OS << formatv("///    - **{0}**\n", Analogue);
    }
    OS << CommentsBreak;
  }

  OS << "/// @returns\n";
  auto Returns = F.getReturns();
  for (auto &Ret : Returns) {
    OS << formatv("///     - ::{0}\n", Ret.getValue());
    auto RetConditions = Ret.getConditions();
    for (auto &RetCondition : RetConditions) {
      OS << formatv("///         + {0}\n", RetCondition);
    }
  }

  OS << formatv("{0}_APIEXPORT {1}_result_t {0}_APICALL ", PrefixUpper,
                PrefixLower);
  OS << F.getName();
  OS << "(\n";
  auto Params = F.getParams();
  for (auto &Param : Params) {
    OS << MakeParamComment(Param) << "\n";
    OS << "  " << Param.getType() << " " << Param.getName();
    if (Param != Params.back()) {
      OS << ",\n";
    } else {
      OS << "\n";
    }
  }
  OS << ");\n\n";
}

static void ProcessEnum(const EnumRec &Enum, raw_ostream &OS) {
  OS << CommentsHeader;
  OS << formatv("/// @brief {0}\n", Enum.getDesc());
  OS << formatv("typedef enum {0} {{\n", Enum.getName());

  uint32_t EtorVal = 0;
  for (const auto &EnumVal : Enum.getValues()) {
    if (Enum.isTyped()) {
      OS << MakeComment(
          formatv("[{0}] {1}", EnumVal.getTaggedType(), EnumVal.getDesc())
              .str());
    } else {
      OS << MakeComment(EnumVal.getDesc());
    }
    OS << formatv(TAB_1 "{0}_{1} = {2},\n", Enum.getEnumValNamePrefix(),
                  EnumVal.getName(), EtorVal++);
  }

  // Add force uint32 val
  OS << formatv(TAB_1 "/// @cond\n" TAB_1
                      "{0}_FORCE_UINT32 = 0x7fffffff\n" TAB_1
                      "/// @endcond\n\n",
                Enum.getEnumValNamePrefix());

  OS << formatv("} {0};\n", Enum.getName());
}

static void ProcessStruct(const StructRec &Struct, raw_ostream &OS) {
  OS << CommentsHeader;
  OS << formatv("/// @brief {0}\n", Struct.getDesc());
  OS << formatv("typedef struct {0} {{\n", Struct.getName());

  for (const auto &Member : Struct.getMembers()) {
    OS << formatv(TAB_1 "{0} {1}; {2}", Member.getType(), Member.getName(),
                  MakeComment(Member.getDesc()));
  }

  OS << formatv("} {0};\n\n", Struct.getName());
}

static void ProcessFuncParamStruct(const FunctionRec &Func, raw_ostream &OS) {
  if (Func.getParams().size() == 0) {
    return;
  }

  auto FuncParamStructBegin = R"(
///////////////////////////////////////////////////////////////////////////////
/// @brief Function parameters for {0}
/// @details Each entry is a pointer to the parameter passed to the function;
typedef struct {1} {{
)";

  OS << formatv(FuncParamStructBegin, Func.getName(),
                Func.getParamStructName());
  for (const auto &Param : Func.getParams()) {
    OS << TAB_1 << Param.getType() << "* p" << Param.getName() << ";\n";
  }
  OS << formatv("} {0};\n", Func.getParamStructName());
}

static void ProcessFuncWithCodeLocVariant(const FunctionRec &Func,
                                          raw_ostream &OS) {

  auto FuncWithCodeLocBegin = R"(
///////////////////////////////////////////////////////////////////////////////
/// @brief Variant of {0} that also sets source code location information
/// @details See also ::{0}
OL_APIEXPORT ol_result_t OL_APICALL {0}WithCodeLoc(
)";
  OS << formatv(FuncWithCodeLocBegin, Func.getName());
  auto Params = Func.getParams();
  for (auto &Param : Params) {
    OS << "  " << Param.getType() << " " << Param.getName();
    OS << ",\n";
  }
  OS << "ol_code_location_t *CodeLocation);\n\n";
}

void EmitOffloadAPI(const RecordKeeper &Records, raw_ostream &OS) {
  OS << GenericHeader;
  OS << FileHeader;
  // Generate main API definitions
  for (auto *R : Records.getAllDerivedDefinitions("APIObject")) {
    if (R->isSubClassOf("Macro")) {
      ProcessMacro(MacroRec{R}, OS);
    } else if (R->isSubClassOf("Typedef")) {
      ProcessTypedef(TypedefRec{R}, OS);
    } else if (R->isSubClassOf("Handle")) {
      ProcessHandle(HandleRec{R}, OS);
    } else if (R->isSubClassOf("Function")) {
      ProcessFunction(FunctionRec{R}, OS);
    } else if (R->isSubClassOf("Enum")) {
      ProcessEnum(EnumRec{R}, OS);
    } else if (R->isSubClassOf("Struct")) {
      ProcessStruct(StructRec{R}, OS);
    }
  }

  // Generate auxiliary definitions (func param structs etc)
  for (auto *R : Records.getAllDerivedDefinitions("Function")) {
    ProcessFuncParamStruct(FunctionRec{R}, OS);
  }

  for (auto *R : Records.getAllDerivedDefinitions("Function")) {
    ProcessFuncWithCodeLocVariant(FunctionRec{R}, OS);
  }

  OS << FileFooter;
}
