#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 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;
		case Auto:
		case EmitNonAscii:
		case EscapeNonAscii:
		case EscapeAsJson:
		case SingleQuoted:
		case DoubleQuoted:
		case Literal:
		case LowerNull:
		case UpperNull:
		case CamelNull:
		case TildeNull:
		case YesNoBool:
		case TrueFalseBool:
		case OnOffBool:
		case UpperCase:
		case LowerCase:
		case CamelCase:
		case LongBool:
		case ShortBool:
		case Dec:
		case Hex:
		case Oct:
		case Flow:
		case Block:
		case LongKey:
			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:
			assertrx(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:
			assertrx(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:
			assertrx(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:
			assertrx(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:
			assertrx(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:
			assertrx(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;
		case Auto:
		case TagByKind:
		case Newline:
		case EmitNonAscii:
		case EscapeNonAscii:
		case EscapeAsJson:
		case SingleQuoted:
		case DoubleQuoted:
		case Literal:
		case LowerNull:
		case UpperNull:
		case CamelNull:
		case TildeNull:
		case YesNoBool:
		case TrueFalseBool:
		case OnOffBool:
		case UpperCase:
		case LowerCase:
		case CamelCase:
		case LongBool:
		case ShortBool:
		case BeginDoc:
		case EndDoc:
		case BeginSeq:
		case EndSeq:
		case Flow:
		case Block:
		case BeginMap:
		case EndMap:
		case Key:
		case Value:
		case LongKey:
			assertrx(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;
		case Auto:
		case TagByKind:
		case Newline:
		case EmitNonAscii:
		case SingleQuoted:
		case DoubleQuoted:
		case Literal:
		case LowerNull:
		case UpperNull:
		case CamelNull:
		case TildeNull:
		case YesNoBool:
		case TrueFalseBool:
		case OnOffBool:
		case UpperCase:
		case LowerCase:
		case CamelCase:
		case LongBool:
		case ShortBool:
		case Dec:
		case Hex:
		case Oct:
		case BeginDoc:
		case EndDoc:
		case BeginSeq:
		case EndSeq:
		case Flow:
		case Block:
		case BeginMap:
		case EndMap:
		case Key:
		case Value:
		case LongKey:
		default:
			return StringEscaping::None;
	}
}

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(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";
				case Auto:
				case TagByKind:
				case Newline:
				case EmitNonAscii:
				case EscapeNonAscii:
				case EscapeAsJson:
				case SingleQuoted:
				case DoubleQuoted:
				case Literal:
				case LowerNull:
				case UpperNull:
				case CamelNull:
				case TildeNull:
				case YesNoBool:
				case TrueFalseBool:
				case OnOffBool:
				case LongBool:
				case ShortBool:
				case Dec:
				case Hex:
				case Oct:
				case BeginDoc:
				case EndDoc:
				case BeginSeq:
				case EndSeq:
				case Flow:
				case Block:
				case BeginMap:
				case EndMap:
				case Key:
				case Value:
				case LongKey:
					break;
			}
			break;
		case OnOffBool:
			switch (caseFmt) {
				case UpperCase:
					return b ? "ON" : "OFF";
				case CamelCase:
					return b ? "On" : "Off";
				case LowerCase:
					return b ? "on" : "off";
				case Auto:
				case TagByKind:
				case Newline:
				case EmitNonAscii:
				case EscapeNonAscii:
				case EscapeAsJson:
				case SingleQuoted:
				case DoubleQuoted:
				case Literal:
				case LowerNull:
				case UpperNull:
				case CamelNull:
				case TildeNull:
				case YesNoBool:
				case TrueFalseBool:
				case OnOffBool:
				case LongBool:
				case ShortBool:
				case Dec:
				case Hex:
				case Oct:
				case BeginDoc:
				case EndDoc:
				case BeginSeq:
				case EndSeq:
				case Flow:
				case Block:
				case BeginMap:
				case EndMap:
				case Key:
				case Value:
				case LongKey:
					break;
			}
			break;
		case TrueFalseBool:
			switch (caseFmt) {
				case UpperCase:
					return b ? "TRUE" : "FALSE";
				case CamelCase:
					return b ? "True" : "False";
				case LowerCase:
					return b ? "true" : "false";
				case Auto:
				case TagByKind:
				case Newline:
				case EmitNonAscii:
				case EscapeNonAscii:
				case EscapeAsJson:
				case SingleQuoted:
				case DoubleQuoted:
				case Literal:
				case LowerNull:
				case UpperNull:
				case CamelNull:
				case TildeNull:
				case YesNoBool:
				case TrueFalseBool:
				case OnOffBool:
				case LongBool:
				case ShortBool:
				case Dec:
				case Hex:
				case Oct:
				case BeginDoc:
				case EndDoc:
				case BeginSeq:
				case EndSeq:
				case Flow:
				case Block:
				case BeginMap:
				case EndMap:
				case Key:
				case Value:
				case LongKey:
					break;
			}
			break;
		case Auto:
		case TagByKind:
		case Newline:
		case EmitNonAscii:
		case EscapeNonAscii:
		case EscapeAsJson:
		case SingleQuoted:
		case DoubleQuoted:
		case Literal:
		case LowerNull:
		case UpperNull:
		case CamelNull:
		case TildeNull:
		case UpperCase:
		case LowerCase:
		case CamelCase:
		case LongBool:
		case ShortBool:
		case Dec:
		case Hex:
		case Oct:
		case BeginDoc:
		case EndDoc:
		case BeginSeq:
		case EndSeq:
		case Flow:
		case Block:
		case BeginMap:
		case EndMap:
		case Key:
		case Value:
		case LongKey:
			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 Auto:
		case TagByKind:
		case Newline:
		case EmitNonAscii:
		case EscapeNonAscii:
		case EscapeAsJson:
		case SingleQuoted:
		case DoubleQuoted:
		case Literal:
		case TildeNull:
		case YesNoBool:
		case TrueFalseBool:
		case OnOffBool:
		case UpperCase:
		case LowerCase:
		case CamelCase:
		case LongBool:
		case ShortBool:
		case Dec:
		case Hex:
		case Oct:
		case BeginDoc:
		case EndDoc:
		case BeginSeq:
		case EndSeq:
		case Flow:
		case Block:
		case BeginMap:
		case EndMap:
		case Key:
		case Value:
		case LongKey:
		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 YAML
