/*
* $Id: json_util.c,v 1.4 2006/01/30 23:07:57 mclark Exp $
*
* Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd.
* Michael Clark <michael@metaparadigm.com>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See COPYING for details.
*
*/

#include "config.h"
#undef realloc

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <limits.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif /* HAVE_SYS_TYPES_H */

#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif /* HAVE_SYS_STAT_H */

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif /* HAVE_FCNTL_H */

#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif /* HAVE_UNISTD_H */

#ifdef WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <io.h>
#endif /* defined(WIN32) */

#if !defined(HAVE_OPEN) && defined(WIN32)
# define open _open
#endif

#if !defined(HAVE_SNPRINTF) && defined(_MSC_VER)
/* MSC has the version as _snprintf */
# define snprintf _snprintf
#elif !defined(HAVE_SNPRINTF)
# error You do not have snprintf on your system.
#endif /* HAVE_SNPRINTF */

//#include "debug.h"
#include "printbuf.h"
#include "json_inttypes.h"
#include "json_object.h"
#include "json_tokener.h"
#include "json_util.h"

//static int sscanf_is_broken = 0;
//static int sscanf_is_broken_testdone = 0;

static void sscanf_is_broken_test(PRUNTIME pRuntime);

/*
* Create a JSON object from already opened file descriptor.
*
* This function can be helpful, when you opened the file already,
* e.g. when you have a temp file.
* Note, that the fd must be readable at the actual position, i.e.
* use lseek(fd, 0, SEEK_SET) before.
*/
struct json_object* json_object_from_fd(PRUNTIME pRuntime, int fd)
{
	struct printbuf *pb;
	struct json_object *obj;
	char buf[JSON_FILE_BUF_SIZE];
	int ret;

	if (!(pb = printbuf_new(pRuntime))) {
		//MC_ERROR("json_object_from_file: printbuf_new failed\n");
		return NULL;
	}
	while ((ret = API_READ(fd, buf, JSON_FILE_BUF_SIZE)) > 0) {
		printbuf_memappend(pRuntime, pb, buf, ret);
	}
	if (ret < 0) {
		//MC_ERROR("json_object_from_fd: error reading fd %d: %s\n", fd, strerror(errno));
		printbuf_free(pRuntime, pb);
		return NULL;
	}
	obj = json_tokener_parse(pRuntime, pb->buf);
	printbuf_free(pRuntime, pb);
	return obj;
}

struct json_object* json_object_from_file(PRUNTIME pRuntime, const char *filename)
{
	struct json_object *obj;
	int fd;

	if ((fd = API__OPEN(filename, O_RDONLY)) < 0) {
		//MC_ERROR("json_object_from_file: error opening file %s: %s\n",filename, strerror(errno));
		return NULL;
	}
	obj = json_object_from_fd(pRuntime, fd);
	API__CLOSE(fd);
	return obj;
}

/* extended "format and write to file" function */

int json_object_to_file_ext(PRUNTIME pRuntime, const char *filename, struct json_object *obj, int flags)
{
	const char *json_str;
	int fd, ret;
	unsigned int wpos, wsize;

	if (!obj) {
		//MC_ERROR("json_object_to_file: object is null\n");
		return -1;
	}

	if ((fd = API__OPEN(filename, O_WRONLY | O_TRUNC | O_CREAT, 0644)) < 0) {
		//MC_ERROR("json_object_to_file: error opening file %s: %s\n",	filename, strerror(errno));
		return -1;
	}

	if (!(json_str = json_object_to_json_string_ext(pRuntime, obj, flags))) {
		API__CLOSE(fd);
		return -1;
	}

	wsize = (unsigned int)(strlen(json_str) & UINT_MAX); /* CAW: probably unnecessary, but the most 64bit safe */
	wpos = 0;
	while (wpos < wsize) {
		if ((ret = API__WRITE(fd, json_str + wpos, wsize - wpos)) < 0) {
			API__CLOSE(fd);
			//MC_ERROR("json_object_to_file: error writing file %s: %s\n",	filename, strerror(errno));
			return -1;
		}

		/* because of the above check for ret < 0, we can safely cast and add */
		wpos += (unsigned int)ret;
	}

	API__CLOSE(fd);
	return 0;
}

// backwards compatible "format and write to file" function

int json_object_to_file(PRUNTIME pRuntime, const char *filename, struct json_object *obj)
{
	return json_object_to_file_ext(pRuntime, filename, obj, JSON_C_TO_STRING_PLAIN);
}

int json_parse_double(PRUNTIME pRuntime, const char *buf, double *retval)
{
	PSTRINGS_CONFIGURATION pStrings = (PSTRINGS_CONFIGURATION)(pRuntime->pModuleSpecificData[INDEX_CONFIGURATION]->lpStrings);

	return (API_SSCANF(buf, pStrings->strLf, retval) == 1 ? 0 : 1); // "%lf"
}

/*
* Not all implementations of sscanf actually work properly.
* Check whether the one we're currently using does, and if
* it's broken, enable the workaround code.
*/
static void sscanf_is_broken_test(PRUNTIME pRuntime)
{
	PSTRINGS_CONFIGURATION pStrings = (PSTRINGS_CONFIGURATION)(pRuntime->pModuleSpecificData[INDEX_CONFIGURATION]->lpStrings);

	PVARIABLES_CONFIGURATION pVariables = (PVARIABLES_CONFIGURATION)(pRuntime->pModuleSpecificData[INDEX_CONFIGURATION]->lpVariables);

	int64_t num64;
	int ret_errno, is_int64_min, ret_errno2, is_int64_max;

	(void)API_SSCANF(pStrings->strSpaceNegativeNumbers, pStrings->strLld, &num64); //" -01234567890123456789012345", "%" SCNd64
	ret_errno = errno;
	is_int64_min = (num64 == INT64_MIN);

	(void)API_SSCANF(pStrings->strSpacePositiveNumbers, pStrings->strLld, &num64); //" 01234567890123456789012345", "%" SCNd64
	ret_errno2 = errno;
	is_int64_max = (num64 == INT64_MAX);

	if (ret_errno != ERANGE || !is_int64_min ||
		ret_errno2 != ERANGE || !is_int64_max)
	{
		//MC_DEBUG("sscanf_is_broken_test failed, enabling workaround code\n");
		
		pVariables->sscanf_is_broken = 1;
	}
}

int json_parse_int64(PRUNTIME pRuntime, const char *buf, int64_t *retval)
{
	PSTRINGS_CONFIGURATION pStrings = (PSTRINGS_CONFIGURATION)(pRuntime->pModuleSpecificData[INDEX_CONFIGURATION]->lpStrings);

	PVARIABLES_CONFIGURATION pVariables = (PVARIABLES_CONFIGURATION)(pRuntime->pModuleSpecificData[INDEX_CONFIGURATION]->lpVariables);

	int64_t num64;
	const char *buf_sig_digits;
	int orig_has_neg;
	int saved_errno;

	if (!pVariables->sscanf_is_broken_testdone)
	{
		sscanf_is_broken_test(pRuntime);
		pVariables->sscanf_is_broken_testdone = 1;
	}

	// Skip leading spaces
	while (API_ISSPACE((int)*buf) && *buf)
		buf++;

	errno = 0; // sscanf won't always set errno, so initialize
	if (API_SSCANF(buf, pStrings->strLld, &num64) != 1) // "%" SCNd64
	{
		//MC_DEBUG("Failed to parse, sscanf != 1\n");
		return 1;
	}

	saved_errno = errno;
	buf_sig_digits = buf;
	orig_has_neg = 0;
	if (*buf_sig_digits == '-')
	{
		buf_sig_digits++;
		orig_has_neg = 1;
	}

	// Not all sscanf implementations actually work
	if (pVariables->sscanf_is_broken && saved_errno != ERANGE)
	{
		char buf_cmp[100];
		char *buf_cmp_start = buf_cmp;
		int recheck_has_neg = 0;
		int buf_cmp_len;

		// Skip leading zeros, but keep at least one digit
		while (buf_sig_digits[0] == '0' && buf_sig_digits[1] != '\0')
			buf_sig_digits++;
		if (num64 == 0) // assume all sscanf impl's will parse -0 to 0
			orig_has_neg = 0; // "-0" is the same as just plain "0"

		API__SNPRINTF(buf_cmp_start, sizeof(buf_cmp), pStrings->strLld, num64);  //"%" PRId64
		if (*buf_cmp_start == '-')
		{
			recheck_has_neg = 1;
			buf_cmp_start++;
		}
		// No need to skip leading spaces or zeros here.

		buf_cmp_len = API_STRLEN(buf_cmp_start);
		/**
		* If the sign is different, or
		* some of the digits are different, or
		* there is another digit present in the original string
		* then we have NOT successfully parsed the value.
		*/
		if (orig_has_neg != recheck_has_neg ||
			strncmp(buf_sig_digits, buf_cmp_start, API_STRLEN(buf_cmp_start)) != 0 ||
			((int)API_STRLEN(buf_sig_digits) != buf_cmp_len &&
			API_ISDIGIT((int)buf_sig_digits[buf_cmp_len])
			)
			)
		{
			saved_errno = ERANGE;
		}
	}

	// Not all sscanf impl's set the value properly when out of range.
	// Always do this, even for properly functioning implementations,
	// since it shouldn't slow things down much.
	if (saved_errno == ERANGE)
	{
		if (orig_has_neg)
			num64 = INT64_MIN;
		else
			num64 = INT64_MAX;
	}
	*retval = num64;
	return 0;
}

#ifndef HAVE_REALLOC
void* rpl_realloc(void* p, size_t n)
{
	if (n == 0)
		n = 1;
	if (p == 0)
		return malloc(n);
	return realloc(p, n);
}
#endif

#define NELEM(a)        (sizeof(a) / sizeof(a[0]))
static const char* json_type_name[] = {
	/* If you change this, be sure to update the enum json_type definition too */
	"null",
	"boolean",
	"double",
	"int",
	"object",
	"array",
	"string",
};

const char *json_type_to_name(enum json_type o_type)
{
	int o_type_int = (int)o_type;
	if (o_type_int < 0 || o_type_int >= (int)NELEM(json_type_name))
	{
		//MC_ERROR("json_type_to_name: type %d is out of range [0,%d]\n", o_type, NELEM(json_type_name));
		return NULL;
	}
	return NULL;// json_type_name[o_type];
}

