#include <sys/stat.h>
#include <sys/mman.h> 
#include <errno.h>
#include <string.h>
#include <ctype.h>

/* Copyright (C) 2011-2013 Ben Bullock <bkb@cpan.org>. */

/* This file connects the pure C JSON parser with the Perl data
   structures. It contains callers for the pure C JSON parser, and
   callback functions which are called by the pure C JSON parser as it
   gets each bit of information.

   The top part of the file is for the "json_to_perl" routine which
   actually puts each bit of the string into Perl. The bottom part of
   the file, after the letters "validation", is for the "valid_json"
   part. */

/* json_argo_t carries around any information we want to know about as
   we parse the text. */

typedef struct json_argo
{
    int verbose : 1;
    int utf8 : 1;
}
json_argo_t;

/* Debugging messages. */

#ifdef __GCC__
#define MESSAGE(...) {                                       \
        if (data->verbose) {                                 \
            printf ("%s:%d: ", __FILE__, __LINE__);          \
            printf ("" __VA_ARGS__);                         \
            printf ("\n");                                   \
        }                                                    \
    }
#else
#define MESSAGE(x,...)
#endif

static json_parse_status
json_argo_string_create (void * vdata, const char * string,
                         json_parse_u_obj * out)
{
    SV * string_sv;
    json_argo_t * data = vdata;

    MESSAGE ("Creating a string from '%s'", string);
    string_sv = newSVpv (string, 0);
    if (data->utf8) {
        SvUTF8_on (string_sv);
    }
    * out = string_sv;
    return json_parse_ok;
}

static json_parse_status
json_argo_integer_create (void * vdata, int integer,
                         json_parse_u_obj * out)
{
    SV * integer_sv;
    json_argo_t * data = vdata;

    MESSAGE ("Creating an integer SV from '%d'", integer);
    integer_sv = newSViv (integer);
    * out = integer_sv;
    return json_parse_ok;
}

static json_parse_status
json_argo_array_create (void * vdata, json_parse_u_obj * out)
{
    SV * array_sv;
    AV * array;

    MESSAGE ("Creating an array");
    array = newAV ();
    array_sv = newRV_inc ((SV *) array);
    * out = array_sv;
    return json_parse_ok;
}

static json_parse_status
json_argo_hash_create (void * vdata, json_parse_u_obj * out)
{
    SV * hash_sv;
    HV * hash;

    MESSAGE ("Creating a hash");
    hash = newHV ();
    hash_sv = newRV_inc ((SV *) hash);
    * out = hash_sv;
    return json_parse_ok;
}

#define TRUEVAL "true"

static json_parse_status
json_argo_ntf_create (void * vdata, json_type t, void ** out)
{
    SV * ntf;
    MESSAGE ("NTF");
    switch (t) {
    case json_null:
    case json_false:
        ntf = & PL_sv_undef; 
        break;
    case json_true:
        ntf = newSVpv (TRUEVAL, strlen (TRUEVAL));
        break;
    default:
        croak ("Unknown type of JSON object %d", t);
    }
    * out = ntf;
    return json_parse_ok;
}

static json_parse_status
json_argo_array_push (void * vdata, void * varray, void * velement)
{
    SV * array_sv;

    MESSAGE ("Pushing onto an array");
    if (! varray) {

    }
    array_sv = varray;
    if (SvROK (array_sv)) {
        SV * deref = SvRV (array_sv);
        if (SvTYPE (deref) == SVt_PVAV) {
            AV * array;
            SV * element;

            array = (AV*) deref;
            element = velement;
            av_push (array, element);
        }
        else {
            croak ("Error in add to array");
        }
    }
    else {
        croak ("Error in add to array");

    }
    return json_parse_ok;
}

static json_parse_status
json_argo_hash_add (void * vdata, void * vhash, void * vleft, void * vright)
{
    SV * hash_ref = vhash;
    MESSAGE ("Adding a pair to hash");
    if (SvROK (hash_ref)) {
        HV * hash = (HV*) SvRV (hash_ref);
        if (SvTYPE (hash) == SVt_PVHV) {
            SV * left;
            SV * right;
            left = vleft;
            right = vright;
            (void) hv_store_ent (hash, left, right, 0);
        }
        else {
            croak ("Error in add to hash");
        }
    }
    else {
        croak ("Error in add to hash");
    }
    return json_parse_ok;
}

#define JSON_PARSE							\
    status = json_parse (& json_bytes, jpo);				\
    if (status == json_parse_ok) {					\
        if (jpo->parse_result) {					\
            return jpo->parse_result;					\
        }								\
        else {								\
            return & PL_sv_undef;					\
        }								\
    }									\
    else if (status == json_parse_no_input_fail) {			\
        return & PL_sv_undef;						\
    }									\
    else {								\
	unsigned char bad_byte = (unsigned char) json_bytes[0];		\
	if (isprint (bad_byte)) {					\
	    croak ("Parsing failed: %s at line %d, byte %d: '%c'",	\
		   json_parse_status_messages[status],			\
		   jpo->buffer.line,					\
		   json_bytes - json_start + 1,				\
		   bad_byte);						\
	}								\
	else {								\
	    croak ("Parsing failed: %s at line %d, byte %d: '0x%2X'",	\
		   json_parse_status_messages[status],			\
		   jpo->buffer.line,					\
		   json_bytes - json_start + 1,				\
		   bad_byte );						\
	}								\
	return & PL_sv_undef;						\
    }

static void
mmap_file (const char * file_name, const char ** json_bytes_ptr,
           unsigned int * json_length_ptr)
{
    int fd;
    struct stat s;
    int json_length;
    int status;
    const char * json_bytes;

    fd = open (file_name, O_RDONLY);
    if (fd < 0) {
        croak ("Can't open %s: %s", file_name, strerror (errno));
    }
    status = fstat (fd, & s);
    if (status < 0) {
        croak ("Can't fstat %s: %s", file_name, strerror (errno));
    }
    json_length = s.st_size;
    json_bytes = mmap (0, json_length, PROT_READ, 0, fd, 0);
    if (json_bytes == MAP_FAILED) {
        croak ("mmap %s failed: %s", file_name, strerror (errno));
    }
    * json_length_ptr = json_length;
    * json_bytes_ptr = json_bytes;
}

static SV *
json_argo_parse_file (json_parse_object * jpo, const char * file_name)
{
    const char * json_bytes;
    const char * json_start;
    json_parse_status status;
    unsigned int json_length;
    mmap_file (file_name, & json_bytes, & json_length);
    JSON_PARSE;
}

/* Given JSON in a string, turn it in to Perl. */

static SV *
json_argo_parse (json_parse_object * jpo, SV * json_sv)
{
    const char * json_bytes;
    const char * json_start;
    json_parse_status status;
    unsigned int json_length;
    if (SvUTF8 (json_sv)) {
        json_argo_t * json_argo_data =  jpo->ud;
        json_argo_data->utf8 = 1;
        //Set this to 1 to get debuggering messages.
        json_argo_data->verbose = 0;
    }

    if (! SvOK (json_sv)) {
        return & PL_sv_undef;
    }
    json_start = SvPV (json_sv, json_length);
    json_bytes = json_start;
    JSON_PARSE;
}

#define INIT_JPO                                \
    json_argo_t json_argo_data = {0};           \
    json_parse_object jpo = {                   \
        json_argo_string_create,                \
        json_argo_string_create,                \
        json_argo_integer_create,               \
        json_argo_array_create,                 \
        json_argo_hash_create,                  \
        json_argo_ntf_create,                   \
        json_argo_array_push,                   \
        json_argo_hash_add,                     \
        & json_argo_data,                       \
    }


static SV *
json_argo_to_perl (SV * json_sv)
{
    INIT_JPO;
    SV * r;

    //    json_argo_data.verbose = 1;
    json_parse_init (& jpo);

    r = json_argo_parse (& jpo, json_sv);
    json_parse_free (& jpo);
    return r;
}

/*             _ _     _       _   _             
   __   ____ _| (_) __| | __ _| |_(_) ___  _ __  
   \ \ / / _` | | |/ _` |/ _` | __| |/ _ \| '_ \ 
    \ V / (_| | | | (_| | (_| | |_| | (_) | | | |
     \_/ \__,_|_|_|\__,_|\__,_|\__|_|\___/|_| |_| */
                                              

/* The following callbacks just return. */

static json_parse_status
json_argo_string_create_valid (void * vdata, const char * string,
                                  json_parse_u_obj * out)
{
    return json_parse_ok;
}

static json_parse_status
json_argo_integer_create_valid (void * vdata, int integer,
                                  json_parse_u_obj * out)
{
    return json_parse_ok;
}

static json_parse_status
json_argo_array_create_valid (void * vdata, json_parse_u_obj * out)
{
    return json_parse_ok;
}

static json_parse_status
json_argo_hash_create_valid (void * vdata, json_parse_u_obj * out)
{
    return json_parse_ok;
}

static json_parse_status
json_argo_ntf_create_valid (void * vdata, json_type t, void ** out)
{
    return json_parse_ok;
}

static json_parse_status
json_argo_array_push_valid (void * vdata, void * varray, void * velement)
{
    return json_parse_ok;
}

static json_parse_status
json_argo_hash_add_valid (void * vdata, void * vhash, void * vleft, void * vright)
{
    return json_parse_ok;
}

int
json_argo_valid_parse (json_parse_object * jpo, SV * json_sv)
{
    const char * json_bytes;
    json_parse_status status;
    unsigned int json_length;
    json_bytes = SvPV (json_sv, json_length);
    status = json_parse (& json_bytes, jpo);
    return ! status;
}

static int
json_argo_valid_json (SV * json_sv)
{
    json_argo_t json_argo_data = {0};
    json_parse_object jpo = {
        json_argo_string_create_valid,
        json_argo_string_create_valid,
        json_argo_integer_create_valid,
        json_argo_array_create_valid,
        json_argo_hash_create_valid,
        json_argo_ntf_create_valid,
        json_argo_array_push_valid,
        json_argo_hash_add_valid,
        & json_argo_data,
    };
    int r;

    json_parse_init (& jpo);

    r = json_argo_valid_parse (& jpo, json_sv);
    json_parse_free (& jpo);
    return r;
}

static SV *
json_argo_file_to_perl (const char * file_name)
{
    INIT_JPO;
    SV * r;

    //    json_argo_data.verbose = 1;
    json_parse_init (& jpo);

    r = json_argo_parse_file (& jpo, file_name);
    json_parse_free (& jpo);
    return r;
}
