// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the Free Software Foundation, Inc., 51
// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

#pragma once

#include <core/Serializer.hpp>
#include <util/types.hpp>

#include <third_party/nonstd/span.hpp>

#include <cstdint>
#include <string>
#include <variant>
#include <vector>

class Config;
class Context;

namespace core {

class CacheEntryDataParser;

namespace Result {

extern const uint8_t k_format_version;

extern const char* const k_unknown_file_type;

using UnderlyingFileTypeInt = uint8_t;
enum class FileType : UnderlyingFileTypeInt {
  // These values are written into the cache result file. This means they must
  // never be changed or removed unless the result file version is incremented.
  // Adding new values is OK.

  // The main output specified with -o or implicitly from the input filename.
  object = 0,

  // Dependency file specified with -MF or implicitly from the output filename.
  dependency = 1,

  // Text sent to standard error output.
  stderr_output = 2,

  // Coverage notes file generated by -ftest-coverage with filename in unmangled
  // form, i.e. output file but with a .gcno extension.
  coverage_unmangled = 3,

  // Stack usage file generated by -fstack-usage, i.e. output file but with a
  // .su extension.
  stackusage = 4,

  // Diagnostics output file specified by --serialize-diagnostics.
  diagnostic = 5,

  // DWARF object file generated by -gsplit-dwarf, i.e. output file but with a
  // .dwo extension.
  dwarf_object = 6,

  // Coverage notes file generated by -ftest-coverage with filename in mangled
  // form, i.e. full output file path but with a .gcno extension and with
  // slashes replaced with hashes.
  coverage_mangled = 7,

  // Text sent to standard output.
  stdout_output = 8,

  // Assembler listing file from -Wa,-a=file.
  assembler_listing = 9,
};

const char* file_type_to_string(FileType type);

std::string gcno_file_in_mangled_form(const Context& ctx);
std::string gcno_file_in_unmangled_form(const Context& ctx);

// This class knows how to deserializer a result cache entry.
class Deserializer
{
public:
  // Read a result from `data`.
  Deserializer(nonstd::span<const uint8_t> data);

  struct Header
  {
    uint8_t format_version = 0;
    uint8_t n_files = 0;
  };

  class Visitor
  {
  public:
    virtual ~Visitor() = default;

    virtual void on_header(const Header& header);

    virtual void on_embedded_file(uint8_t file_number,
                                  FileType file_type,
                                  nonstd::span<const uint8_t> data) = 0;
    virtual void on_raw_file(uint8_t file_number,
                             FileType file_type,
                             uint64_t file_size) = 0;
  };

  // Throws core::Error on error.
  void visit(Visitor& visitor) const;

private:
  nonstd::span<const uint8_t> m_data;

  void parse_file_entry(CacheEntryDataParser& parser,
                        uint8_t file_number) const;
};

inline void
Deserializer::Visitor::on_header(const Header& /*header*/)
{
}

// This class knows how to serialize a result cache entry.
class Serializer : public core::Serializer
{
public:
  Serializer(const Config& config);

  // Register data to include in the result. The data must live until
  // serialize() has been called.
  void add_data(FileType file_type, nonstd::span<const uint8_t> data);

  // Register a file path whose content should be included in the result.
  [[nodiscard]] bool add_file(FileType file_type, const std::string& path);

  // core::Serializer
  uint32_t serialized_size() const override;
  void serialize(util::Bytes& output) override;

  static bool use_raw_files(const Config& config);

  struct RawFile
  {
    uint8_t file_number;
    std::string path;
  };

  // Get raw files to store in local storage.
  const std::vector<RawFile>& get_raw_files() const;

private:
  const Config& m_config;
  uint64_t m_serialized_size;

  struct FileEntry
  {
    FileType file_type;
    std::variant<nonstd::span<const uint8_t>, std::string> data;
  };
  std::vector<FileEntry> m_file_entries;

  std::vector<RawFile> m_raw_files;
};

} // namespace Result

} // namespace core
