#include "JsonWriter.h"

#include <iomanip>
#include <stdexcept>

namespace RenderCadenceCompositor
{
namespace
{
constexpr int kMaxDepth = 32;

void AppendHexEscape(std::ostringstream& stream, unsigned char value)
{
	stream << "\\u"
		<< std::hex << std::uppercase << std::setw(4) << std::setfill('0')
		<< static_cast<int>(value)
		<< std::dec << std::nouppercase << std::setfill(' ');
}
}

void JsonWriter::BeginObject()
{
	BeginValue();
	mStream << "{";
	PushScope(ScopeKind::Object);
}

void JsonWriter::EndObject()
{
	PopScope(ScopeKind::Object);
	mStream << "}";
}

void JsonWriter::BeginArray()
{
	BeginValue();
	mStream << "[";
	PushScope(ScopeKind::Array);
}

void JsonWriter::EndArray()
{
	PopScope(ScopeKind::Array);
	mStream << "]";
}

void JsonWriter::Key(const std::string& name)
{
	BeginKey();
	mStream << "\"" << EscapeString(name) << "\":";
}

void JsonWriter::String(const std::string& value)
{
	BeginValue();
	mStream << "\"" << EscapeString(value) << "\"";
}

void JsonWriter::Bool(bool value)
{
	BeginValue();
	mStream << (value ? "true" : "false");
}

void JsonWriter::Null()
{
	BeginValue();
	mStream << "null";
}

void JsonWriter::Int(int64_t value)
{
	BeginValue();
	mStream << value;
}

void JsonWriter::UInt(uint64_t value)
{
	BeginValue();
	mStream << value;
}

void JsonWriter::Double(double value)
{
	BeginValue();
	mStream << std::setprecision(15) << value;
}

void JsonWriter::KeyString(const std::string& name, const std::string& value)
{
	Key(name);
	String(value);
}

void JsonWriter::KeyBool(const std::string& name, bool value)
{
	Key(name);
	Bool(value);
}

void JsonWriter::KeyNull(const std::string& name)
{
	Key(name);
	Null();
}

void JsonWriter::KeyInt(const std::string& name, int64_t value)
{
	Key(name);
	Int(value);
}

void JsonWriter::KeyUInt(const std::string& name, uint64_t value)
{
	Key(name);
	UInt(value);
}

void JsonWriter::KeyDouble(const std::string& name, double value)
{
	Key(name);
	Double(value);
}

std::string JsonWriter::StringValue() const
{
	if (mScopeDepth != 0)
		throw std::logic_error("JSON document has unclosed scopes.");
	return mStream.str();
}

void JsonWriter::Reset()
{
	mStream.str(std::string());
	mStream.clear();
	mScopeDepth = 0;
}

std::string JsonWriter::EscapeString(const std::string& value)
{
	std::ostringstream stream;
	for (unsigned char character : value)
	{
		switch (character)
		{
		case '"':
			stream << "\\\"";
			break;
		case '\\':
			stream << "\\\\";
			break;
		case '\b':
			stream << "\\b";
			break;
		case '\f':
			stream << "\\f";
			break;
		case '\n':
			stream << "\\n";
			break;
		case '\r':
			stream << "\\r";
			break;
		case '\t':
			stream << "\\t";
			break;
		default:
			if (character < 0x20)
				AppendHexEscape(stream, character);
			else
				stream << character;
			break;
		}
	}
	return stream.str();
}

void JsonWriter::BeginValue()
{
	if (mScopeDepth == 0)
		return;

	Scope& scope = mScopes[mScopeDepth - 1];
	if (scope.kind == ScopeKind::Object)
	{
		if (!scope.expectingValue)
			throw std::logic_error("JSON object value must follow a key.");
		scope.expectingValue = false;
		return;
	}

	if (!scope.first)
		mStream << ",";
	scope.first = false;
}

void JsonWriter::BeginKey()
{
	if (mScopeDepth == 0)
		throw std::logic_error("JSON key cannot be written outside an object.");

	Scope& scope = mScopes[mScopeDepth - 1];
	if (scope.kind != ScopeKind::Object)
		throw std::logic_error("JSON key cannot be written inside an array.");
	if (scope.expectingValue)
		throw std::logic_error("JSON object key cannot be written before its previous value.");

	if (!scope.first)
		mStream << ",";
	scope.first = false;
	scope.expectingValue = true;
}

void JsonWriter::PushScope(ScopeKind kind)
{
	if (mScopeDepth >= kMaxDepth)
		throw std::logic_error("JSON nesting is too deep.");

	mScopes[mScopeDepth++] = Scope{ kind, true, false };
}

void JsonWriter::PopScope(ScopeKind kind)
{
	if (mScopeDepth == 0)
		throw std::logic_error("JSON scope underflow.");

	Scope& scope = mScopes[mScopeDepth - 1];
	if (scope.kind != kind)
		throw std::logic_error("JSON scope kind mismatch.");
	if (scope.expectingValue)
		throw std::logic_error("JSON object key is missing a value.");

	--mScopeDepth;
}
}
