//===-- Reproducer.cpp ----------------------------------------------------===//
//
// 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 "lldb/Utility/ReproducerProvider.h"
#include "lldb/Utility/ProcessInfo.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/WithColor.h"
#include "llvm/Support/raw_ostream.h"

using namespace lldb_private;
using namespace lldb_private::repro;
using namespace llvm;
using namespace llvm::yaml;

llvm::Expected<std::unique_ptr<DataRecorder>>
DataRecorder::Create(const FileSpec &filename) {
  std::error_code ec;
  auto recorder = std::make_unique<DataRecorder>(std::move(filename), ec);
  if (ec)
    return llvm::errorCodeToError(ec);
  return std::move(recorder);
}

llvm::Expected<std::unique_ptr<YamlRecorder>>
YamlRecorder::Create(const FileSpec &filename) {
  std::error_code ec;
  auto recorder = std::make_unique<YamlRecorder>(std::move(filename), ec);
  if (ec)
    return llvm::errorCodeToError(ec);
  return std::move(recorder);
}

void VersionProvider::Keep() {
  FileSpec file = GetRoot().CopyByAppendingPathComponent(Info::file);
  std::error_code ec;
  llvm::raw_fd_ostream os(file.GetPath(), ec, llvm::sys::fs::OF_TextWithCRLF);
  if (ec)
    return;
  os << m_version << "\n";
}

FlushingFileCollector::FlushingFileCollector(llvm::StringRef files_path,
                                             llvm::StringRef dirs_path,
                                             std::error_code &ec) {
  auto clear = llvm::make_scope_exit([this]() {
    m_files_os.reset();
    m_dirs_os.reset();
  });
  m_files_os.emplace(files_path, ec, llvm::sys::fs::OF_Append);
  if (ec)
    return;
  m_dirs_os.emplace(dirs_path, ec, llvm::sys::fs::OF_Append);
  if (ec)
    return;
  clear.release();
}

void FlushingFileCollector::addFileImpl(StringRef file) {
  if (m_files_os) {
    *m_files_os << file << '\0';
    m_files_os->flush();
  }
}

llvm::vfs::directory_iterator
FlushingFileCollector::addDirectoryImpl(const Twine &dir,
                                        IntrusiveRefCntPtr<vfs::FileSystem> vfs,
                                        std::error_code &dir_ec) {
  if (m_dirs_os) {
    *m_dirs_os << dir << '\0';
    m_dirs_os->flush();
  }
  return vfs->dir_begin(dir, dir_ec);
}

void FileProvider::RecordInterestingDirectory(const llvm::Twine &dir) {
  if (m_collector)
    m_collector->addFile(dir);
}

void FileProvider::RecordInterestingDirectoryRecursive(const llvm::Twine &dir) {
  if (m_collector)
    m_collector->addDirectory(dir);
}

llvm::Expected<std::unique_ptr<ProcessInfoRecorder>>
ProcessInfoRecorder::Create(const FileSpec &filename) {
  std::error_code ec;
  auto recorder =
      std::make_unique<ProcessInfoRecorder>(std::move(filename), ec);
  if (ec)
    return llvm::errorCodeToError(ec);
  return std::move(recorder);
}

void ProcessInfoProvider::Keep() {
  std::vector<std::string> files;
  for (auto &recorder : m_process_info_recorders) {
    recorder->Stop();
    files.push_back(recorder->GetFilename().GetPath());
  }

  FileSpec file = GetRoot().CopyByAppendingPathComponent(Info::file);
  std::error_code ec;
  llvm::raw_fd_ostream os(file.GetPath(), ec, llvm::sys::fs::OF_TextWithCRLF);
  if (ec)
    return;
  llvm::yaml::Output yout(os);
  yout << files;
}

void ProcessInfoProvider::Discard() { m_process_info_recorders.clear(); }

ProcessInfoRecorder *ProcessInfoProvider::GetNewProcessInfoRecorder() {
  std::size_t i = m_process_info_recorders.size() + 1;
  std::string filename = (llvm::Twine(Info::name) + llvm::Twine("-") +
                          llvm::Twine(i) + llvm::Twine(".yaml"))
                             .str();
  auto recorder_or_error = ProcessInfoRecorder::Create(
      GetRoot().CopyByAppendingPathComponent(filename));
  if (!recorder_or_error) {
    llvm::consumeError(recorder_or_error.takeError());
    return nullptr;
  }

  m_process_info_recorders.push_back(std::move(*recorder_or_error));
  return m_process_info_recorders.back().get();
}

void ProcessInfoRecorder::Record(const ProcessInstanceInfoList &process_infos) {
  if (!m_record)
    return;
  llvm::yaml::Output yout(m_os);
  yout << const_cast<ProcessInstanceInfoList &>(process_infos);
  m_os.flush();
}

void SymbolFileProvider::AddSymbolFile(const UUID *uuid,
                                       const FileSpec &module_file,
                                       const FileSpec &symbol_file) {
  if (!uuid || (!module_file && !symbol_file))
    return;
  m_symbol_files.emplace_back(uuid->GetAsString(), module_file.GetPath(),
                              symbol_file.GetPath());
}

void SymbolFileProvider::Keep() {
  FileSpec file = this->GetRoot().CopyByAppendingPathComponent(Info::file);
  std::error_code ec;
  llvm::raw_fd_ostream os(file.GetPath(), ec, llvm::sys::fs::OF_TextWithCRLF);
  if (ec)
    return;

  // Remove duplicates.
  llvm::sort(m_symbol_files.begin(), m_symbol_files.end());
  m_symbol_files.erase(
      std::unique(m_symbol_files.begin(), m_symbol_files.end()),
      m_symbol_files.end());

  llvm::yaml::Output yout(os);
  yout << m_symbol_files;
}

SymbolFileLoader::SymbolFileLoader(Loader *loader) {
  if (!loader)
    return;

  FileSpec file = loader->GetFile<SymbolFileProvider::Info>();
  if (!file)
    return;

  auto error_or_file = llvm::MemoryBuffer::getFile(file.GetPath());
  if (auto err = error_or_file.getError())
    return;

  llvm::yaml::Input yin((*error_or_file)->getBuffer());
  yin >> m_symbol_files;
}

std::pair<FileSpec, FileSpec>
SymbolFileLoader::GetPaths(const UUID *uuid) const {
  if (!uuid)
    return {};

  auto it = std::lower_bound(m_symbol_files.begin(), m_symbol_files.end(),
                             SymbolFileProvider::Entry(uuid->GetAsString()));
  if (it == m_symbol_files.end())
    return {};
  return std::make_pair<FileSpec, FileSpec>(FileSpec(it->module_path),
                                            FileSpec(it->symbol_path));
}

void ProviderBase::anchor() {}
char CommandProvider::ID = 0;
char FileProvider::ID = 0;
char ProviderBase::ID = 0;
char VersionProvider::ID = 0;
char WorkingDirectoryProvider::ID = 0;
char HomeDirectoryProvider::ID = 0;
char ProcessInfoProvider::ID = 0;
char SymbolFileProvider::ID = 0;
const char *CommandProvider::Info::file = "command-interpreter.yaml";
const char *CommandProvider::Info::name = "command-interpreter";
const char *FileProvider::Info::file = "files.yaml";
const char *FileProvider::Info::name = "files";
const char *VersionProvider::Info::file = "version.txt";
const char *VersionProvider::Info::name = "version";
const char *WorkingDirectoryProvider::Info::file = "cwd.txt";
const char *WorkingDirectoryProvider::Info::name = "cwd";
const char *HomeDirectoryProvider::Info::file = "home.txt";
const char *HomeDirectoryProvider::Info::name = "home";
const char *ProcessInfoProvider::Info::file = "process-info.yaml";
const char *ProcessInfoProvider::Info::name = "process-info";
const char *SymbolFileProvider::Info::file = "symbol-files.yaml";
const char *SymbolFileProvider::Info::name = "symbol-files";
