/*
 * Copyright (c) 2018-2019, Linaro Ltd.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors
 * may be used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#include <sys/stat.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <lzma.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "json.h"

static const char *input_buf;
static int input_pos;
static int input_len;

static int json_parse_array(struct json_value *array);
static int json_parse_object(struct json_value *object);
static int json_parse_property(struct json_value *value);

static int input(void)
{
	if (input_pos >= input_len)
		return 0;

	return input_buf[input_pos++];
}

static void unput(void)
{
	input_pos--;
}

static void json_skip_whitespace(void)
{
	int ch;

	while ((ch = input()) && isspace(ch))
		;
	unput();
}

static int json_parse_string(struct json_value *value)
{
	char buf[128];
	char *b = buf;
	int ch;

	ch = input();
	if (ch != '"') {
		unput();
		return 0;
	}

	while ((ch = input()) && ch != '"' && b - buf < sizeof(buf) - 1)
		*b++ = ch;
	*b = '\0';

	if (!ch)
		return -1;

	value->type = JSON_TYPE_STRING;
	value->u.string = strdup(buf);

	return 1;
}

static int json_parse_number(struct json_value *value)
{
	char buf[20];
	char *b = buf;
	int ch;

	while ((ch = input()) && isdigit(ch) && b - buf < sizeof(buf) - 1)
		*b++ = ch;
	*b = '\0';
	unput();

	if (b == buf)
		return 0;

	value->type = JSON_TYPE_NUMBER;
	value->u.number = strtod(buf, NULL);

	return 1;
}

static int json_parse_keyword(struct json_value *value)
{
	const char *match;
	const char *m;
	int ch;

	ch = input();
	switch (ch) {
	case 't':
		match = "true";
		value->type = JSON_TYPE_TRUE;
		break;
	case 'f':
		match = "false";
		value->type = JSON_TYPE_FALSE;
		break;
	case 'n':
		match = "null";
		value->type = JSON_TYPE_NULL;
		break;
	default:
		unput();
		return 0;
	}

	m = match;
	while (*m && *m++ == ch)
		ch = input();
	unput();

	return *m == '\0' ? 1 : -1;
}

static int json_parse_value(struct json_value *value)
{
	int ret;

	json_skip_whitespace();

	ret = json_parse_object(value);
	if (ret)
		goto out;

	ret = json_parse_array(value);
	if (ret)
		goto out;

	ret = json_parse_string(value);
	if (ret)
		goto out;

	ret = json_parse_number(value);
	if (ret)
		goto out;

	ret = json_parse_keyword(value);
	if (ret)
		goto out;

	fprintf(stderr, "unable to match a value\n");
	return -1;

out:
	json_skip_whitespace();
	return ret;
}

static int json_parse_array(struct json_value *array)
{
	struct json_value *value;
	struct json_value *last = NULL;
	int ret;
	int ch;

	ch = input();
	if (ch != '[') {
		unput();
		return 0;
	}

	array->type = JSON_TYPE_ARRAY;
	do {
		value = calloc(1, sizeof(*value));
		if (!value)
			return -1;

		ret = json_parse_value(value);
		if (ret <= 0) {
			free(value);
			return -1;
		}

		if (!array->u.value)
			array->u.value = value;
		if (last)
			last->next = value;
		last = value;

		ch = input();
		if (ch == ']') {
			return 1;
		}

	} while (ch == ',');

	fprintf(stderr, "expected ',' got '%c'\n", ch);

	return -1;
}

static int json_parse_object(struct json_value *object)
{
	struct json_value *value;
	struct json_value *last = NULL;
	int ret;
	int ch;

	ch = input();
	if (ch != '{') {
		unput();
		return 0;
	}

	object->type = JSON_TYPE_OBJECT;

	do {
		value = calloc(1, sizeof(*value));
		if (!value)
			return -1;

		ret = json_parse_property(value);
		if (ret <= 0) {
			free(value);
			return -1;
		}

		if (!object->u.value)
			object->u.value = value;
		if (last)
			last->next = value;
		last = value;

		ch = input();
		if (ch == '}') {
			return 1;
		}
	} while (ch == ',');

	return -1;
}

static int json_parse_property(struct json_value *value)
{
	struct json_value key;
	int ret;
	int ch;

	json_skip_whitespace();

	ret = json_parse_string(&key);
	if (ret <= 0)
		return -1;

	value->key = key.u.string;

	json_skip_whitespace();

	ch = input();
	if (ch != ':')
		return -1;

	ret = json_parse_value(value);
	if (ret <= 0)
		return -1;

	return 1;
}

struct json_value *json_parse(const char *json)
{
	struct json_value *root;
	int ret;

	input_buf = json;
	input_pos = 0;
	input_len = strlen(input_buf);

	root = calloc(1, sizeof(*root));
	if (!root)
		return NULL;

	ret = json_parse_value(root);
	if (ret != 1) {
		free(root);
		return NULL;
	}

	return root;
}

extern int lzma_decomp(const char *file);

struct json_value *json_parse_file(const char *file)
{
	struct json_value *root;
	struct stat sb;
	int ret;
	int fd;

	if ((strlen(file) > 3) && !strcmp(&file[strlen(file)-3], ".xz"))
	        fd = lzma_decomp(file);
	else
	        fd = open(file, O_RDONLY);

	if (fd < 0) {
		fprintf(stderr, "failed to open %s: %s\n", file, strerror(errno));
		return NULL;
	}

	ret = fstat(fd, &sb);
	if (ret < 0)
		return NULL;

	input_pos = 0;
	input_len = sb.st_size;
	input_buf = malloc(sb.st_size);

	ret = read(fd, (char *)input_buf, input_len);

	close(fd);

	if (ret != input_len) {
		fprintf(stderr, "failed to read %d bytes form %s\n", input_len, file);
		return NULL;
	}

	root = calloc(1, sizeof(*root));
	if (!root)
		return NULL;

	ret = json_parse_value(root);
	if (ret != 1) {
		json_free(root);
		return NULL;
	}

	return root;
}

struct json_value *json_get_child(struct json_value *object, const char *key)
{
	struct json_value *it;

	if(object->type != JSON_TYPE_OBJECT)
		return NULL;

	for (it = object->u.value; it; it = it->next) {
		if (!strcmp(it->key, key))
			return it;
	}

	return NULL;
}

int json_count_children(struct json_value *array)
{
	struct json_value *it;
	int count = 0;

	if (!array || array->type != JSON_TYPE_ARRAY)
		return -1;

	for (it = array->u.value; it; it = it->next)
		count++;

	return count;
}

int json_get_number(struct json_value *object, const char *key, double *number)
{
	struct json_value *it;

	if (!object || object->type != JSON_TYPE_OBJECT)
		return -1;

	for (it = object->u.value; it; it = it->next) {
		if (!strcmp(it->key, key)) {
			if (it->type != JSON_TYPE_NUMBER)
				return -1;

			*number = it->u.number;
			return 0;
		}
	}

	return -1;
}

const char *json_get_string(struct json_value *object, const char *key)
{
	struct json_value *it;

	if (!object || object->type != JSON_TYPE_OBJECT)
		return NULL;

	for (it = object->u.value; it; it = it->next) {
		if (!strcmp(it->key, key)) {
			if (it->type != JSON_TYPE_STRING)
				return NULL;

			return it->u.string;
		}
	}

	return NULL;
}

void json_free(struct json_value *value)
{
	struct json_value *next;
	struct json_value *it;

	free((char *)value->key);

	switch (value->type) {
	case JSON_TYPE_OBJECT:
	case JSON_TYPE_ARRAY:
		it = value->u.value;
		while (it) {
			next = it->next;
			json_free(it);
			it = next;
		}
		break;
	case JSON_TYPE_STRING:
		free((char *)value->u.string);
		break;
	}

	free(value);
}
