/*
 * Copyright (c) 2007-2011, Lloyd Hilaiel <lloyd@hilaiel.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "api/yajl_gen.h"
#include "yajl_buf.h"
#include "yajl_encode.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdarg.h>

typedef enum {
	yajl_gen_start,
	yajl_gen_map_start,
	yajl_gen_map_key,
	yajl_gen_map_val,
	yajl_gen_array_start,
	yajl_gen_in_array,
	yajl_gen_complete,
	yajl_gen_error
} yajl_gen_state;

struct yajl_gen_t {
	unsigned int flags;
	unsigned int depth;
	const char *indentString;
	yajl_gen_state state[YAJL_MAX_DEPTH];
	yajl_print_t print;
	void *ctx; /* yajl_buf */
	/* memory allocation routines */
	yajl_alloc_funcs alloc;
};

int
yajl_gen_config(yajl_gen g, yajl_gen_option opt, ...) {
	int rv = 1;
	va_list ap;
	va_start(ap, opt);

	switch (opt) {
		case yajl_gen_beautify:
		case yajl_gen_validate_utf8:
			if (va_arg(ap, int)) g->flags |= opt;
			else g->flags &= ~opt;
			break;
		case yajl_gen_indent_string: {
			const char *indent = va_arg(ap, const char *);
			g->indentString = indent;
			for (; *indent; indent++) {
				if (*indent != '\n'
				    && *indent != '\v'
				    && *indent != '\f'
				    && *indent != '\t'
				    && *indent != '\r'
				    && *indent != ' ') {
					g->indentString = NULL;
					rv = 0;
				}
			}
			break;
		}
		case yajl_gen_print_callback:
			yajl_buf_free(g->ctx);
			g->print = va_arg(ap, const yajl_print_t);
			g->ctx = va_arg(ap, void *);
			break;
		default:
			rv = 0;
	}

	va_end(ap);

	return rv;
}


yajl_gen
yajl_gen_alloc(const yajl_alloc_funcs *afs) {
	yajl_gen g = NULL;
	yajl_alloc_funcs afsBuffer;

	/* first order of business is to set up memory allocation routines */
	if (afs != NULL) {
		if (afs->malloc == NULL || afs->realloc == NULL || afs->free == NULL) {
			return NULL;
		}
	} else {
		yajl_set_default_alloc_funcs(&afsBuffer);
		afs = &afsBuffer;
	}

	g = (yajl_gen) YA_MALLOC(afs, sizeof(struct yajl_gen_t));
	if (!g) return NULL;

	memset((void *) g, 0, sizeof(struct yajl_gen_t));
	/* copy in pointers to allocation routines */
	memcpy((void *) &(g->alloc), (void *) afs, sizeof(yajl_alloc_funcs));

	g->print = (yajl_print_t) &yajl_buf_append;
	g->ctx = yajl_buf_alloc(&(g->alloc));
	g->indentString = "    ";

	return g;
}

void
yajl_gen_free(yajl_gen g) {
	if (g->print == (yajl_print_t) &yajl_buf_append) yajl_buf_free((yajl_buf) g->ctx);
	YA_FREE(&(g->alloc), g);
}

#define INSERT_SEP \
    if (g->state[g->depth] == yajl_gen_map_key ||               \
        g->state[g->depth] == yajl_gen_in_array) {              \
        g->print(g->ctx, ",", 1);                               \
        if ((g->flags & yajl_gen_beautify)) g->print(g->ctx, "\n", 1);               \
    } else if (g->state[g->depth] == yajl_gen_map_val) {        \
        g->print(g->ctx, ":", 1);                               \
        if ((g->flags & yajl_gen_beautify)) g->print(g->ctx, " ", 1);                \
   }

#define INSERT_WHITESPACE                                               \
    if ((g->flags & yajl_gen_beautify)) {                                                    \
        if (g->state[g->depth] != yajl_gen_map_val) {                   \
            unsigned int _i;                                            \
            for (_i=0;_i<g->depth;_i++)                                 \
                g->print(g->ctx,                                        \
                         g->indentString,                               \
                         (unsigned int)strlen(g->indentString));        \
        }                                                               \
    }

#define ENSURE_NOT_KEY \
    if (g->state[g->depth] == yajl_gen_map_key ||       \
        g->state[g->depth] == yajl_gen_map_start)  {    \
        return yajl_gen_keys_must_be_strings;           \
    }                                                   \

/* check that we're not complete, or in error state.  in a valid state
 * to be generating */
#define ENSURE_VALID_STATE \
    if (g->state[g->depth] == yajl_gen_error) {   \
        return yajl_gen_in_error_state;\
    } else if (g->state[g->depth] == yajl_gen_complete) {   \
        return yajl_gen_generation_complete;                \
    }

#define INCREMENT_DEPTH \
    if (++(g->depth) >= YAJL_MAX_DEPTH) return yajl_max_depth_exceeded;

#define DECREMENT_DEPTH \
  if (--(g->depth) >= YAJL_MAX_DEPTH) return yajl_gen_error;

#define APPENDED_ATOM \
    switch (g->state[g->depth]) {                   \
        case yajl_gen_start:                        \
            g->state[g->depth] = yajl_gen_complete; \
            break;                                  \
        case yajl_gen_map_start:                    \
        case yajl_gen_map_key:                      \
            g->state[g->depth] = yajl_gen_map_val;  \
            break;                                  \
        case yajl_gen_array_start:                  \
            g->state[g->depth] = yajl_gen_in_array; \
            break;                                  \
        case yajl_gen_map_val:                      \
            g->state[g->depth] = yajl_gen_map_key;  \
            break;                                  \
        default:                                    \
            break;                                  \
    }                                               \

#define FINAL_NEWLINE                                        \
    if ((g->flags & yajl_gen_beautify) && g->state[g->depth] == yajl_gen_complete) \
        g->print(g->ctx, "\n", 1);

yajl_gen_status
yajl_gen_integer(yajl_gen g, long long int number) {
	char i[32];
	ENSURE_VALID_STATE;
	ENSURE_NOT_KEY;
	INSERT_SEP;
	INSERT_WHITESPACE;
	sprintf(i, "%lld", number);
	g->print(g->ctx, i, (unsigned int) strlen(i));
	APPENDED_ATOM;
	FINAL_NEWLINE;
	return yajl_gen_status_ok;
}

#ifdef WIN32
#include <float.h>
#define isnan _isnan
#define isinf !_finite
#endif

yajl_gen_status
yajl_gen_double(yajl_gen g, double number) {
	char i[32];
	ENSURE_VALID_STATE;
	ENSURE_NOT_KEY;
	if (isnan(number) || isinf(number)) return yajl_gen_invalid_number;
	INSERT_SEP;
	INSERT_WHITESPACE;
	sprintf(i, "%.20g", number);
	g->print(g->ctx, i, (unsigned int) strlen(i));
	APPENDED_ATOM;
	FINAL_NEWLINE;
	return yajl_gen_status_ok;
}

yajl_gen_status
yajl_gen_number(yajl_gen g, const char *s, size_t l) {
	ENSURE_VALID_STATE;
	ENSURE_NOT_KEY;
	INSERT_SEP;
	INSERT_WHITESPACE;
	g->print(g->ctx, s, l);
	APPENDED_ATOM;
	FINAL_NEWLINE;
	return yajl_gen_status_ok;
}

yajl_gen_status
yajl_gen_string(yajl_gen g, const unsigned char *str,
                size_t len) {
	// if validation is enabled, check that the string is valid utf8
	// XXX: This checking could be done a little faster, in the same pass as
	// the string encoding
	if (g->flags & yajl_gen_validate_utf8) {
		if (!yajl_string_validate_utf8(str, len)) {
			return yajl_gen_invalid_string;
		}
	}
	ENSURE_VALID_STATE;
	INSERT_SEP;
	INSERT_WHITESPACE;
	g->print(g->ctx, "\"", 1);
	yajl_string_encode(g->print, g->ctx, str, len, g->flags & yajl_gen_escape_solidus);
	g->print(g->ctx, "\"", 1);
	APPENDED_ATOM;
	FINAL_NEWLINE;
	return yajl_gen_status_ok;
}

yajl_gen_status
yajl_gen_null(yajl_gen g) {
	ENSURE_VALID_STATE;
	ENSURE_NOT_KEY;
	INSERT_SEP;
	INSERT_WHITESPACE;
	g->print(g->ctx, "null", strlen("null"));
	APPENDED_ATOM;
	FINAL_NEWLINE;
	return yajl_gen_status_ok;
}

yajl_gen_status
yajl_gen_bool(yajl_gen g, int boolean) {
	const char *val = boolean ? "true" : "false";

	ENSURE_VALID_STATE;
	ENSURE_NOT_KEY;
	INSERT_SEP;
	INSERT_WHITESPACE;
	g->print(g->ctx, val, (unsigned int) strlen(val));
	APPENDED_ATOM;
	FINAL_NEWLINE;
	return yajl_gen_status_ok;
}

yajl_gen_status
yajl_gen_map_open(yajl_gen g) {
	ENSURE_VALID_STATE;
	ENSURE_NOT_KEY;
	INSERT_SEP;
	INSERT_WHITESPACE;
	INCREMENT_DEPTH;

	g->state[g->depth] = yajl_gen_map_start;
	g->print(g->ctx, "{", 1);
	if ((g->flags & yajl_gen_beautify)) g->print(g->ctx, "\n", 1);
	FINAL_NEWLINE;
	return yajl_gen_status_ok;
}

yajl_gen_status
yajl_gen_map_close(yajl_gen g) {
	ENSURE_VALID_STATE;
	DECREMENT_DEPTH;

	if ((g->flags & yajl_gen_beautify)) g->print(g->ctx, "\n", 1);
	APPENDED_ATOM;
	INSERT_WHITESPACE;
	g->print(g->ctx, "}", 1);
	FINAL_NEWLINE;
	return yajl_gen_status_ok;
}

yajl_gen_status
yajl_gen_array_open(yajl_gen g) {
	ENSURE_VALID_STATE;
	ENSURE_NOT_KEY;
	INSERT_SEP;
	INSERT_WHITESPACE;
	INCREMENT_DEPTH;
	g->state[g->depth] = yajl_gen_array_start;
	g->print(g->ctx, "[", 1);
	if ((g->flags & yajl_gen_beautify)) g->print(g->ctx, "\n", 1);
	FINAL_NEWLINE;
	return yajl_gen_status_ok;
}

yajl_gen_status
yajl_gen_array_close(yajl_gen g) {
	ENSURE_VALID_STATE;
	DECREMENT_DEPTH;
	if ((g->flags & yajl_gen_beautify)) g->print(g->ctx, "\n", 1);
	APPENDED_ATOM;
	INSERT_WHITESPACE;
	g->print(g->ctx, "]", 1);
	FINAL_NEWLINE;
	return yajl_gen_status_ok;
}

yajl_gen_status
yajl_gen_get_buf(yajl_gen g, const unsigned char **buf,
                 size_t *len) {
	if (g->print != (yajl_print_t) &yajl_buf_append) return yajl_gen_no_buf;
	*buf = yajl_buf_data((yajl_buf) g->ctx);
	*len = yajl_buf_len((yajl_buf) g->ctx);
	return yajl_gen_status_ok;
}

void
yajl_gen_clear(yajl_gen g) {
	if (g->print == (yajl_print_t) &yajl_buf_append) yajl_buf_clear((yajl_buf) g->ctx);
}
