#include <sstream>

#include "emitterutils.h"
#include "indentation.h"  // IWYU pragma: keep
#include "yaml-cpp/emitter.h"
#include "yaml-cpp/emitterdef.h"
#include "yaml-cpp/emittermanip.h"
#include "yaml-cpp/exceptions.h"  // IWYU pragma: keep

namespace YODA_YAML {
class Binary;
struct _Null;

Emitter::Emitter() : m_pState(new EmitterState), m_stream{} {}

Emitter::Emitter(std::ostream& stream)
    : m_pState(new EmitterState), m_stream(stream) {}

Emitter::~Emitter() = default;

const char* Emitter::c_str() const { return m_stream.str(); }

std::size_t Emitter::size() const { return m_stream.pos(); }

// state checking
bool Emitter::good() const { return m_pState->good(); }

const std::string Emitter::GetLastError() const {
  return m_pState->GetLastError();
}

// global setters
bool Emitter::SetOutputCharset(EMITTER_MANIP value) {
  return m_pState->SetOutputCharset(value, FmtScope::Global);
}

bool Emitter::SetStringFormat(EMITTER_MANIP value) {
  return m_pState->SetStringFormat(value, FmtScope::Global);
}

bool Emitter::SetBoolFormat(EMITTER_MANIP value) {
  bool ok = false;
  if (m_pState->SetBoolFormat(value, FmtScope::Global))
    ok = true;
  if (m_pState->SetBoolCaseFormat(value, FmtScope::Global))
    ok = true;
  if (m_pState->SetBoolLengthFormat(value, FmtScope::Global))
    ok = true;
  return ok;
}

bool Emitter::SetNullFormat(EMITTER_MANIP value) {
  return m_pState->SetNullFormat(value, FmtScope::Global);
}

bool Emitter::SetIntBase(EMITTER_MANIP value) {
  return m_pState->SetIntFormat(value, FmtScope::Global);
}

bool Emitter::SetSeqFormat(EMITTER_MANIP value) {
  return m_pState->SetFlowType(GroupType::Seq, value, FmtScope::Global);
}

bool Emitter::SetMapFormat(EMITTER_MANIP value) {
  bool ok = false;
  if (m_pState->SetFlowType(GroupType::Map, value, FmtScope::Global))
    ok = true;
  if (m_pState->SetMapKeyFormat(value, FmtScope::Global))
    ok = true;
  return ok;
}

bool Emitter::SetIndent(std::size_t n) {
  return m_pState->SetIndent(n, FmtScope::Global);
}

bool Emitter::SetPreCommentIndent(std::size_t n) {
  return m_pState->SetPreCommentIndent(n, FmtScope::Global);
}

bool Emitter::SetPostCommentIndent(std::size_t n) {
  return m_pState->SetPostCommentIndent(n, FmtScope::Global);
}

bool Emitter::SetFloatPrecision(std::size_t n) {
  return m_pState->SetFloatPrecision(n, FmtScope::Global);
}

bool Emitter::SetDoublePrecision(std::size_t n) {
  return m_pState->SetDoublePrecision(n, FmtScope::Global);
}

void Emitter::RestoreGlobalModifiedSettings() {
  m_pState->RestoreGlobalModifiedSettings();
}

// SetLocalValue
// . Either start/end a group, or set a modifier locally
Emitter& Emitter::SetLocalValue(EMITTER_MANIP value) {
  if (!good())
    return *this;

  switch (value) {
    case BeginDoc:
      EmitBeginDoc();
      break;
    case EndDoc:
      EmitEndDoc();
      break;
    case BeginSeq:
      EmitBeginSeq();
      break;
    case EndSeq:
      EmitEndSeq();
      break;
    case BeginMap:
      EmitBeginMap();
      break;
    case EndMap:
      EmitEndMap();
      break;
    case Key:
    case Value:
      // deprecated (these can be deduced by the parity of nodes in a map)
      break;
    case TagByKind:
      EmitKindTag();
      break;
    case Newline:
      EmitNewline();
      break;
    default:
      m_pState->SetLocalValue(value);
      break;
  }
  return *this;
}

Emitter& Emitter::SetLocalIndent(const _Indent& indent) {
  m_pState->SetIndent(indent.value, FmtScope::Local);
  return *this;
}

Emitter& Emitter::SetLocalPrecision(const _Precision& precision) {
  if (precision.floatPrecision >= 0)
    m_pState->SetFloatPrecision(precision.floatPrecision, FmtScope::Local);
  if (precision.doublePrecision >= 0)
    m_pState->SetDoublePrecision(precision.doublePrecision, FmtScope::Local);
  return *this;
}

// EmitBeginDoc
void Emitter::EmitBeginDoc() {
  if (!good())
    return;

  if (m_pState->CurGroupType() != GroupType::NoType) {
    m_pState->SetError("Unexpected begin document");
    return;
  }

  if (m_pState->HasAnchor() || m_pState->HasTag()) {
    m_pState->SetError("Unexpected begin document");
    return;
  }

  if (m_stream.col() > 0)
    m_stream << "\n";
  m_stream << "---\n";

  m_pState->StartedDoc();
}

// EmitEndDoc
void Emitter::EmitEndDoc() {
  if (!good())
    return;

  if (m_pState->CurGroupType() != GroupType::NoType) {
    m_pState->SetError("Unexpected begin document");
    return;
  }

  if (m_pState->HasAnchor() || m_pState->HasTag()) {
    m_pState->SetError("Unexpected begin document");
    return;
  }

  if (m_stream.col() > 0)
    m_stream << "\n";
  m_stream << "...\n";
}

// EmitBeginSeq
void Emitter::EmitBeginSeq() {
  if (!good())
    return;

  PrepareNode(m_pState->NextGroupType(GroupType::Seq));

  m_pState->StartedGroup(GroupType::Seq);
}

// EmitEndSeq
void Emitter::EmitEndSeq() {
  if (!good())
    return;
  FlowType::value originalType = m_pState->CurGroupFlowType();

  if (m_pState->CurGroupChildCount() == 0)
    m_pState->ForceFlow();

  if (m_pState->CurGroupFlowType() == FlowType::Flow) {
    if (m_stream.comment())
      m_stream << "\n";
    m_stream << IndentTo(m_pState->CurIndent());
    if (originalType == FlowType::Block) {
      m_stream << "[";
    } else {
      if (m_pState->CurGroupChildCount() == 0 && !m_pState->HasBegunNode())
        m_stream << "[";
    }
    m_stream << "]";
  }

  m_pState->EndedGroup(GroupType::Seq);
}

// EmitBeginMap
void Emitter::EmitBeginMap() {
  if (!good())
    return;

  PrepareNode(m_pState->NextGroupType(GroupType::Map));

  m_pState->StartedGroup(GroupType::Map);
}

// EmitEndMap
void Emitter::EmitEndMap() {
  if (!good())
    return;
  FlowType::value originalType = m_pState->CurGroupFlowType();

  if (m_pState->CurGroupChildCount() == 0)
    m_pState->ForceFlow();

  if (m_pState->CurGroupFlowType() == FlowType::Flow) {
    if (m_stream.comment())
      m_stream << "\n";
    m_stream << IndentTo(m_pState->CurIndent());
    if (originalType == FlowType::Block) {
      m_stream << "{";
    } else {
      if (m_pState->CurGroupChildCount() == 0 && !m_pState->HasBegunNode())
        m_stream << "{";
    }
    m_stream << "}";
  }

  m_pState->EndedGroup(GroupType::Map);
}

// EmitNewline
void Emitter::EmitNewline() {
  if (!good())
    return;

  PrepareNode(EmitterNodeType::NoType);
  m_stream << "\n";
  m_pState->SetNonContent();
}

bool Emitter::CanEmitNewline() const { return true; }

// Put the stream in a state so we can simply write the next node
// E.g., if we're in a sequence, write the "- "
void Emitter::PrepareNode(EmitterNodeType::value child) {
  switch (m_pState->CurGroupNodeType()) {
    case EmitterNodeType::NoType:
      PrepareTopNode(child);
      break;
    case EmitterNodeType::FlowSeq:
      FlowSeqPrepareNode(child);
      break;
    case EmitterNodeType::BlockSeq:
      BlockSeqPrepareNode(child);
      break;
    case EmitterNodeType::FlowMap:
      FlowMapPrepareNode(child);
      break;
    case EmitterNodeType::BlockMap:
      BlockMapPrepareNode(child);
      break;
    case EmitterNodeType::Property:
    case EmitterNodeType::Scalar:
      assert(false);
      break;
  }
}

void Emitter::PrepareTopNode(EmitterNodeType::value child) {
  if (child == EmitterNodeType::NoType)
    return;

  if (m_pState->CurGroupChildCount() > 0 && m_stream.col() > 0)
    EmitBeginDoc();

  switch (child) {
    case EmitterNodeType::NoType:
      break;
    case EmitterNodeType::Property:
    case EmitterNodeType::Scalar:
    case EmitterNodeType::FlowSeq:
    case EmitterNodeType::FlowMap:
      // TODO: if we were writing null, and
      // we wanted it blank, we wouldn't want a space
      SpaceOrIndentTo(m_pState->HasBegunContent(), 0);
      break;
    case EmitterNodeType::BlockSeq:
    case EmitterNodeType::BlockMap:
      if (m_pState->HasBegunNode())
        m_stream << "\n";
      break;
  }
}

void Emitter::FlowSeqPrepareNode(EmitterNodeType::value child) {
  const std::size_t lastIndent = m_pState->LastIndent();

  if (!m_pState->HasBegunNode()) {
    if (m_stream.comment())
      m_stream << "\n";
    m_stream << IndentTo(lastIndent);
    if (m_pState->CurGroupChildCount() == 0)
      m_stream << "[";
    else
      m_stream << ",";
  }

  switch (child) {
    case EmitterNodeType::NoType:
      break;
    case EmitterNodeType::Property:
    case EmitterNodeType::Scalar:
    case EmitterNodeType::FlowSeq:
    case EmitterNodeType::FlowMap:
      SpaceOrIndentTo(
          m_pState->HasBegunContent() || m_pState->CurGroupChildCount() > 0,
          lastIndent);
      break;
    case EmitterNodeType::BlockSeq:
    case EmitterNodeType::BlockMap:
      assert(false);
      break;
  }
}

void Emitter::BlockSeqPrepareNode(EmitterNodeType::value child) {
  const std::size_t curIndent = m_pState->CurIndent();
  const std::size_t nextIndent = curIndent + m_pState->CurGroupIndent();

  if (child == EmitterNodeType::NoType)
    return;

  if (!m_pState->HasBegunContent()) {
    if (m_pState->CurGroupChildCount() > 0 || m_stream.comment()) {
      m_stream << "\n";
    }
    m_stream << IndentTo(curIndent);
    m_stream << "-";
  }

  switch (child) {
    case EmitterNodeType::NoType:
      break;
    case EmitterNodeType::Property:
    case EmitterNodeType::Scalar:
    case EmitterNodeType::FlowSeq:
    case EmitterNodeType::FlowMap:
      SpaceOrIndentTo(m_pState->HasBegunContent(), nextIndent);
      break;
    case EmitterNodeType::BlockSeq:
      m_stream << "\n";
      break;
    case EmitterNodeType::BlockMap:
      if (m_pState->HasBegunContent() || m_stream.comment())
        m_stream << "\n";
      break;
  }
}

void Emitter::FlowMapPrepareNode(EmitterNodeType::value child) {
  if (m_pState->CurGroupChildCount() % 2 == 0) {
    if (m_pState->GetMapKeyFormat() == LongKey)
      m_pState->SetLongKey();

    if (m_pState->CurGroupLongKey())
      FlowMapPrepareLongKey(child);
    else
      FlowMapPrepareSimpleKey(child);
  } else {
    if (m_pState->CurGroupLongKey())
      FlowMapPrepareLongKeyValue(child);
    else
      FlowMapPrepareSimpleKeyValue(child);
  }
}

void Emitter::FlowMapPrepareLongKey(EmitterNodeType::value child) {
  const std::size_t lastIndent = m_pState->LastIndent();

  if (!m_pState->HasBegunNode()) {
    if (m_stream.comment())
      m_stream << "\n";
    m_stream << IndentTo(lastIndent);
    if (m_pState->CurGroupChildCount() == 0)
      m_stream << "{ ?";
    else
      m_stream << ", ?";
  }

  switch (child) {
    case EmitterNodeType::NoType:
      break;
    case EmitterNodeType::Property:
    case EmitterNodeType::Scalar:
    case EmitterNodeType::FlowSeq:
    case EmitterNodeType::FlowMap:
      SpaceOrIndentTo(
          m_pState->HasBegunContent() || m_pState->CurGroupChildCount() > 0,
          lastIndent);
      break;
    case EmitterNodeType::BlockSeq:
    case EmitterNodeType::BlockMap:
      assert(false);
      break;
  }
}

void Emitter::FlowMapPrepareLongKeyValue(EmitterNodeType::value child) {
  const std::size_t lastIndent = m_pState->LastIndent();

  if (!m_pState->HasBegunNode()) {
    if (m_stream.comment())
      m_stream << "\n";
    m_stream << IndentTo(lastIndent);
    m_stream << ":";
  }

  switch (child) {
    case EmitterNodeType::NoType:
      break;
    case EmitterNodeType::Property:
    case EmitterNodeType::Scalar:
    case EmitterNodeType::FlowSeq:
    case EmitterNodeType::FlowMap:
      SpaceOrIndentTo(
          m_pState->HasBegunContent() || m_pState->CurGroupChildCount() > 0,
          lastIndent);
      break;
    case EmitterNodeType::BlockSeq:
    case EmitterNodeType::BlockMap:
      assert(false);
      break;
  }
}

void Emitter::FlowMapPrepareSimpleKey(EmitterNodeType::value child) {
  const std::size_t lastIndent = m_pState->LastIndent();

  if (!m_pState->HasBegunNode()) {
    if (m_stream.comment())
      m_stream << "\n";
    m_stream << IndentTo(lastIndent);
    if (m_pState->CurGroupChildCount() == 0)
      m_stream << "{";
    else
      m_stream << ",";
  }

  switch (child) {
    case EmitterNodeType::NoType:
      break;
    case EmitterNodeType::Property:
    case EmitterNodeType::Scalar:
    case EmitterNodeType::FlowSeq:
    case EmitterNodeType::FlowMap:
      SpaceOrIndentTo(
          m_pState->HasBegunContent() || m_pState->CurGroupChildCount() > 0,
          lastIndent);
      break;
    case EmitterNodeType::BlockSeq:
    case EmitterNodeType::BlockMap:
      assert(false);
      break;
  }
}

void Emitter::FlowMapPrepareSimpleKeyValue(EmitterNodeType::value child) {
  const std::size_t lastIndent = m_pState->LastIndent();

  if (!m_pState->HasBegunNode()) {
    if (m_stream.comment())
      m_stream << "\n";
    m_stream << IndentTo(lastIndent);
    if (m_pState->HasAlias()) {
      m_stream << " ";
    }
    m_stream << ":";
  }

  switch (child) {
    case EmitterNodeType::NoType:
      break;
    case EmitterNodeType::Property:
    case EmitterNodeType::Scalar:
    case EmitterNodeType::FlowSeq:
    case EmitterNodeType::FlowMap:
      SpaceOrIndentTo(
          m_pState->HasBegunContent() || m_pState->CurGroupChildCount() > 0,
          lastIndent);
      break;
    case EmitterNodeType::BlockSeq:
    case EmitterNodeType::BlockMap:
      assert(false);
      break;
  }
}

void Emitter::BlockMapPrepareNode(EmitterNodeType::value child) {
  if (m_pState->CurGroupChildCount() % 2 == 0) {
    if (m_pState->GetMapKeyFormat() == LongKey)
      m_pState->SetLongKey();
    if (child == EmitterNodeType::BlockSeq ||
        child == EmitterNodeType::BlockMap)
      m_pState->SetLongKey();

    if (m_pState->CurGroupLongKey())
      BlockMapPrepareLongKey(child);
    else
      BlockMapPrepareSimpleKey(child);
  } else {
    if (m_pState->CurGroupLongKey())
      BlockMapPrepareLongKeyValue(child);
    else
      BlockMapPrepareSimpleKeyValue(child);
  }
}

void Emitter::BlockMapPrepareLongKey(EmitterNodeType::value child) {
  const std::size_t curIndent = m_pState->CurIndent();
  const std::size_t childCount = m_pState->CurGroupChildCount();

  if (child == EmitterNodeType::NoType)
    return;

  if (!m_pState->HasBegunContent()) {
    if (childCount > 0) {
      m_stream << "\n";
    }
    if (m_stream.comment()) {
      m_stream << "\n";
    }
    m_stream << IndentTo(curIndent);
    m_stream << "?";
  }

  switch (child) {
    case EmitterNodeType::NoType:
      break;
    case EmitterNodeType::Property:
    case EmitterNodeType::Scalar:
    case EmitterNodeType::FlowSeq:
    case EmitterNodeType::FlowMap:
      SpaceOrIndentTo(true, curIndent + 1);
      break;
    case EmitterNodeType::BlockSeq:
    case EmitterNodeType::BlockMap:
      if (m_pState->HasBegunContent())
        m_stream << "\n";
      break;
  }
}

void Emitter::BlockMapPrepareLongKeyValue(EmitterNodeType::value child) {
  const std::size_t curIndent = m_pState->CurIndent();

  if (child == EmitterNodeType::NoType)
    return;

  if (!m_pState->HasBegunContent()) {
    m_stream << "\n";
    m_stream << IndentTo(curIndent);
    m_stream << ":";
  }

  switch (child) {
    case EmitterNodeType::NoType:
      break;
    case EmitterNodeType::Property:
    case EmitterNodeType::Scalar:
    case EmitterNodeType::FlowSeq:
    case EmitterNodeType::FlowMap:
      SpaceOrIndentTo(true, curIndent + 1);
      break;
    case EmitterNodeType::BlockSeq:
    case EmitterNodeType::BlockMap:
      if (m_pState->HasBegunContent())
        m_stream << "\n";
      SpaceOrIndentTo(true, curIndent + 1);
      break;
  }
}

void Emitter::BlockMapPrepareSimpleKey(EmitterNodeType::value child) {
  const std::size_t curIndent = m_pState->CurIndent();
  const std::size_t childCount = m_pState->CurGroupChildCount();

  if (child == EmitterNodeType::NoType)
    return;

  if (!m_pState->HasBegunNode()) {
    if (childCount > 0) {
      m_stream << "\n";
    }
  }

  switch (child) {
    case EmitterNodeType::NoType:
      break;
    case EmitterNodeType::Property:
    case EmitterNodeType::Scalar:
    case EmitterNodeType::FlowSeq:
    case EmitterNodeType::FlowMap:
      SpaceOrIndentTo(m_pState->HasBegunContent(), curIndent);
      break;
    case EmitterNodeType::BlockSeq:
    case EmitterNodeType::BlockMap:
      break;
  }
}

void Emitter::BlockMapPrepareSimpleKeyValue(EmitterNodeType::value child) {
  const std::size_t curIndent = m_pState->CurIndent();
  const std::size_t nextIndent = curIndent + m_pState->CurGroupIndent();

  if (!m_pState->HasBegunNode()) {
    if (m_pState->HasAlias()) {
      m_stream << " ";
    }
    m_stream << ":";
  }

  switch (child) {
    case EmitterNodeType::NoType:
      break;
    case EmitterNodeType::Property:
    case EmitterNodeType::Scalar:
    case EmitterNodeType::FlowSeq:
    case EmitterNodeType::FlowMap:
      SpaceOrIndentTo(true, nextIndent);
      break;
    case EmitterNodeType::BlockSeq:
    case EmitterNodeType::BlockMap:
      m_stream << "\n";
      break;
  }
}

// SpaceOrIndentTo
// . Prepares for some more content by proper spacing
void Emitter::SpaceOrIndentTo(bool requireSpace, std::size_t indent) {
  if (m_stream.comment())
    m_stream << "\n";
  if (m_stream.col() > 0 && requireSpace)
    m_stream << " ";
  m_stream << IndentTo(indent);
}

void Emitter::PrepareIntegralStream(std::stringstream& stream) const {

  switch (m_pState->GetIntFormat()) {
    case Dec:
      stream << std::dec;
      break;
    case Hex:
      stream << "0x";
      stream << std::hex;
      break;
    case Oct:
      stream << "0";
      stream << std::oct;
      break;
    default:
      assert(false);
  }
}

void Emitter::StartedScalar() { m_pState->StartedScalar(); }

// *******************************************************************************************
// overloads of Write

StringEscaping::value GetStringEscapingStyle(const EMITTER_MANIP emitterManip) {
  switch (emitterManip) {
    case EscapeNonAscii:
      return StringEscaping::NonAscii;
    case EscapeAsJson:
      return StringEscaping::JSON;
    default:
      return StringEscaping::None;
      break;
  }
}

Emitter& Emitter::Write(const std::string& str) {
  if (!good())
    return *this;

  StringEscaping::value stringEscaping = GetStringEscapingStyle(m_pState->GetOutputCharset());

  const StringFormat::value strFormat =
      Utils::ComputeStringFormat(str, m_pState->GetStringFormat(),
                                 m_pState->CurGroupFlowType(), stringEscaping == StringEscaping::NonAscii);

  if (strFormat == StringFormat::Literal || str.size() > 1024)
    m_pState->SetMapKeyFormat(YODA_YAML::LongKey, FmtScope::Local);

  PrepareNode(EmitterNodeType::Scalar);

  switch (strFormat) {
    case StringFormat::Plain:
      m_stream << str;
      break;
    case StringFormat::SingleQuoted:
      Utils::WriteSingleQuotedString(m_stream, str);
      break;
    case StringFormat::DoubleQuoted:
      Utils::WriteDoubleQuotedString(m_stream, str, stringEscaping);
      break;
    case StringFormat::Literal:
      Utils::WriteLiteralString(m_stream, str,
                                m_pState->CurIndent() + m_pState->GetIndent());
      break;
  }

  StartedScalar();

  return *this;
}

std::size_t Emitter::GetFloatPrecision() const {
  return m_pState->GetFloatPrecision();
}

std::size_t Emitter::GetDoublePrecision() const {
  return m_pState->GetDoublePrecision();
}

const char* Emitter::ComputeFullBoolName(bool b) const {
  const EMITTER_MANIP mainFmt = (m_pState->GetBoolLengthFormat() == ShortBool
                                     ? YesNoBool
                                     : m_pState->GetBoolFormat());
  const EMITTER_MANIP caseFmt = m_pState->GetBoolCaseFormat();
  switch (mainFmt) {
    case YesNoBool:
      switch (caseFmt) {
        case UpperCase:
          return b ? "YES" : "NO";
        case CamelCase:
          return b ? "Yes" : "No";
        case LowerCase:
          return b ? "yes" : "no";
        default:
          break;
      }
      break;
    case OnOffBool:
      switch (caseFmt) {
        case UpperCase:
          return b ? "ON" : "OFF";
        case CamelCase:
          return b ? "On" : "Off";
        case LowerCase:
          return b ? "on" : "off";
        default:
          break;
      }
      break;
    case TrueFalseBool:
      switch (caseFmt) {
        case UpperCase:
          return b ? "TRUE" : "FALSE";
        case CamelCase:
          return b ? "True" : "False";
        case LowerCase:
          return b ? "true" : "false";
        default:
          break;
      }
      break;
    default:
      break;
  }
  return b ? "y" : "n";  // should never get here, but it can't hurt to give
                         // these answers
}

const char* Emitter::ComputeNullName() const {
  switch (m_pState->GetNullFormat()) {
    case LowerNull:
      return "null";
    case UpperNull:
      return "NULL";
    case CamelNull:
      return "Null";
    case TildeNull:
      // fallthrough
    default:
      return "~";
  }
}

Emitter& Emitter::Write(bool b) {
  if (!good())
    return *this;

  PrepareNode(EmitterNodeType::Scalar);

  const char* name = ComputeFullBoolName(b);
  if (m_pState->GetBoolLengthFormat() == ShortBool)
    m_stream << name[0];
  else
    m_stream << name;

  StartedScalar();

  return *this;
}

Emitter& Emitter::Write(char ch) {
  if (!good())
    return *this;



  PrepareNode(EmitterNodeType::Scalar);
  Utils::WriteChar(m_stream, ch, GetStringEscapingStyle(m_pState->GetOutputCharset()));
  StartedScalar();

  return *this;
}

Emitter& Emitter::Write(const _Alias& alias) {
  if (!good())
    return *this;

  if (m_pState->HasAnchor() || m_pState->HasTag()) {
    m_pState->SetError(ErrorMsg::INVALID_ALIAS);
    return *this;
  }

  PrepareNode(EmitterNodeType::Scalar);

  if (!Utils::WriteAlias(m_stream, alias.content)) {
    m_pState->SetError(ErrorMsg::INVALID_ALIAS);
    return *this;
  }

  StartedScalar();

  m_pState->SetAlias();

  return *this;
}

Emitter& Emitter::Write(const _Anchor& anchor) {
  if (!good())
    return *this;

  if (m_pState->HasAnchor()) {
    m_pState->SetError(ErrorMsg::INVALID_ANCHOR);
    return *this;
  }

  PrepareNode(EmitterNodeType::Property);

  if (!Utils::WriteAnchor(m_stream, anchor.content)) {
    m_pState->SetError(ErrorMsg::INVALID_ANCHOR);
    return *this;
  }

  m_pState->SetAnchor();

  return *this;
}

Emitter& Emitter::Write(const _Tag& tag) {
  if (!good())
    return *this;

  if (m_pState->HasTag()) {
    m_pState->SetError(ErrorMsg::INVALID_TAG);
    return *this;
  }

  PrepareNode(EmitterNodeType::Property);

  bool success = false;
  if (tag.type == _Tag::Type::Verbatim)
    success = Utils::WriteTag(m_stream, tag.content, true);
  else if (tag.type == _Tag::Type::PrimaryHandle)
    success = Utils::WriteTag(m_stream, tag.content, false);
  else
    success = Utils::WriteTagWithPrefix(m_stream, tag.prefix, tag.content);

  if (!success) {
    m_pState->SetError(ErrorMsg::INVALID_TAG);
    return *this;
  }

  m_pState->SetTag();

  return *this;
}

void Emitter::EmitKindTag() { Write(LocalTag("")); }

Emitter& Emitter::Write(const _Comment& comment) {
  if (!good())
    return *this;

  PrepareNode(EmitterNodeType::NoType);

  if (m_stream.col() > 0)
    m_stream << Indentation(m_pState->GetPreCommentIndent());
  Utils::WriteComment(m_stream, comment.content,
                      m_pState->GetPostCommentIndent());

  m_pState->SetNonContent();

  return *this;
}

Emitter& Emitter::Write(const _Null& /*null*/) {
  if (!good())
    return *this;

  PrepareNode(EmitterNodeType::Scalar);

  m_stream << ComputeNullName();

  StartedScalar();

  return *this;
}

Emitter& Emitter::Write(const Binary& binary) {
  Write(SecondaryTag("binary"));

  if (!good())
    return *this;

  PrepareNode(EmitterNodeType::Scalar);
  Utils::WriteBinary(m_stream, binary);
  StartedScalar();

  return *this;
}
}  // namespace YODA_YAML
