/*
 * CoreFreq (C) 2015-2026 CYRIL COURTIAT
 * Contributors: Andrew Gurinovich ; CyrIng
 * Licenses: GPL2
 *
 * Some ideas taken from https://github.com/cesanta/frozen/ 
 * under Apache 2.0 license
 */

#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>

#include "corefreq-cli-extra.h"

#define UNUSED(expr) do { (void)(expr); } while (0)

/*				--[ JSON toolkit ]--			*/
struct json_state;

enum JSON_STATE {
	DEFAULT, IN_ARRAY, IN_ARRAY2, IN_OBJECT, IN_OBJECT2
};

int json_writer_stdout(struct json_state * state, const char *str, size_t len)
{
	UNUSED(state);
	return fwrite(str, len, 1, stdout);
}

size_t get_utf8_char_len(unsigned char ch)
{
	if ((ch & 0x80) == 0)
		return 1;
	switch (ch & 0xf0) {
	case 0xf0:
		return 4;
	case 0xe0:
		return 3;
	default:
		return 2;
	}
}

/* This routine does NOT handle unicode finely - everything above 0x7f should
 * be \u0000 encoded, but this requires us to be utf8 capable. Check the
 * following tiny libraries(for future improvement):
 * https://github.com/git/git/blob/master/utf8.c
 * https://github.com/JeffBezanson/cutef8/blob/master/utf8.c
 * https://github.com/sheredom/utf8.h
 * https://github.com/JuliaStrings/utf8proc
 */

int json_escape(struct json_state *state, const char *p, size_t len)
{
	size_t i, cl;
	const char *hex_digits = "0123456789abcdef";
	const char *specials = "btnvfr";
	int n = 0;

    for (i = 0; i < len; i++) {
	unsigned char ch = ((unsigned char *) p)[i];
	if (ch == '"' || ch == '\\') {
		n += state->write(state, "\\", 1);
		n += state->write(state, p + i, 1);
	} else if (ch >= '\b' && ch <= '\r') {
		n += state->write(state, "\\", 1);
		n += state->write(state, &specials[ch - '\b'], 1);
	} else if (isprint(ch)) {
		n += state->write(state, p + i, 1);
	} else if ((cl = get_utf8_char_len(ch)) == 1) {
		n += state->write(state, "\\u00", 4);
		n += state->write(state, &hex_digits[(ch >> 4) % 0xf], 1);
		n += state->write(state, &hex_digits[ch % 0xf], 1);
	} else {
		n += state->write(state, p + i, cl);
		i += cl - 1;
	}
    }
	return n;
}

void json_start_object(struct json_state *state)
{
	assert(state->depth < JSON_MAX_DEPTH);
	/* TODO: assert(state->nested_state[state->depth] != IN_OBJECT); */
	if (state->nested_state[state->depth] == IN_ARRAY2 ) {
		state->write(state, ", {", 3);
	} else {
		state->write(state, "{", 1);
	}
	if (state->nested_state[state->depth] == IN_ARRAY) {
		state->nested_state[state->depth] = IN_ARRAY2;
	}
	if (state->nested_state[state->depth] == IN_OBJECT) {
		state->nested_state[state->depth] = IN_OBJECT2;
	}
	state->nested_state[++state->depth] = IN_OBJECT;
}

void json_end_object(struct json_state *state)
{
	assert(state->depth >= 0);
	assert(state->nested_state[state->depth] == \
		IN_OBJECT || state->nested_state[state->depth] == IN_OBJECT2);

	state->write(state, "}", 1);
	state->nested_state[state->depth--] = DEFAULT;
}

void json_start_arr(struct json_state *state)
{
	assert(state->depth < JSON_MAX_DEPTH);

	if (state->nested_state[state->depth] == IN_ARRAY2) {
		state->write(state, ", [", 3);
	} else {
		state->write(state, "[", 1);
	}
	if (state->nested_state[state->depth] == IN_ARRAY) {
		state->nested_state[state->depth] = IN_ARRAY2;
	}
	if (state->nested_state[state->depth] == IN_OBJECT) {
		state->nested_state[state->depth] = IN_OBJECT2;
	}
	state->nested_state[++state->depth] = IN_ARRAY;
}

void json_end_arr(struct json_state *state)
{
	assert(state->depth >= 0);
	assert(state->nested_state[state->depth] == \
		IN_ARRAY || state->nested_state[state->depth] == IN_ARRAY2);

	state->write(state, "]", 1);
	state->nested_state[state->depth--] = DEFAULT;
}

void json_key(struct json_state *state, char * key)
{
	assert(state->nested_state[state->depth] == \
		IN_OBJECT || state->nested_state[state->depth] == IN_OBJECT2);

	if (state->nested_state[state->depth] == IN_OBJECT2) {
		state->write(state, ", ", 1);
	}
	state->write(state, "\"", 1);
	json_escape(state, key, strlen(key));
	state->write(state, "\":", 2);
}

void json_string(struct json_state *state, char * value)
{
	assert(state->nested_state[state->depth] != DEFAULT);

	if (state->nested_state[state->depth] == IN_ARRAY2) {
		state->write(state, ", ", 2);
	}
	if (state->nested_state[state->depth] == IN_ARRAY) {
		state->nested_state[state->depth] = IN_ARRAY2;
	}
	if (state->nested_state[state->depth] == IN_OBJECT) {
		state->nested_state[state->depth] = IN_OBJECT2;
	}
	state->write(state, "\"", 1);
	json_escape(state, value, strlen(value));
	state->write(state, "\"", 1);
}

void json_literal(struct json_state *state, char * format, ...)
{
	assert(state->nested_state[state->depth] != DEFAULT);

	if (state->nested_state[state->depth] == IN_ARRAY2) {
		state->write(state, ", ", 2);
	}
	if (state->nested_state[state->depth] == IN_ARRAY) {
		state->nested_state[state->depth] = IN_ARRAY2;
	}
	if (state->nested_state[state->depth] == IN_OBJECT) {
		state->nested_state[state->depth] = IN_OBJECT2;
	}
	va_list args;
	va_start(args, format);
	char buf[JSON_MAX_VALUE];
	const int rc = vsprintf(buf, format, args);
	const size_t bufsz = rc > 0 ? (size_t) rc : 0;
	state->write(state, buf, bufsz);
	va_end(args);
}

/*				--[ UTF toolkit ]--			*/

void ISO_8859_To_Unicode(unsigned char *pIn, unsigned char *pOut)
{
	while ( (*pIn) != '\0' ) {
		if ( (*pIn) > 0x7f ) {
			switch ( (*pIn) ) {
			case 0xc0 ... 0xff:	/* To UTF-8 Ctrl:C3 */
				(*pOut) = (*pIn) ^ 0x40;
				break;
			case 0xa0 ... 0xbf:	/* To UTF-8 Ctrl:C2 */
				(*pOut) = (*pIn) ^ 0x60;
				break;
			default:
				(*pOut) = 0x20;
				break;
			}
		} else {
			(*pOut) = (*pIn);
		}
		pIn++;
		pOut++;
	}
	(*pOut) = '\0';
}
