//===- CLOptionsSetup.cpp - Helpers to setup debug CL options ---*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include "mlir/Debug/CLOptionsSetup.h"

#include "mlir/Debug/Counter.h"
#include "mlir/Debug/DebuggerExecutionContextHook.h"
#include "mlir/Debug/ExecutionContext.h"
#include "mlir/Debug/Observers/ActionLogging.h"
#include "mlir/Debug/Observers/ActionProfiler.h"
#include "mlir/IR/MLIRContext.h"
#include "mlir/Support/FileUtilities.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/ToolOutputFile.h"

using namespace mlir;
using namespace mlir::tracing;
using namespace llvm;

namespace {
struct DebugConfigCLOptions : public DebugConfig {
  DebugConfigCLOptions() {
    static cl::opt<std::string, /*ExternalStorage=*/true> logActionsTo{
        "log-actions-to",
        cl::desc("Log action execution to a file, or stderr if "
                 " '-' is passed"),
        cl::location(logActionsToFlag)};

    static cl::opt<std::string, /*ExternalStorage=*/true> profileActionsTo{
        "profile-actions-to",
        cl::desc("Profile action execution to a file, or stderr if "
                 " '-' is passed"),
        cl::location(profileActionsToFlag)};

    static cl::list<std::string> logActionLocationFilter(
        "log-mlir-actions-filter",
        cl::desc(
            "Comma separated list of locations to filter actions from logging"),
        cl::CommaSeparated,
        cl::cb<void, std::string>([&](const std::string &location) {
          static bool registerOnce = [&] {
            addLogActionLocFilter(&locBreakpointManager);
            return true;
          }();
          (void)registerOnce;
          static std::vector<std::string> locations;
          locations.push_back(location);
          StringRef locStr = locations.back();

          // Parse the individual location filters and set the breakpoints.
          auto diag = [](Twine msg) { llvm::errs() << msg << "\n"; };
          auto locBreakpoint =
              tracing::FileLineColLocBreakpoint::parseFromString(locStr, diag);
          if (failed(locBreakpoint)) {
            llvm::errs() << "Invalid location filter: " << locStr << "\n";
            exit(1);
          }
          auto [file, line, col] = *locBreakpoint;
          locBreakpointManager.addBreakpoint(file, line, col);
        }));
  }
  tracing::FileLineColLocBreakpointManager locBreakpointManager;
};

} // namespace

static ManagedStatic<DebugConfigCLOptions> clOptionsConfig;
void DebugConfig::registerCLOptions() { *clOptionsConfig; }

DebugConfig DebugConfig::createFromCLOptions() { return *clOptionsConfig; }

class InstallDebugHandler::Impl {
public:
  Impl(MLIRContext &context, const DebugConfig &config) {
    if (config.getLogActionsTo().empty() &&
        config.getProfileActionsTo().empty() &&
        !config.isDebuggerActionHookEnabled()) {
      if (tracing::DebugCounter::isActivated())
        context.registerActionHandler(tracing::DebugCounter());
      return;
    }
    errs() << "ExecutionContext registered on the context";
    if (tracing::DebugCounter::isActivated())
      emitError(UnknownLoc::get(&context),
                "Debug counters are incompatible with --log-actions-to and "
                "--mlir-enable-debugger-hook options and are disabled");
    if (!config.getLogActionsTo().empty()) {
      std::string errorMessage;
      logActionsFile = openOutputFile(config.getLogActionsTo(), &errorMessage);
      if (!logActionsFile) {
        emitError(UnknownLoc::get(&context),
                  "Opening file for --log-actions-to failed: ")
            << errorMessage << "\n";
        return;
      }
      logActionsFile->keep();
      raw_fd_ostream &logActionsStream = logActionsFile->os();
      actionLogger = std::make_unique<tracing::ActionLogger>(logActionsStream);
      for (const auto *locationBreakpoint : config.getLogActionsLocFilters())
        actionLogger->addBreakpointManager(locationBreakpoint);
      executionContext.registerObserver(actionLogger.get());
    }

    if (!config.getProfileActionsTo().empty()) {
      std::string errorMessage;
      profileActionsFile =
          openOutputFile(config.getProfileActionsTo(), &errorMessage);
      if (!profileActionsFile) {
        emitError(UnknownLoc::get(&context),
                  "Opening file for --profile-actions-to failed: ")
            << errorMessage << "\n";
        return;
      }
      profileActionsFile->keep();
      raw_fd_ostream &profileActionsStream = profileActionsFile->os();
      actionProfiler =
          std::make_unique<tracing::ActionProfiler>(profileActionsStream);
      executionContext.registerObserver(actionProfiler.get());
    }

    if (config.isDebuggerActionHookEnabled()) {
      errs() << " (with Debugger hook)";
      setupDebuggerExecutionContextHook(executionContext);
    }
    errs() << "\n";
    context.registerActionHandler(executionContext);
  }

private:
  std::unique_ptr<ToolOutputFile> logActionsFile;
  tracing::ExecutionContext executionContext;
  std::unique_ptr<tracing::ActionLogger> actionLogger;
  std::vector<std::unique_ptr<tracing::FileLineColLocBreakpoint>>
      locationBreakpoints;
  std::unique_ptr<ToolOutputFile> profileActionsFile;
  std::unique_ptr<tracing::ActionProfiler> actionProfiler;
};

InstallDebugHandler::InstallDebugHandler(MLIRContext &context,
                                         const DebugConfig &config)
    : impl(std::make_unique<Impl>(context, config)) {}

InstallDebugHandler::~InstallDebugHandler() = default;