#pragma once

/* BIG FAT WARNING!!!
 *
 * The type-structure of this file is expected to remain consistent for 'atdna'
 * Any changes to the types or namespacing must be reflected in 'atdna/main.cpp'
 */

#include <memory>
#include <string>
#include <vector>

#include <sys/types.h>

#include "athena/DNAOp.hpp"
#include "athena/Global.hpp"
#include "athena/IStreamReader.hpp"
#include "athena/IStreamWriter.hpp"

using namespace std::literals;

namespace athena::io {

/**
 * @brief Base DNA class used against 'atdna'
 * @tparam DNAE Default-endianness for contained DNA values
 *
 * Athena bundles a build-tool called 'atdna'. This tool functions
 * just like the 'clang' compiler, except it emits a full .cpp implementation
 * with all read/write calls necessary to marshal the DNA structure to/from
 * a streamed medium
 */
template <Endian DNAE>
struct DNA {
  /**
   * @brief Designated byte-order used for serializing fields
   */
  static constexpr Endian DNAEndian = DNAE;

  /**
   * @brief Template type signaling atdna to capture the value where it's used
   * @tparam T The type of the value. Can be any numeric type or atVec* type
   * @tparam VE Endianness of the value
   */
  template <typename T, Endian VE = DNAE>
  using Value = T;

  /**
   * @brief Template type wrapping std::vector and signaling atdna to manipulate it where it's used
   * @tparam T The type of contained elements. Can be any numeric type, atVec* type, or another DNA subclass
   * @tparam cntVar C++ expression wrapped in DNA_COUNT macro to determine number of elements for vector
   * @tparam VE Endianness of the contained values
   */
  template <typename T, size_t cntVar, Endian VE = DNAE>
  using Vector = std::vector<T>;

  /**
   * @brief Template type wrapping std::unique_ptr<atUint8[]> and signaling atdna to read a
   *        raw byte-buffer where it's used
   * @tparam sizeVar C++ expression wrapped in DNA_COUNT macro to determine number of bytes for buffer
   */
  template <size_t sizeVar>
  using Buffer = std::unique_ptr<atUint8[]>;

  /**
   * @brief Template type wrapping std::string and signaling atdna to read string data where it's used
   * @tparam sizeVar C++ expression wrapped in DNA_COUNT macro to determine number of characters for string
   *                 -1 literal indicates null-terminated string
   */
  template <atInt32 sizeVar = -1>
  using String = std::string;

  /**
   * @brief Template type wrapping std::wstring and signaling atdna to read wstring data where it's used
   * @tparam sizeVar C++ expression wrapped in DNA_COUNT macro to determine number of characters for wstring
   *                 -1 literal indicates null-terminated wstring
   */
  template <atInt32 sizeVar = -1, Endian VE = DNAE>
  using WString = std::wstring;

  /**
   * @brief Meta Template signaling atdna to insert a stream seek where it's used
   * @tparam offset C++ expression wrapped in DNA_COUNT macro to determine number of bytes to seek
   * @tparam direction SeekOrigin to seek relative to
   */
  template <off_t offset, SeekOrigin direction>
  struct Seek {};

  /**
   * @brief Meta Template signaling atdna to insert an aligning stream seek where it's used
   * @tparam align Number of bytes to align to
   */
  template <size_t align>
  struct Align {};

  /**
   * @brief Meta Template preventing atdna from emitting read/write implementations
   */
  struct Delete {};

  /* Bring fundamental operations into DNA subclasses for easier per-op overrides */
  using Read = athena::io::Read<PropType::None>;
  using Write = athena::io::Write<PropType::None>;
  using BinarySize = athena::io::BinarySize<PropType::None>;
  using PropCount = athena::io::PropCount<PropType::None>;
  using ReadYaml = athena::io::ReadYaml<PropType::None>;
  using WriteYaml = athena::io::WriteYaml<PropType::None>;
};

/**
 * @brief Virtual DNA wrapper for subclasses that utilize virtual method calls
 * @tparam DNAE Default-endianness for contained DNA values
 *
 * Typically, static template-based DNA resolution is sufficient; however, formats
 * with a tree of variously-typed data structures would benefit from having a vtable.
 *
 * Note: It's not recommended to implement these directly. Instead, use the AT_DECL_DNA or
 * AT_DECL_EXPLCIT_DNA macro in the subclasses. Specializing the Enumerate method
 * is the proper way to override individual I/O operations. Owners of the virtualized
 * subclass will typically use a unique_ptr to reference the data; specializing their own
 * Enumerate methods as such:
 *
 * template <> void MySubclass::Enumerate<Read>(typename Read::StreamT& r)
 * { (Do stuff with `r`) }
 */
template <Endian DNAE>
struct DNAV : DNA<DNAE> {
  virtual ~DNAV() = default;
  virtual void read(athena::io::IStreamReader& r) = 0;
  virtual void write(athena::io::IStreamWriter& w) const = 0;
  virtual void binarySize(size_t& s) const = 0;
  virtual std::string_view DNATypeV() const = 0;
};

template <Endian DNAE>
struct DNAVYaml : DNAV<DNAE> {
  virtual ~DNAVYaml() = default;
  void read(athena::io::IStreamReader& r) override = 0;
  void write(athena::io::IStreamWriter& w) const override = 0;
  void binarySize(size_t& s) const override = 0;
  virtual void read(athena::io::YAMLDocReader& r) = 0;
  virtual void write(athena::io::YAMLDocWriter& w) const = 0;
};

/** Macro to supply count variable to atdna and mute it for other compilers */
#ifdef __clang__
#define AT_DNA_COUNT(...) sizeof(__VA_ARGS__)
#else
#define AT_DNA_COUNT(...) 0
#endif

} // namespace athena::io
