/* Type for adding whitespace. */

typedef struct json_ws {
    SV * news;
    SV * olds;
    /* Length of new string. */
    unsigned int news_l;
    /* Copy point. */
    char * q;
    /* Origin */
    char * p;
    /* Top of token tree. */
    json_token_t * t;
    /* Token under examination now. */
    json_token_t * next;

    /* Whitespace to add before and after. */
    char * before[n_json_tokens];
    char * after[n_json_tokens];
    int array_depth;
    int object_depth;
    char * array_indent;
    char * object_indent;
}
json_ws_t;

static void copy_whitespace (json_ws_t * ws, char * w)
{
    char * q;
    q = ws->q;
    while (*w) {
	*q++ = *w++;
    }
    ws->q = q;
}

static INLINE int whitespace_json (json_ws_t * ws)
{
    /* Copy place. */
    char * c;
    /* Value of q at entry to this routine, used to calculate added
       length. */
    char * qorig;
    json_token_t * next;
    char * q;

    q = ws->q;
    qorig = q;
    next = ws->next;

    while (next) {
	/* Copy start of string. */
	copy_whitespace (ws, ws->before[next->type]);
	switch (next->type) {
	case json_token_object:
	    *q++ = '{';
	    ws->object_depth++;
	    ws->q = q;
	    q += whitespace_json (ws);
	    ws->object_depth--;
	    *q++ = '}';
	    break;
	case json_token_array:
	    *q++ = '[';
	    ws->array_depth++;
	    ws->q = q;
	    q += whitespace_json (ws);
	    ws->object_depth--;
	    *q++ = ']';
	    break;
	case json_token_string:
	case json_token_key:
	case json_token_literal:
	case json_token_number:
	    for (c = ws->p + next->start; c <= ws->p + next->end; c++) {
		*q++ = *c;
	    }
	    break;
	case json_token_comma:
	    *q++ = ',';
	    break;
	case json_token_colon:
	    *q++ = ':';
	    break;
	default:
	    croak ("unhandled token type %d", next->type);
	}
	/* Copy end of string. */
	c = ws->after[next->type];
	while (*c) {
	    *q++ = *c++;
	}
	next = next->next;
    }
    return q - qorig;
}

static int copy_json (char * p, char * q, json_token_t * t)
{
    /* Loop variable. */
    json_token_t * next;
    /* Copy place. */
    char * c;
    /* Value of q at entry to this routine, used to calculate added
       length. */
    char * qorig;

    next = t;
    qorig = q;
    while (next) {
	switch (next->type) {
	case json_token_object:
	    *q++ = '{';
	    q += copy_json (p, q, next->child);
	    *q++ = '}';
	    break;
	case json_token_array:
	    *q++ = '[';
	    q += copy_json (p, q, next->child);
	    *q++ = ']';
	    break;
	case json_token_string:
	case json_token_key:
	case json_token_literal:
	case json_token_number:
	    for (c = p + next->start; c < p + next->end; c++) {
		*q++ = *c;
	    }
	    break;
	case json_token_comma:
	    *q++ = ',';
	    break;
	case json_token_colon:
	    *q++ = ':';
	    break;
	default:
	    croak ("unhandled token type %d", next->type);
	}
	next = next->next;
    }
    return q - qorig;
}

/* Remove all the whitespace. */

static SV * strip_whitespace (json_token_t * tokens, SV * json)
{
    SV * stripped;
    char * p;
    char * q;
    /* Original length. */
    STRLEN l;
    /* Length of output. */
    unsigned int m;
    p = SvPV (json, l);
    stripped = newSV (l);
    /* Tell Perl it's a string. */
    SvPOK_on (stripped);
    /* Set UTF-8 if necessary. */
    if (SvUTF8 (json)) {
	SvUTF8_on (stripped);
    }
    /* Get a pointer to the string inside "stripped". */
    q = SvPVX (stripped);
    m = copy_json (p, q, tokens);
    /* Set the length. */
    SvCUR_set (stripped, m);
    return stripped;
}
