#ifndef SRC_BLOB_SERIALIZER_DESERIALIZER_INL_H_
#define SRC_BLOB_SERIALIZER_DESERIALIZER_INL_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include "blob_serializer_deserializer.h"

#include <ostream>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>

#include "debug_utils-inl.h"

// This is related to the blob that is used in snapshots and has nothing to do
// with `node_blob.h`.

namespace node {

struct EnvSerializeInfo;
struct PropInfo;
struct RealmSerializeInfo;

namespace builtins {
struct CodeCacheInfo;
}  // namespace builtins

// These operator<< overload declarations are needed because
// BlobSerializerDeserializer::ToStr() uses these.

std::ostream& operator<<(std::ostream& output,
                         const builtins::CodeCacheInfo& info);

std::ostream& operator<<(std::ostream& output,
                         const std::vector<builtins::CodeCacheInfo>& vec);

std::ostream& operator<<(std::ostream& output, const std::vector<uint8_t>& vec);

std::ostream& operator<<(std::ostream& output,
                         const std::vector<PropInfo>& vec);

std::ostream& operator<<(std::ostream& output, const PropInfo& info);

std::ostream& operator<<(std::ostream& output,
                         const std::vector<std::string>& vec);

std::ostream& operator<<(std::ostream& output, const RealmSerializeInfo& i);

std::ostream& operator<<(std::ostream& output, const EnvSerializeInfo& i);

template <typename... Args>
void BlobSerializerDeserializer::Debug(const char* format,
                                       Args&&... args) const {
  if (is_debug) {
    FPrintF(stderr, format, std::forward<Args>(args)...);
  }
}

template <typename T>
std::string BlobSerializerDeserializer::ToStr(const T& arg) const {
  std::stringstream ss;
  ss << arg;
  return ss.str();
}

template <typename T>
std::string BlobSerializerDeserializer::GetName() const {
#define TYPE_LIST(V)                                                           \
  V(builtins::CodeCacheInfo)                                                   \
  V(PropInfo)                                                                  \
  V(std::string)

#define V(TypeName)                                                            \
  if constexpr (std::is_same_v<T, TypeName>) {                                 \
    return #TypeName;                                                          \
  } else  // NOLINT(readability/braces)
  TYPE_LIST(V)
#undef V

  if constexpr (std::is_arithmetic_v<T>) {
    return (std::is_unsigned_v<T>   ? "uint"
            : std::is_integral_v<T> ? "int"
                                    : "float") +
           std::to_string(sizeof(T) * 8) + "_t";
  }
  return "";
}

// Helper for reading numeric types.
template <typename Impl>
template <typename T>
T BlobDeserializer<Impl>::ReadArithmetic() {
  static_assert(std::is_arithmetic_v<T>, "Not an arithmetic type");
  T result;
  ReadArithmetic(&result, 1);
  return result;
}

// Layout of vectors:
// [ 4/8 bytes ] count
// [   ...     ] contents (count * size of individual elements)
template <typename Impl>
template <typename T>
std::vector<T> BlobDeserializer<Impl>::ReadVector() {
  if (is_debug) {
    std::string name = GetName<T>();
    Debug("\nReadVector<%s>()(%d-byte)\n", name.c_str(), sizeof(T));
  }
  size_t count = static_cast<size_t>(ReadArithmetic<size_t>());
  if (count == 0) {
    return std::vector<T>();
  }
  if (is_debug) {
    Debug("Reading %d vector elements...\n", count);
  }
  std::vector<T> result;
  if constexpr (std::is_arithmetic_v<T>) {
    result = ReadArithmeticVector<T>(count);
  } else {
    result = ReadNonArithmeticVector<T>(count);
  }
  if (is_debug) {
    std::string str = std::is_arithmetic_v<T> ? "" : ToStr(result);
    std::string name = GetName<T>();
    Debug("ReadVector<%s>() read %s\n", name.c_str(), str.c_str());
  }
  return result;
}

template <typename Impl>
std::string BlobDeserializer<Impl>::ReadString() {
  size_t length = ReadArithmetic<size_t>();

  if (is_debug) {
    Debug("ReadString(), length=%d: ", length);
  }

  CHECK_GT(length, 0);  // There should be no empty strings.
  MallocedBuffer<char> buf(length + 1);
  memcpy(buf.data, sink.data() + read_total, length + 1);
  std::string result(buf.data, length);  // This creates a copy of buf.data.

  if (is_debug) {
    Debug("\"%s\", read %zu bytes\n", result.c_str(), length + 1);
  }

  read_total += length + 1;
  return result;
}

// Helper for reading an array of numeric types.
template <typename Impl>
template <typename T>
void BlobDeserializer<Impl>::ReadArithmetic(T* out, size_t count) {
  static_assert(std::is_arithmetic_v<T>, "Not an arithmetic type");
  DCHECK_GT(count, 0);  // Should not read contents for vectors of size 0.
  if (is_debug) {
    std::string name = GetName<T>();
    Debug("Read<%s>()(%d-byte), count=%d: ", name.c_str(), sizeof(T), count);
  }

  size_t size = sizeof(T) * count;
  memcpy(out, sink.data() + read_total, size);

  if (is_debug) {
    std::string str =
        "{ " + std::to_string(out[0]) + (count > 1 ? ", ... }" : " }");
    Debug("%s, read %zu bytes\n", str.c_str(), size);
  }
  read_total += size;
}

// Helper for reading numeric vectors.
template <typename Impl>
template <typename Number>
std::vector<Number> BlobDeserializer<Impl>::ReadArithmeticVector(size_t count) {
  static_assert(std::is_arithmetic_v<Number>, "Not an arithmetic type");
  DCHECK_GT(count, 0);  // Should not read contents for vectors of size 0.
  std::vector<Number> result(count);
  ReadArithmetic(result.data(), count);
  return result;
}

// Helper for reading non-numeric vectors.
template <typename Impl>
template <typename T>
std::vector<T> BlobDeserializer<Impl>::ReadNonArithmeticVector(size_t count) {
  static_assert(!std::is_arithmetic_v<T>, "Arithmetic type");
  DCHECK_GT(count, 0);  // Should not read contents for vectors of size 0.
  std::vector<T> result;
  result.reserve(count);
  bool original_is_debug = is_debug;
  is_debug = original_is_debug && !std::is_same_v<T, std::string>;
  for (size_t i = 0; i < count; ++i) {
    if (is_debug) {
      Debug("\n[%d] ", i);
    }
    result.push_back(ReadElement<T>());
  }
  is_debug = original_is_debug;

  return result;
}

template <typename Impl>
template <typename T>
T BlobDeserializer<Impl>::ReadElement() {
  if constexpr (std::is_arithmetic_v<T>) {
    return ReadArithmetic<T>();
  } else if constexpr (std::is_same_v<T, std::string>) {
    return ReadString();
  } else {
    return impl()->template Read<T>();
  }
}

// Helper for writing numeric types.
template <typename Impl>
template <typename T>
size_t BlobSerializer<Impl>::WriteArithmetic(const T& data) {
  static_assert(std::is_arithmetic_v<T>, "Not an arithmetic type");
  return WriteArithmetic(&data, 1);
}

// Layout of vectors:
// [ 4/8 bytes ] count
// [   ...     ] contents (count * size of individual elements)
template <typename Impl>
template <typename T>
size_t BlobSerializer<Impl>::WriteVector(const std::vector<T>& data) {
  if (is_debug) {
    std::string str = std::is_arithmetic_v<T> ? "" : ToStr(data);
    std::string name = GetName<T>();
    Debug("\nWriteVector<%s>() (%d-byte), count=%d: %s\n",
          name.c_str(),
          sizeof(T),
          data.size(),
          str.c_str());
  }

  size_t written_total = WriteArithmetic<size_t>(data.size());
  if (data.size() == 0) {
    return written_total;
  }

  if constexpr (std::is_arithmetic_v<T>) {
    written_total += WriteArithmeticVector<T>(data);
  } else {
    written_total += WriteNonArithmeticVector<T>(data);
  }

  if (is_debug) {
    std::string name = GetName<T>();
    Debug("WriteVector<%s>() wrote %d bytes\n", name.c_str(), written_total);
  }

  return written_total;
}

// The layout of a written string:
// [  4/8 bytes     ] length
// [ |length| bytes ] contents
template <typename Impl>
size_t BlobSerializer<Impl>::WriteString(const std::string& data) {
  CHECK_GT(data.size(), 0);  // No empty strings should be written.
  size_t written_total = WriteArithmetic<size_t>(data.size());
  if (is_debug) {
    std::string str = ToStr(data);
    Debug("WriteString(), length=%zu: \"%s\"\n", data.size(), data.c_str());
  }

  // Write the null-terminated string.
  size_t length = data.size() + 1;
  sink.insert(sink.end(), data.c_str(), data.c_str() + length);
  written_total += length;

  if (is_debug) {
    Debug("WriteString() wrote %zu bytes\n", written_total);
  }

  return written_total;
}

// Helper for writing an array of numeric types.
template <typename Impl>
template <typename T>
size_t BlobSerializer<Impl>::WriteArithmetic(const T* data, size_t count) {
  static_assert(std::is_arithmetic_v<T>, "Arithmetic type");
  DCHECK_GT(count, 0);  // Should not write contents for vectors of size 0.
  if (is_debug) {
    std::string str =
        "{ " + std::to_string(data[0]) + (count > 1 ? ", ... }" : " }");
    std::string name = GetName<T>();
    Debug("Write<%s>() (%zu-byte), count=%zu: %s",
          name.c_str(),
          sizeof(T),
          count,
          str.c_str());
  }

  size_t size = sizeof(T) * count;
  const char* pos = reinterpret_cast<const char*>(data);
  sink.insert(sink.end(), pos, pos + size);

  if (is_debug) {
    Debug(", wrote %zu bytes\n", size);
  }
  return size;
}

// Helper for writing numeric vectors.
template <typename Impl>
template <typename Number>
size_t BlobSerializer<Impl>::WriteArithmeticVector(
    const std::vector<Number>& data) {
  static_assert(std::is_arithmetic_v<Number>, "Arithmetic type");
  return WriteArithmetic(data.data(), data.size());
}

// Helper for writing non-numeric vectors.
template <typename Impl>
template <typename T>
size_t BlobSerializer<Impl>::WriteNonArithmeticVector(
    const std::vector<T>& data) {
  static_assert(!std::is_arithmetic_v<T>, "Arithmetic type");
  DCHECK_GT(data.size(),
            0);  // Should not write contents for vectors of size 0.
  size_t written_total = 0;
  bool original_is_debug = is_debug;
  is_debug = original_is_debug && !std::is_same_v<T, std::string>;
  for (size_t i = 0; i < data.size(); ++i) {
    if (is_debug) {
      Debug("\n[%d] ", i);
    }
    written_total += WriteElement<T>(data[i]);
  }
  is_debug = original_is_debug;

  return written_total;
}

template <typename Impl>
template <typename T>
size_t BlobSerializer<Impl>::WriteElement(const T& data) {
  if constexpr (std::is_arithmetic_v<T>) {
    return WriteArithmetic<T>(data);
  } else if constexpr (std::is_same_v<T, std::string>) {
    return WriteString(data);
  } else {
    return impl()->template Write<T>(data);
  }
}

}  // namespace node

#endif  // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#endif  // SRC_BLOB_SERIALIZER_DESERIALIZER_INL_H_
