#ifndef EMITTER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define EMITTER_H_62B23520_7C8E_11DE_8A39_0800200C9A66

#if defined(_MSC_VER) ||                                            \
    (defined(__GNUC__) && (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || \
     (__GNUC__ >= 4))  // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif

#include <cmath>
#include <cstddef>
#include <limits>
#include <memory>
#include <sstream>
#include <string>
#include <type_traits>

#include "yaml-cpp/binary.h"
#include "yaml-cpp/dll.h"
#include "yaml-cpp/emitterdef.h"
#include "yaml-cpp/emittermanip.h"
#include "yaml-cpp/null.h"
#include "yaml-cpp/ostream_wrapper.h"

namespace YODA_YAML {
class Binary;
struct _Null;
}  // namespace YODA_YAML

namespace YODA_YAML {
class EmitterState;

class YAML_CPP_API Emitter {
 public:
  Emitter();
  explicit Emitter(std::ostream& stream);
  Emitter(const Emitter&) = delete;
  Emitter& operator=(const Emitter&) = delete;
  ~Emitter();

  // output
  const char* c_str() const;
  std::size_t size() const;

  // state checking
  bool good() const;
  const std::string GetLastError() const;

  // global setters
  bool SetOutputCharset(EMITTER_MANIP value);
  bool SetStringFormat(EMITTER_MANIP value);
  bool SetBoolFormat(EMITTER_MANIP value);
  bool SetNullFormat(EMITTER_MANIP value);
  bool SetIntBase(EMITTER_MANIP value);
  bool SetSeqFormat(EMITTER_MANIP value);
  bool SetMapFormat(EMITTER_MANIP value);
  bool SetIndent(std::size_t n);
  bool SetPreCommentIndent(std::size_t n);
  bool SetPostCommentIndent(std::size_t n);
  bool SetFloatPrecision(std::size_t n);
  bool SetDoublePrecision(std::size_t n);
  void RestoreGlobalModifiedSettings();

  // local setters
  Emitter& SetLocalValue(EMITTER_MANIP value);
  Emitter& SetLocalIndent(const _Indent& indent);
  Emitter& SetLocalPrecision(const _Precision& precision);

  // overloads of write
  Emitter& Write(const std::string& str);
  Emitter& Write(bool b);
  Emitter& Write(char ch);
  Emitter& Write(const _Alias& alias);
  Emitter& Write(const _Anchor& anchor);
  Emitter& Write(const _Tag& tag);
  Emitter& Write(const _Comment& comment);
  Emitter& Write(const _Null& n);
  Emitter& Write(const Binary& binary);

  template <typename T>
  Emitter& WriteIntegralType(T value);

  template <typename T>
  Emitter& WriteStreamable(T value);

 private:
  template <typename T>
  void SetStreamablePrecision(std::stringstream&) {}
  std::size_t GetFloatPrecision() const;
  std::size_t GetDoublePrecision() const;

  void PrepareIntegralStream(std::stringstream& stream) const;
  void StartedScalar();

 private:
  void EmitBeginDoc();
  void EmitEndDoc();
  void EmitBeginSeq();
  void EmitEndSeq();
  void EmitBeginMap();
  void EmitEndMap();
  void EmitNewline();
  void EmitKindTag();
  void EmitTag(bool verbatim, const _Tag& tag);

  void PrepareNode(EmitterNodeType::value child);
  void PrepareTopNode(EmitterNodeType::value child);
  void FlowSeqPrepareNode(EmitterNodeType::value child);
  void BlockSeqPrepareNode(EmitterNodeType::value child);

  void FlowMapPrepareNode(EmitterNodeType::value child);

  void FlowMapPrepareLongKey(EmitterNodeType::value child);
  void FlowMapPrepareLongKeyValue(EmitterNodeType::value child);
  void FlowMapPrepareSimpleKey(EmitterNodeType::value child);
  void FlowMapPrepareSimpleKeyValue(EmitterNodeType::value child);

  void BlockMapPrepareNode(EmitterNodeType::value child);

  void BlockMapPrepareLongKey(EmitterNodeType::value child);
  void BlockMapPrepareLongKeyValue(EmitterNodeType::value child);
  void BlockMapPrepareSimpleKey(EmitterNodeType::value child);
  void BlockMapPrepareSimpleKeyValue(EmitterNodeType::value child);

  void SpaceOrIndentTo(bool requireSpace, std::size_t indent);

  const char* ComputeFullBoolName(bool b) const;
  const char* ComputeNullName() const;
  bool CanEmitNewline() const;

 private:
  std::unique_ptr<EmitterState> m_pState;
  ostream_wrapper m_stream;
};

template <typename T>
inline Emitter& Emitter::WriteIntegralType(T value) {
  if (!good())
    return *this;

  PrepareNode(EmitterNodeType::Scalar);

  std::stringstream stream;
  PrepareIntegralStream(stream);
  stream << value;
  m_stream << stream.str();

  StartedScalar();

  return *this;
}

template <typename T>
inline Emitter& Emitter::WriteStreamable(T value) {
  if (!good())
    return *this;

  PrepareNode(EmitterNodeType::Scalar);

  std::stringstream stream;
  SetStreamablePrecision<T>(stream);

  bool special = false;
  if (std::is_floating_point<T>::value) {
    if ((std::numeric_limits<T>::has_quiet_NaN ||
         std::numeric_limits<T>::has_signaling_NaN) &&
        std::isnan(value)) {
      special = true;
      stream << ".nan";
    } else if (std::numeric_limits<T>::has_infinity && std::isinf(value)) {
      special = true;
      if (std::signbit(value)) {
        stream << "-.inf";
      } else {
        stream << ".inf";
      }
    }
  }

  if (!special) {
    stream << value;
  }
  m_stream << stream.str();

  StartedScalar();

  return *this;
}

template <>
inline void Emitter::SetStreamablePrecision<float>(std::stringstream& stream) {
  stream.precision(static_cast<std::streamsize>(GetFloatPrecision()));
}

template <>
inline void Emitter::SetStreamablePrecision<double>(std::stringstream& stream) {
  stream.precision(static_cast<std::streamsize>(GetDoublePrecision()));
}

// overloads of insertion
inline Emitter& operator<<(Emitter& emitter, const std::string& v) {
  return emitter.Write(v);
}
inline Emitter& operator<<(Emitter& emitter, bool v) {
  return emitter.Write(v);
}
inline Emitter& operator<<(Emitter& emitter, char v) {
  return emitter.Write(v);
}
inline Emitter& operator<<(Emitter& emitter, unsigned char v) {
  return emitter.Write(static_cast<char>(v));
}
inline Emitter& operator<<(Emitter& emitter, const _Alias& v) {
  return emitter.Write(v);
}
inline Emitter& operator<<(Emitter& emitter, const _Anchor& v) {
  return emitter.Write(v);
}
inline Emitter& operator<<(Emitter& emitter, const _Tag& v) {
  return emitter.Write(v);
}
inline Emitter& operator<<(Emitter& emitter, const _Comment& v) {
  return emitter.Write(v);
}
inline Emitter& operator<<(Emitter& emitter, const _Null& v) {
  return emitter.Write(v);
}
inline Emitter& operator<<(Emitter& emitter, const Binary& b) {
  return emitter.Write(b);
}

inline Emitter& operator<<(Emitter& emitter, const char* v) {
  return emitter.Write(std::string(v));
}

inline Emitter& operator<<(Emitter& emitter, int v) {
  return emitter.WriteIntegralType(v);
}
inline Emitter& operator<<(Emitter& emitter, unsigned int v) {
  return emitter.WriteIntegralType(v);
}
inline Emitter& operator<<(Emitter& emitter, short v) {
  return emitter.WriteIntegralType(v);
}
inline Emitter& operator<<(Emitter& emitter, unsigned short v) {
  return emitter.WriteIntegralType(v);
}
inline Emitter& operator<<(Emitter& emitter, long v) {
  return emitter.WriteIntegralType(v);
}
inline Emitter& operator<<(Emitter& emitter, unsigned long v) {
  return emitter.WriteIntegralType(v);
}
inline Emitter& operator<<(Emitter& emitter, long long v) {
  return emitter.WriteIntegralType(v);
}
inline Emitter& operator<<(Emitter& emitter, unsigned long long v) {
  return emitter.WriteIntegralType(v);
}

inline Emitter& operator<<(Emitter& emitter, float v) {
  return emitter.WriteStreamable(v);
}
inline Emitter& operator<<(Emitter& emitter, double v) {
  return emitter.WriteStreamable(v);
}

inline Emitter& operator<<(Emitter& emitter, EMITTER_MANIP value) {
  return emitter.SetLocalValue(value);
}

inline Emitter& operator<<(Emitter& emitter, _Indent indent) {
  return emitter.SetLocalIndent(indent);
}

inline Emitter& operator<<(Emitter& emitter, _Precision precision) {
  return emitter.SetLocalPrecision(precision);
}
}  // namespace YODA_YAML

#endif  // EMITTER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
