/*
 * Copyright 2017, Andrew Lindesay <apl@lindesay.co.nz>
 * Distributed under the terms of the MIT License.
 */
 
 
#include "JsonTextWriter.h"
 
#include <stdio.h>
#include <stdlib.h>
 
#include <UnicodeChar.h>
 
 
namespace BPrivate {
 
 
static bool
b_json_is_7bit_clean(uint8 c)
{
	return c >= 0x20 && c < 0x7f;
}
 
 
static bool
b_json_is_illegal(uint8 c)
{
	return c < 0x20 || c == 0x7f;
}
 
 
static const char*
b_json_simple_esc_sequence(char c)
{
	switch (c) {
		case '"':
			return "\\\"";
		case '\\':
			return "\\\\";
		case '/':
			return "\\/";
		case '\b':
			return "\\b";
		case '\f':
			return "\\f";
		case '\n':
			return "\\n";
		case '\r':
			return "\\r";
		case '\t':
			return "\\t";
		default:
			return NULL;
	}
}
 
 
static size_t
b_json_len_7bit_clean_non_esc(uint8* c, size_t length) {
	size_t result = 0;
 
	while (result < length
		&& b_json_is_7bit_clean(c[result])
		&& b_json_simple_esc_sequence(c[result]) == NULL) {
		result++;
	}
 
	return result;
}
 
 
/*! The class and sub-classes of it are used as a stack internal to the
    BJsonTextWriter class.  As the JSON is parsed, the stack of these
    internal listeners follows the stack of the JSON parsing in terms of
    containers; arrays and objects.
*/
 
class BJsonTextWriterStackedEventListener : public BJsonEventListener {
public:
								BJsonTextWriterStackedEventListener(
									BJsonTextWriter* writer,
									BJsonTextWriterStackedEventListener* parent);
								~BJsonTextWriterStackedEventListener();
 
				bool			Handle(const BJsonEvent& event);
				void			HandleError(status_t status, int32 line,
									const char* message);
				void			Complete();
 
				status_t		ErrorStatus();
 
				BJsonTextWriterStackedEventListener*
								Parent();
 
protected:
 
			status_t			StreamNumberNode(const BJsonEvent& event);
 
			status_t			StreamStringVerbatim(const char* string);
			status_t			StreamStringVerbatim(const char* string,
									off_t offset, size_t length);
 
			status_t			StreamStringEncoded(const char* string);
			status_t			StreamStringEncoded(const char* string,
									off_t offset, size_t length);
 
			status_t			StreamQuotedEncodedString(const char* string);
			status_t			StreamQuotedEncodedString(const char* string,
									off_t offset, size_t length);
 
			status_t			StreamChar(char c);
 
		virtual	bool			WillAdd();
		virtual void			DidAdd();
 
			void				SetStackedListenerOnWriter(
									BJsonTextWriterStackedEventListener*
									stackedListener);
 
			BJsonTextWriter*
								fWriter;
			BJsonTextWriterStackedEventListener*
								fParent;
				uint32			fCount;
 
};
 
 
class BJsonTextWriterArrayStackedEventListener
	: public BJsonTextWriterStackedEventListener {
public:
								BJsonTextWriterArrayStackedEventListener(
									BJsonTextWriter* writer,
									BJsonTextWriterStackedEventListener* parent);
								~BJsonTextWriterArrayStackedEventListener();
 
				bool			Handle(const BJsonEvent& event);
 
protected:
				bool			WillAdd();
};
 
 
class BJsonTextWriterObjectStackedEventListener
	: public BJsonTextWriterStackedEventListener {
public:
								BJsonTextWriterObjectStackedEventListener(
									BJsonTextWriter* writer,
									BJsonTextWriterStackedEventListener* parent);
								~BJsonTextWriterObjectStackedEventListener();
 
				bool			Handle(const BJsonEvent& event);
};
 
} // namespace BPrivate
 
 
using BPrivate::BJsonTextWriterStackedEventListener;
using BPrivate::BJsonTextWriterArrayStackedEventListener;
using BPrivate::BJsonTextWriterObjectStackedEventListener;
 
 
// #pragma mark - BJsonTextWriterStackedEventListener
 
 
BJsonTextWriterStackedEventListener::BJsonTextWriterStackedEventListener(
	BJsonTextWriter* writer,
	BJsonTextWriterStackedEventListener* parent)
{
	fWriter = writer;
	fParent = parent;
	fCount = 0;
}
 
 
BJsonTextWriterStackedEventListener::~BJsonTextWriterStackedEventListener()
{
}
 
 
bool
BJsonTextWriterStackedEventListener::Handle(const BJsonEvent& event)
{
	status_t writeResult = B_OK;
 
	if (fWriter->ErrorStatus() != B_OK)
		return false;
 
	switch (event.EventType()) {
 
		case B_JSON_NUMBER:
		case B_JSON_STRING:
		case B_JSON_TRUE:
		case B_JSON_FALSE:
		case B_JSON_NULL:
		case B_JSON_OBJECT_START:
		case B_JSON_ARRAY_START:
			if (!WillAdd())
				return false;
			break;
 
		default:
			break;
	}
 
	switch (event.EventType()) {
 
		case B_JSON_NUMBER:
			writeResult = StreamNumberNode(event);
			break;
 
		case B_JSON_STRING:
			writeResult = StreamQuotedEncodedString(event.Content());
			break;
 
		case B_JSON_TRUE:
			writeResult = StreamStringVerbatim("true", 0, 4);
			break;
 
		case B_JSON_FALSE:
			writeResult = StreamStringVerbatim("false", 0, 5);
			break;
 
		case B_JSON_NULL:
			writeResult = StreamStringVerbatim("null", 0, 4);
			break;
 
		case B_JSON_OBJECT_START:
		{
			writeResult = StreamChar('{');
 
			if (writeResult == B_OK) {
				SetStackedListenerOnWriter(
					new BJsonTextWriterObjectStackedEventListener(
						fWriter, this));
			}
			break;
		}
 
		case B_JSON_ARRAY_START:
		{
			writeResult = StreamChar('[');
 
			if (writeResult == B_OK) {
				SetStackedListenerOnWriter(
					new BJsonTextWriterArrayStackedEventListener(
						fWriter, this));
			}
			break;
		}
 
		default:
		{
			HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE,
				"unexpected type of json item to add to container");
			return false;
		}
	}
 
	if (writeResult == B_OK)
		DidAdd();
	else {
		HandleError(writeResult, JSON_EVENT_LISTENER_ANY_LINE,
			"error writing output");
	}
 
	return ErrorStatus() == B_OK;
}
 
 
void
BJsonTextWriterStackedEventListener::HandleError(status_t status, int32 line,
	const char* message)
{
	fWriter->HandleError(status, line, message);
}
 
 
void
BJsonTextWriterStackedEventListener::Complete()
{
		// illegal state.
	HandleError(JSON_EVENT_LISTENER_ANY_LINE, B_NOT_ALLOWED,
		"Complete() called on stacked message listener");
}
 
 
status_t
BJsonTextWriterStackedEventListener::ErrorStatus()
{
	return fWriter->ErrorStatus();
}
 
 
BJsonTextWriterStackedEventListener*
BJsonTextWriterStackedEventListener::Parent()
{
	return fParent;
}
 
 
status_t
BJsonTextWriterStackedEventListener::StreamNumberNode(const BJsonEvent& event)
{
	return fWriter->StreamNumberNode(event);
}
 
 
status_t
BJsonTextWriterStackedEventListener::StreamStringVerbatim(const char* string)
{
	return fWriter->StreamStringVerbatim(string);
}
 
 
status_t
BJsonTextWriterStackedEventListener::StreamStringVerbatim(const char* string,
	off_t offset, size_t length)
{
	return fWriter->StreamStringVerbatim(string, offset, length);
}
 
 
status_t
BJsonTextWriterStackedEventListener::StreamStringEncoded(const char* string)
{
	return fWriter->StreamStringEncoded(string);
}
 
 
status_t
BJsonTextWriterStackedEventListener::StreamStringEncoded(const char* string,
	off_t offset, size_t length)
{
	return fWriter->StreamStringEncoded(string, offset, length);
}
 
 
status_t
BJsonTextWriterStackedEventListener::StreamQuotedEncodedString(
	const char* string)
{
	return fWriter->StreamQuotedEncodedString(string);
}
 
 
status_t
BJsonTextWriterStackedEventListener::StreamQuotedEncodedString(
	const char* string, off_t offset, size_t length)
{
	return fWriter->StreamQuotedEncodedString(string, offset, length);
}
 
 
status_t
BJsonTextWriterStackedEventListener::StreamChar(char c)
{
	return fWriter->StreamChar(c);
}
 
 
bool
BJsonTextWriterStackedEventListener::WillAdd()
{
	return true; // carry on
}
 
 
void
BJsonTextWriterStackedEventListener::DidAdd()
{
	fCount++;
}
 
 
void
BJsonTextWriterStackedEventListener::SetStackedListenerOnWriter(
	BJsonTextWriterStackedEventListener* stackedListener)
{
	fWriter->SetStackedListener(stackedListener);
}
 
 
// #pragma mark - BJsonTextWriterArrayStackedEventListener
 
 
BJsonTextWriterArrayStackedEventListener::BJsonTextWriterArrayStackedEventListener(
	BJsonTextWriter* writer,
	BJsonTextWriterStackedEventListener* parent)
	:
	BJsonTextWriterStackedEventListener(writer, parent)
{
}
 
 
BJsonTextWriterArrayStackedEventListener
	::~BJsonTextWriterArrayStackedEventListener()
{
}
 
 
bool
BJsonTextWriterArrayStackedEventListener::Handle(const BJsonEvent& event)
{
	status_t writeResult = B_OK;
 
	if (fWriter->ErrorStatus() != B_OK)
		return false;
 
	switch (event.EventType()) {
		case B_JSON_ARRAY_END:
		{
			writeResult = StreamChar(']');
 
			if (writeResult == B_OK) {
				SetStackedListenerOnWriter(fParent);
				delete this;
				return true; // must exit immediately after delete this.
			}
			break;
		}
 
		default:
			return BJsonTextWriterStackedEventListener::Handle(event);
	}
 
	if(writeResult != B_OK) {
		HandleError(writeResult, JSON_EVENT_LISTENER_ANY_LINE,
			"error writing output");
	}
 
	return ErrorStatus() == B_OK;
}
 
 
bool
BJsonTextWriterArrayStackedEventListener::WillAdd()
{
	status_t writeResult = B_OK;
 
	if (writeResult == B_OK && fCount > 0)
		writeResult = StreamChar(',');
 
	if (writeResult != B_OK) {
		HandleError(B_IO_ERROR, JSON_EVENT_LISTENER_ANY_LINE,
			"error writing data");
		return false;
	}
 
	return BJsonTextWriterStackedEventListener::WillAdd();
}
 
 
// #pragma mark - BJsonTextWriterObjectStackedEventListener
 
 
BJsonTextWriterObjectStackedEventListener::BJsonTextWriterObjectStackedEventListener(
	BJsonTextWriter* writer,
	BJsonTextWriterStackedEventListener* parent)
	:
	BJsonTextWriterStackedEventListener(writer, parent)
{
}
 
 
BJsonTextWriterObjectStackedEventListener
	::~BJsonTextWriterObjectStackedEventListener()
{
}
 
 
bool
BJsonTextWriterObjectStackedEventListener::Handle(const BJsonEvent& event)
{
	status_t writeResult = B_OK;
 
	if (fWriter->ErrorStatus() != B_OK)
		return false;
 
	switch (event.EventType()) {
		case B_JSON_OBJECT_END:
		{
			writeResult = StreamChar('}');
 
			if (writeResult == B_OK) {
				SetStackedListenerOnWriter(fParent);
				delete this;
				return true; // just exit after delete this.
			}
			break;
		}
 
		case B_JSON_OBJECT_NAME:
		{
			if (writeResult == B_OK && fCount > 0)
				writeResult = StreamChar(',');
 
			if (writeResult == B_OK)
				writeResult = StreamQuotedEncodedString(event.Content());
 
			if (writeResult == B_OK)
				writeResult = StreamChar(':');
 
			break;
		}
 
		default:
			return BJsonTextWriterStackedEventListener::Handle(event);
	}
 
	if (writeResult != B_OK) {
		HandleError(writeResult, JSON_EVENT_LISTENER_ANY_LINE,
			"error writing data");
	}
 
	return ErrorStatus() == B_OK;
}
 
 
// #pragma mark - BJsonTextWriter
 
 
BJsonTextWriter::BJsonTextWriter(
	BDataIO* dataIO)
	:
	fDataIO(dataIO)
{
 
		// this is a preparation for this buffer to easily be used later
		// to efficiently output encoded unicode characters.
 
	fUnicodeAssemblyBuffer[0] = '\\';
	fUnicodeAssemblyBuffer[1] = 'u';
 
	fStackedListener = new BJsonTextWriterStackedEventListener(this, NULL);
}
 
 
BJsonTextWriter::~BJsonTextWriter()
{
	BJsonTextWriterStackedEventListener* listener = fStackedListener;
 
	while (listener != NULL) {
		BJsonTextWriterStackedEventListener* nextListener = listener->Parent();
		delete listener;
		listener = nextListener;
	}
 
	fStackedListener = NULL;
}
 
 
bool
BJsonTextWriter::Handle(const BJsonEvent& event)
{
	return fStackedListener->Handle(event);
}
 
 
void
BJsonTextWriter::Complete()
{
		// upon construction, this object will add one listener to the
		// stack.  On complete, this listener should still be there;
		// otherwise this implies an unterminated structure such as array
		// / object.
 
	if (fStackedListener->Parent() != NULL) {
		HandleError(B_BAD_DATA, JSON_EVENT_LISTENER_ANY_LINE,
			"unexpected end of input data");
	}
}
 
 
void
BJsonTextWriter::SetStackedListener(
	BJsonTextWriterStackedEventListener* stackedListener)
{
	fStackedListener = stackedListener;
}
 
 
status_t
BJsonTextWriter::StreamNumberNode(const BJsonEvent& event)
{
	return StreamStringVerbatim(event.Content());
}
 
 
status_t
BJsonTextWriter::StreamStringVerbatim(const char* string)
{
	return StreamStringVerbatim(string, 0, strlen(string));
}
 
 
status_t
BJsonTextWriter::StreamStringVerbatim(const char* string,
	off_t offset, size_t length)
{
	return fDataIO->WriteExactly(&string[offset], length);
}
 
 
status_t
BJsonTextWriter::StreamStringEncoded(const char* string)
{
	return StreamStringEncoded(string, 0, strlen(string));
}
 
 
status_t
BJsonTextWriter::StreamStringUnicodeCharacter(uint32 c)
{
	sprintf(&fUnicodeAssemblyBuffer[2], "%04" B_PRIx32, c);
		// note that the buffer's first two bytes are populated with the JSON
		// prefix for a unicode char.
	return StreamStringVerbatim(fUnicodeAssemblyBuffer, 0, 6);
}
 
 
/*! Note that this method will expect a UTF-8 encoded string. */
 
status_t
BJsonTextWriter::StreamStringEncoded(const char* string,
	off_t offset, size_t length)
{
	status_t writeResult = B_OK;
	uint8* string8bit = (uint8*)string;
	size_t i = 0;
 
	while (i < length && writeResult == B_OK) {
		uint8 c = string8bit[offset + i];
		const char* simpleEsc = b_json_simple_esc_sequence(c);
 
		if (simpleEsc != NULL) {
			// here the character to emit is something like a tab or a quote
			// in this case the output JSON should escape it so that it looks
			// like \t or \n in the output.
			writeResult = StreamStringVerbatim(simpleEsc, 0, 2);
			i++;
		} else {
			if (b_json_is_7bit_clean(c)) {
				// in this case the first character is a simple one that can be
				// output without any special handling.  Find the sequence of
				// such characters and output them as a sequence so that it's
				// included as one write operation.
				size_t l = 1 + b_json_len_7bit_clean_non_esc(
					&string8bit[offset + i + 1], length - (offset + i + 1));
				writeResult = StreamStringVerbatim(&string[offset + i], 0, l);
				i += static_cast<size_t>(l);
			} else {
				if (b_json_is_illegal(c)) {
					fprintf(stderr, "! string encoding error - illegal "
						"character [%" B_PRIu32 "]\n", static_cast<uint32>(c));
					i++;
				} else {
					// now we have a UTF-8 sequence.  Read the UTF-8 sequence
					// to get the unicode character and then encode that as
					// JSON.
					const char* unicodeStr = &string[offset + i];
					uint32 unicodeCharacter = BUnicodeChar::FromUTF8(
						&unicodeStr);
					writeResult = StreamStringUnicodeCharacter(
						unicodeCharacter);
					i += static_cast<size_t>(unicodeStr - &string[offset + i]);
				}
			}
		}
	}
 
	return writeResult;
}
 
 
status_t
BJsonTextWriter::StreamQuotedEncodedString(const char* string)
{
	return StreamQuotedEncodedString(string, 0, strlen(string));
}
 
 
status_t
BJsonTextWriter::StreamQuotedEncodedString(const char* string,
	off_t offset, size_t length)
{
	status_t write_result = B_OK;
 
	if (write_result == B_OK)
		write_result = StreamChar('\"');
 
	if (write_result == B_OK)
		write_result = StreamStringEncoded(string, offset, length);
 
	if (write_result == B_OK)
		write_result = StreamChar('\"');
 
	return write_result;
}
 
 
status_t
BJsonTextWriter::StreamChar(char c)
{
	return fDataIO->WriteExactly(&c, 1);
}

V547 Expression 'write_result == ((int) 0)' is always true.