/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
#include <assert.h>
#include <apr_lib.h>
#include <apr_strings.h>
#include <apr_buckets.h>
#include <apr_date.h>

#include "md_json.h"
#include "md_log.h"
#include "md_http.h"
#include "md_time.h"
#include "md_util.h"

/* jansson thinks everyone compiles with the platform's cc in its fullest capabilities
 * when undefining their INLINEs, we get static, unused functions, arg 
 */
#if defined(__GNUC__)
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
#pragma GCC diagnostic push
#endif
#pragma GCC diagnostic ignored "-Wunused-function"
#pragma GCC diagnostic ignored "-Wunreachable-code"
#elif defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
#endif

#include <jansson_config.h>
#undef  JSON_INLINE
#define JSON_INLINE 
#include <jansson.h>

#if defined(__GNUC__)
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
#pragma GCC diagnostic pop
#endif
#elif defined(__clang__)
#pragma clang diagnostic pop
#endif

struct md_json_t {
    apr_pool_t *p;
    json_t *j;
};

/**************************************************************************************************/
/* lifecycle */

static apr_status_t json_pool_cleanup(void *data)
{
    md_json_t *json = data;
    if (json) {
        md_json_destroy(json);
    }
    return APR_SUCCESS;
}

static md_json_t *json_create(apr_pool_t *pool, json_t *j)
{
    md_json_t *json;
    
    if (!j) {
        apr_abortfunc_t abfn = apr_pool_abort_get(pool);
        if (abfn) {
            abfn(APR_ENOMEM);
        }
        assert(j != NULL); /* failsafe in case abort is unset */
    }
    json = apr_pcalloc(pool, sizeof(*json));
    json->p = pool;
    json->j = j;
    apr_pool_cleanup_register(pool, json, json_pool_cleanup, apr_pool_cleanup_null);
        
    return json;
}

md_json_t *md_json_create(apr_pool_t *pool)
{
    return json_create(pool, json_object());
}

md_json_t *md_json_create_s(apr_pool_t *pool, const char *s)
{
    return json_create(pool, json_string(s));
}

void md_json_destroy(md_json_t *json)
{
    if (json && json->j) {
        assert(json->j->refcount > 0);
        json_decref(json->j);
        json->j = NULL;
    }
}

md_json_t *md_json_copy(apr_pool_t *pool, const md_json_t *json)
{
    return json_create(pool, json_copy(json->j));
}

md_json_t *md_json_clone(apr_pool_t *pool, const md_json_t *json)
{
    return json_create(pool, json_deep_copy(json->j));
}

/**************************************************************************************************/
/* selectors */


static json_t *jselect(const md_json_t *json, va_list ap)
{
    json_t *j;
    const char *key;
    
    j = json->j;
    key = va_arg(ap, char *);
    while (key && j) {
        j = json_object_get(j, key);
        key = va_arg(ap, char *);
    }
    return j;
}

static json_t *jselect_parent(const char **child_key, int create, md_json_t *json, va_list ap)
{
    const char *key, *next;
    json_t *j, *jn;
    
    *child_key = NULL;
    j = json->j;
    key = va_arg(ap, char *);
    while (key && j) {
        next = va_arg(ap, char *);
        if (next) {
            jn = json_object_get(j, key);
            if (!jn && create) {
                jn = json_object();
                json_object_set_new(j, key, jn);
            }
            j = jn;
        }
        else {
            *child_key = key;
        }
        key = next;
    }
    return j;
}

static apr_status_t jselect_add(json_t *val, md_json_t *json, va_list ap)
{
    const char *key;
    json_t *j, *aj;
    
    j = jselect_parent(&key, 1, json, ap);
    
    if (!j || !json_is_object(j)) {
        return APR_EINVAL;
    }
    
    aj = json_object_get(j, key);
    if (!aj) {
        aj = json_array();
        json_object_set(j, key, aj);
    }
    
    if (!json_is_array(aj)) {
        return APR_EINVAL;
    }

    json_array_append(aj, val);
    return APR_SUCCESS;
}

static apr_status_t jselect_insert(json_t *val, size_t index, md_json_t *json, va_list ap)
{
    const char *key;
    json_t *j, *aj;
    
    j = jselect_parent(&key, 1, json, ap);
    
    if (!j || !json_is_object(j)) {
        json_decref(val);
        return APR_EINVAL;
    }
    
    aj = json_object_get(j, key);
    if (!aj) {
        aj = json_array();
        json_object_set(j, key, aj);
    }
    
    if (!json_is_array(aj)) {
        json_decref(val);
        return APR_EINVAL;
    }

    if (json_array_size(aj) <= index) {
        json_array_append(aj, val);
    }
    else {
        json_array_insert(aj, index, val);
    }
    return APR_SUCCESS;
}

static apr_status_t jselect_set(json_t *val, md_json_t *json, va_list ap)
{
    const char *key;
    json_t *j;
    
    j = jselect_parent(&key, 1, json, ap);
    
    if (!j) {
        return APR_EINVAL;
    }
    
    if (key) {
        if (!json_is_object(j)) {
            return APR_EINVAL;
        }
        json_object_set(j, key, val);
    }
    else {
        /* replace */
        if (json->j) {
            json_decref(json->j);
        }
        json_incref(val);
        json->j = val;
    }
    return APR_SUCCESS;
}

static apr_status_t jselect_set_new(json_t *val, md_json_t *json, va_list ap)
{
    const char *key;
    json_t *j;
    
    j = jselect_parent(&key, 1, json, ap);
    
    if (!j) {
        json_decref(val);
        return APR_EINVAL;
    }
    
    if (key) {
        if (!json_is_object(j)) {
            json_decref(val);
            return APR_EINVAL;
        }
        json_object_set_new(j, key, val);
    }
    else {
        /* replace */
        if (json->j) {
            json_decref(json->j);
        }
        json->j = val;
    }
    return APR_SUCCESS;
}

int md_json_has_key(const md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);

    return j != NULL;
}

/**************************************************************************************************/
/* type things */

int md_json_is(const md_json_type_t jtype, md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);
    switch (jtype) {
        case MD_JSON_TYPE_OBJECT: return (j && json_is_object(j));
        case MD_JSON_TYPE_ARRAY: return (j && json_is_array(j));
        case MD_JSON_TYPE_STRING: return (j && json_is_string(j));
        case MD_JSON_TYPE_REAL: return (j && json_is_real(j));
        case MD_JSON_TYPE_INT: return (j && json_is_integer(j));
        case MD_JSON_TYPE_BOOL: return (j && (json_is_true(j) || json_is_false(j)));
        case MD_JSON_TYPE_NULL: return (j == NULL);
    }
    return 0;
}

static const char *md_json_type_name(const md_json_t *json)
{
    json_t *j = json->j;
    if (json_is_object(j)) return "object";
    if (json_is_array(j)) return "array";
    if (json_is_string(j)) return "string";
    if (json_is_real(j)) return "real";
    if (json_is_integer(j)) return "integer";
    if (json_is_true(j)) return "true";
    if (json_is_false(j)) return "false";
    return "unknown";
}

/**************************************************************************************************/
/* booleans */

int md_json_getb(const md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);

    return j? json_is_true(j) : 0;
}

apr_status_t md_json_setb(int value, md_json_t *json, ...)
{
    va_list ap;
    apr_status_t rv;
    
    va_start(ap, json);
    rv = jselect_set_new(json_boolean(value), json, ap);
    va_end(ap);
    return rv;
}

/**************************************************************************************************/
/* numbers */

double md_json_getn(const md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);
    return (j && json_is_number(j))? json_number_value(j) : 0.0;
}

apr_status_t md_json_setn(double value, md_json_t *json, ...)
{
    va_list ap;
    apr_status_t rv;
    
    va_start(ap, json);
    rv = jselect_set_new(json_real(value), json, ap);
    va_end(ap);
    return rv;
}

/**************************************************************************************************/
/* longs */

long md_json_getl(const md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);
    return (long)((j && json_is_number(j))? json_integer_value(j) : 0L);
}

apr_status_t md_json_setl(long value, md_json_t *json, ...)
{
    va_list ap;
    apr_status_t rv;
    
    va_start(ap, json);
    rv = jselect_set_new(json_integer(value), json, ap);
    va_end(ap);
    return rv;
}

/**************************************************************************************************/
/* strings */

const char *md_json_gets(const md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);

    return (j && json_is_string(j))? json_string_value(j) : NULL;
}

const char *md_json_dups(apr_pool_t *p, const md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);

    return (j && json_is_string(j))? apr_pstrdup(p, json_string_value(j)) : NULL;
}

apr_status_t md_json_sets(const char *value, md_json_t *json, ...)
{
    va_list ap;
    apr_status_t rv;
    
    va_start(ap, json);
    rv = jselect_set_new(json_string(value), json, ap);
    va_end(ap);
    return rv;
}

/**************************************************************************************************/
/* time */

apr_time_t md_json_get_time(const md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);

    if (!j || !json_is_string(j)) return 0;
    return apr_date_parse_rfc(json_string_value(j));
}

apr_status_t md_json_set_time(apr_time_t value, md_json_t *json, ...)
{
    char ts[APR_RFC822_DATE_LEN];
    va_list ap;
    apr_status_t rv;
    
    apr_rfc822_date(ts, value);
    va_start(ap, json);
    rv = jselect_set_new(json_string(ts), json, ap);
    va_end(ap);
    return rv;
}

/**************************************************************************************************/
/* json itself */

md_json_t *md_json_getj(md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);
    
    if (j) {
        if (j == json->j) {
            return json;
        }
        json_incref(j);
        return json_create(json->p, j);
    }
    return NULL;
}

md_json_t *md_json_dupj(apr_pool_t *p, const md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);
    
    if (j) {
        json_incref(j);
        return json_create(p, j);
    }
    return NULL;
}

const md_json_t *md_json_getcj(const md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);
    
    if (j) {
        if (j == json->j) {
            return json;
        }
        json_incref(j);
        return json_create(json->p, j);
    }
    return NULL;
}

apr_status_t md_json_setj(const md_json_t *value, md_json_t *json, ...)
{
    va_list ap;
    apr_status_t rv;
    const char *key;
    json_t *j;
    
    if (value) {
        va_start(ap, json);
        rv = jselect_set(value->j, json, ap);
        va_end(ap);
    }
    else {
        va_start(ap, json);
        j = jselect_parent(&key, 1, json, ap);
        va_end(ap);
        
        if (key && j && !json_is_object(j)) {
            json_object_del(j, key);
            rv = APR_SUCCESS;
        }
        else {
            rv = APR_EINVAL;
        }
    }
    return rv;
}

apr_status_t md_json_addj(const md_json_t *value, md_json_t *json, ...)
{
    va_list ap;
    apr_status_t rv;
    
    va_start(ap, json);
    rv = jselect_add(value->j, json, ap);
    va_end(ap);
    return rv;
}

apr_status_t md_json_insertj(md_json_t *value, size_t index, md_json_t *json, ...)
{
    va_list ap;
    apr_status_t rv;
    
    va_start(ap, json);
    rv = jselect_insert(value->j, index, json, ap);
    va_end(ap);
    return rv;
}

apr_size_t md_json_limita(size_t max_elements, md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    apr_size_t n = 0;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);

    if (j && json_is_array(j)) {
        n = json_array_size(j);
        while (n > max_elements) {
            json_array_remove(j, n-1);
            n = json_array_size(j);
        }
    }
    return n;
}

/**************************************************************************************************/
/* arrays / objects */

apr_status_t md_json_clr(md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);

    if (j && json_is_object(j)) {
        json_object_clear(j);
    }
    else if (j && json_is_array(j)) {
        json_array_clear(j);
    }
    return APR_SUCCESS;
}

apr_status_t md_json_del(md_json_t *json, ...)
{
    const char *key;
    json_t *j;
    va_list ap;
    
    va_start(ap, json);
    j = jselect_parent(&key, 0, json, ap);
    va_end(ap);
    
    if (key && j && json_is_object(j)) {
        json_object_del(j, key);
    }
    return APR_SUCCESS;
}

/**************************************************************************************************/
/* object strings */

apr_status_t md_json_gets_dict(apr_table_t *dict, const md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);

    if (j && json_is_object(j)) {
        const char *key;
        json_t *val;
        
        json_object_foreach(j, key, val) {
            if (json_is_string(val)) {
                apr_table_set(dict, key, json_string_value(val));
            }
        }
        return APR_SUCCESS;
    }
    return APR_ENOENT;
}

static int object_set(void *data, const char *key, const char *val)
{
    json_t *j = data, *nj = json_string(val);
    json_object_set(j, key, nj);
    json_decref(nj);
    return 1;
}
 
apr_status_t md_json_sets_dict(apr_table_t *dict, md_json_t *json, ...)
{
    json_t *nj, *j;
    va_list ap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);
    
    if (!j || !json_is_object(j)) {
        const char *key;
        
        va_start(ap, json);
        j = jselect_parent(&key, 1, json, ap);
        va_end(ap);
        
        if (!key || !j || !json_is_object(j)) {
            return APR_EINVAL;
        }
        nj = json_object();
        json_object_set_new(j, key, nj);
        j = nj; 
    }
    
    apr_table_do(object_set, j, dict, NULL);
    return APR_SUCCESS;
}

/**************************************************************************************************/
/* conversions */

apr_status_t md_json_pass_to(void *value, md_json_t *json, apr_pool_t *p, void *baton)
{
    (void)p;
    (void)baton;
    return md_json_setj(value, json, NULL);
}

apr_status_t md_json_pass_from(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton)
{
    (void)p;
    (void)baton;
    *pvalue = json;
    return APR_SUCCESS;
}

apr_status_t md_json_clone_to(void *value, md_json_t *json, apr_pool_t *p, void *baton)
{
    (void)baton;
    return md_json_setj(md_json_clone(p, value), json, NULL);
}

apr_status_t md_json_clone_from(void **pvalue, const md_json_t *json, apr_pool_t *p, void *baton)
{
    (void)baton;
    *pvalue = md_json_clone(p, json);
    return APR_SUCCESS;
}

/**************************************************************************************************/
/* array generic */

apr_status_t md_json_geta(apr_array_header_t *a, md_json_from_cb *cb, void *baton,
                          const md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    apr_status_t rv = APR_SUCCESS;
    size_t index;
    json_t *val;
    md_json_t wrap;
    void *element;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);
    
    if (!j || !json_is_array(j)) {
        return APR_ENOENT;
    }
        
    wrap.p = a->pool;
    json_array_foreach(j, index, val) {
        wrap.j = val;
        if (APR_SUCCESS == (rv = cb(&element, &wrap, wrap.p, baton))) {
            if (element) {
                APR_ARRAY_PUSH(a, void*) = element;
            }
        }
        else if (APR_ENOENT == rv) {
            rv = APR_SUCCESS;
        }
        else {
            break;
        }
    }
    return rv;
}

apr_status_t md_json_seta(apr_array_header_t *a, md_json_to_cb *cb, void *baton, 
                          md_json_t *json, ...)
{
    json_t *j, *nj;
    md_json_t wrap;
    apr_status_t rv = APR_SUCCESS;
    va_list ap;
    int i;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);
    
    if (!j || !json_is_array(j)) {
        const char *key;
        
        va_start(ap, json);
        j = jselect_parent(&key, 1, json, ap);
        va_end(ap);
        
        if (!key || !j || !json_is_object(j)) {
            return APR_EINVAL;
        }
        nj = json_array();
        json_object_set_new(j, key, nj);
        j = nj; 
    }
    
    json_array_clear(j);
    wrap.p = json->p;
    for (i = 0; i < a->nelts; ++i) {
        if (!cb) {
            return APR_EINVAL;
        }    
        wrap.j = json_string("");
        if (APR_SUCCESS == (rv = cb(APR_ARRAY_IDX(a, i, void*), &wrap, json->p, baton))) {
            json_array_append_new(j, wrap.j);
        }
    }
    return rv;
}

int md_json_itera(md_json_itera_cb *cb, void *baton, md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    size_t index;
    json_t *val;
    md_json_t wrap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);
    
    if (!j || !json_is_array(j)) {
        return 0;
    }
        
    wrap.p = json->p;
    json_array_foreach(j, index, val) {
        wrap.j = val;
        if (!cb(baton, index, &wrap)) {
            return 0;
        }
    }
    return 1;
}

int md_json_iterkey(md_json_iterkey_cb *cb, void *baton, md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    const char *key;
    json_t *val;
    md_json_t wrap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);
    
    if (!j || !json_is_object(j)) {
        return 0;
    }
        
    wrap.p = json->p;
    json_object_foreach(j, key, val) {
        wrap.j = val;
        if (!cb(baton, key, &wrap)) {
            return 0;
        }
    }
    return 1;
}

/**************************************************************************************************/
/* array strings */

apr_status_t md_json_getsa(apr_array_header_t *a, const md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);

    if (j && json_is_array(j)) {
        size_t index;
        json_t *val;
        
        json_array_foreach(j, index, val) {
            if (json_is_string(val)) {
                APR_ARRAY_PUSH(a, const char *) = json_string_value(val);
            }
        }
        return APR_SUCCESS;
    }
    return APR_ENOENT;
}

apr_status_t md_json_dupsa(apr_array_header_t *a, apr_pool_t *p, md_json_t *json, ...)
{
    json_t *j;
    va_list ap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);

    if (j && json_is_array(j)) {
        size_t index;
        json_t *val;
        
        apr_array_clear(a);
        json_array_foreach(j, index, val) {
            if (json_is_string(val)) {
                APR_ARRAY_PUSH(a, const char *) = apr_pstrdup(p, json_string_value(val));
            }
        }
        return APR_SUCCESS;
    }
    return APR_ENOENT;
}

apr_status_t md_json_setsa(apr_array_header_t *a, md_json_t *json, ...)
{
    json_t *nj, *j;
    va_list ap;
    int i;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);
    
    if (!j || !json_is_array(j)) {
        const char *key;
        
        va_start(ap, json);
        j = jselect_parent(&key, 1, json, ap);
        va_end(ap);
        
        if (!key || !j || !json_is_object(j)) {
            return APR_EINVAL;
        }
        nj = json_array();
        json_object_set_new(j, key, nj);
        j = nj; 
    }
    
    json_array_clear(j);
    for (i = 0; i < a->nelts; ++i) {
        json_array_append_new(j, json_string(APR_ARRAY_IDX(a, i, const char*)));
    }
    return APR_SUCCESS;
}

/**************************************************************************************************/
/* formatting, parsing */

typedef struct {
    const md_json_t *json;
    md_json_fmt_t fmt;
    const char *fname;
    apr_file_t *f;
} j_write_ctx;

/* Convert from md_json_fmt_t to the Jansson json_dumpX flags. */
static size_t fmt_to_flags(md_json_fmt_t fmt)
{
    /* NOTE: JSON_PRESERVE_ORDER is off by default before Jansson 2.8. It
     * doesn't have any semantic effect on the protocol, but it does let the
     * md_json_writeX unit tests run deterministically. */
    return JSON_PRESERVE_ORDER |
           ((fmt == MD_JSON_FMT_COMPACT) ? JSON_COMPACT : JSON_INDENT(2)); 
}

static int dump_cb(const char *buffer, size_t len, void *baton)
{
    apr_bucket_brigade *bb = baton;
    apr_status_t rv;
    
    rv = apr_brigade_write(bb, NULL, NULL, buffer, len);
    return (rv == APR_SUCCESS)? 0 : -1;
}

apr_status_t md_json_writeb(const md_json_t *json, md_json_fmt_t fmt, apr_bucket_brigade *bb)
{
    int rv = json_dump_callback(json->j, dump_cb, bb, fmt_to_flags(fmt));
    return rv? APR_EGENERAL : APR_SUCCESS;
}

static int chunk_cb(const char *buffer, size_t len, void *baton)
{
    apr_array_header_t *chunks = baton;
    char *chunk;
    
    if (len > 0) {
        chunk = apr_palloc(chunks->pool, len+1);
        memcpy(chunk, buffer, len);
        chunk[len] = '\0';
        APR_ARRAY_PUSH(chunks, const char*) = chunk;
    }
    return 0;
}

const char *md_json_writep(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt)
{
    apr_array_header_t *chunks;
    int rv;

    chunks = apr_array_make(p, 10, sizeof(char *));
    rv = json_dump_callback(json->j, chunk_cb, chunks, fmt_to_flags(fmt));
    if (APR_SUCCESS != rv) {
        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
                      "md_json_writep failed to dump JSON");
        return NULL;
    }

    switch (chunks->nelts) {
        case 0:
            return "";
        case 1:
            return APR_ARRAY_IDX(chunks, 0, const char*);
        default:
            return apr_array_pstrcat(p, chunks, 0);
    }
}

apr_status_t md_json_writef(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, apr_file_t *f)
{
    apr_status_t rv;
    const char *s;
    
    if ((s = md_json_writep(json, p, fmt))) {
        rv = apr_file_write_full(f, s, strlen(s), NULL);
        if (APR_SUCCESS != rv) {
            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, json->p, "md_json_writef: error writing file");
        }
    }
    else {
        rv = APR_EINVAL;
        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, json->p, 
                      "md_json_writef: error dumping json (%s)", md_json_dump_state(json, p));
    }
    return rv;
}

apr_status_t md_json_fcreatex(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, 
                              const char *fpath, apr_fileperms_t perms)
{
    apr_status_t rv;
    apr_file_t *f;
    
    rv = md_util_fcreatex(&f, fpath, perms, p);
    if (APR_SUCCESS == rv) {
        rv = md_json_writef(json, p, fmt, f);
        apr_file_close(f);
    }
    return rv;
}

static apr_status_t write_json(void *baton, apr_file_t *f, apr_pool_t *p)
{
    j_write_ctx *ctx = baton;
    apr_status_t rv = md_json_writef(ctx->json, p, ctx->fmt, f);
    if (APR_SUCCESS != rv) {
        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "freplace json in %s", ctx->fname);
    }
    return rv;
}

apr_status_t md_json_freplace(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, 
                              const char *fpath, apr_fileperms_t perms)
{
    j_write_ctx ctx;
    ctx.json = json;
    ctx.fmt = fmt;
    ctx.fname = fpath;
    return md_util_freplace(fpath, perms, p, write_json, &ctx);
}

apr_status_t md_json_readd(md_json_t **pjson, apr_pool_t *pool, const char *data, size_t data_len)
{
    json_error_t error;
    json_t *j;
    
    j = json_loadb(data, data_len, 0, &error);
    if (!j) {
        return APR_EINVAL;
    }
    *pjson = json_create(pool, j);
    return APR_SUCCESS;
}

static size_t load_cb(void *data, size_t max_len, void *baton)
{
    apr_bucket_brigade *body = baton;
    size_t blen, read_len = 0;
    const char *bdata;
    char *dest = data;
    apr_bucket *b;
    apr_status_t rv;
    
    while (body && !APR_BRIGADE_EMPTY(body) && max_len > 0) {
        b = APR_BRIGADE_FIRST(body);
        if (APR_BUCKET_IS_METADATA(b)) {
            if (APR_BUCKET_IS_EOS(b)) {
                body = NULL;
            }
        }
        else {
            rv = apr_bucket_read(b, &bdata, &blen, APR_BLOCK_READ);
            if (rv == APR_SUCCESS) {
                if (blen > max_len) {
                    apr_bucket_split(b, max_len);
                    blen = max_len;
                }
                memcpy(dest, bdata, blen);
                read_len += blen;
                max_len -= blen;
                dest += blen;
            }
            else {
                body = NULL;
                if (!APR_STATUS_IS_EOF(rv)) {
                    /* everything beside EOF is an error */
                    read_len = (size_t)-1;
                }
            }
        }
        APR_BUCKET_REMOVE(b);
        apr_bucket_delete(b);
    }
    
    return read_len;
}

apr_status_t md_json_readb(md_json_t **pjson, apr_pool_t *pool, apr_bucket_brigade *bb)
{
    json_error_t error;
    json_t *j;
    
    j = json_load_callback(load_cb, bb, 0, &error);
    if (j) {
        *pjson = json_create(pool, j);
    } else {
        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, pool,
                      "failed to load JSON file: %s (line %d:%d)",
                      error.text, error.line, error.column);
    }
    return (j && *pjson) ? APR_SUCCESS : APR_EINVAL;
}

static size_t load_file_cb(void *data, size_t max_len, void *baton)
{
    apr_file_t *f = baton;
    apr_size_t len = max_len;
    apr_status_t rv;
    
    rv = apr_file_read(f, data, &len);
    if (APR_SUCCESS == rv) {
        return len;
    }
    else if (APR_EOF == rv) {
        return 0;
    }
    return (size_t)-1;
}

apr_status_t md_json_readf(md_json_t **pjson, apr_pool_t *p, const char *fpath)
{
    apr_file_t *f;
    json_t *j;
    apr_status_t rv;
    json_error_t error;
    
    rv = apr_file_open(&f, fpath, APR_FOPEN_READ, 0, p);
    if (rv != APR_SUCCESS) {
        return rv;
    }

    j = json_load_callback(load_file_cb, f, 0, &error);
    if (j) {
        *pjson = json_create(p, j);
    }
    else {
        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
                      "failed to load JSON file %s: %s (line %d:%d)",
                      fpath, error.text, error.line, error.column);
    }

    apr_file_close(f);
    return (j && *pjson) ? APR_SUCCESS : APR_EINVAL;
}

/**************************************************************************************************/
/* http get */

apr_status_t md_json_read_http(md_json_t **pjson, apr_pool_t *pool, const md_http_response_t *res)
{
    apr_status_t rv = APR_ENOENT;
    const char *ctype, *p;

    *pjson = NULL;
    if (!res->body) goto cleanup;
    ctype = md_util_parse_ct(res->req->pool, apr_table_get(res->headers, "content-type"));
    if (!ctype) goto cleanup;
    p = ctype + strlen(ctype) +1;
    if (!strcmp(p - sizeof("/json"), "/json")
        || !strcmp(p - sizeof("+json"), "+json")) {
        rv = md_json_readb(pjson, pool, res->body);
    }
cleanup:
    return rv;
}

typedef struct {
    apr_status_t rv;
    apr_pool_t *pool;
    md_json_t *json;
} resp_data;

static apr_status_t json_resp_cb(const md_http_response_t *res, void *data)
{
    resp_data *resp = data;
    return md_json_read_http(&resp->json, resp->pool, res);
}

apr_status_t md_json_http_get(md_json_t **pjson, apr_pool_t *pool,
                              struct md_http_t *http, const char *url)
{
    apr_status_t rv;
    resp_data resp;
    
    memset(&resp, 0, sizeof(resp));
    resp.pool = pool;
    
    rv = md_http_GET_perform(http, url, NULL, json_resp_cb, &resp);
    
    if (rv == APR_SUCCESS) {
        *pjson = resp.json;
        return resp.rv;
    }
    *pjson = NULL;
    return rv;
}


apr_status_t md_json_copy_to(md_json_t *dest, const md_json_t *src, ...)
{
    json_t *j;
    va_list ap;
    apr_status_t rv = APR_SUCCESS;
    
    va_start(ap, src);
    j = jselect(src, ap);
    va_end(ap);

    if (j) {
        va_start(ap, src);
        rv = jselect_set(j, dest, ap);
        va_end(ap);
    }
    return rv;
}

const char *md_json_dump_state(const md_json_t *json, apr_pool_t *p)
{
    if (!json) return "NULL";
    return apr_psprintf(p, "%s, refc=%ld", md_json_type_name(json), (long)json->j->refcount);
}

apr_status_t md_json_set_timeperiod(const md_timeperiod_t *tp, md_json_t *json, ...)
{
    char ts[APR_RFC822_DATE_LEN];
    json_t *jn, *j;
    va_list ap;
    const char *key;
    apr_status_t rv;
    
    if (!tp || tp->start || tp->end) {
        jn = json_object();
        apr_rfc822_date(ts, tp->start);
        json_object_set_new(jn, "from", json_string(ts));
        apr_rfc822_date(ts, tp->end);
        json_object_set_new(jn, "until", json_string(ts));
        
        va_start(ap, json);
        rv = jselect_set_new(jn, json, ap);
        va_end(ap);
        return rv;
    }
    else {
        va_start(ap, json);
        j = jselect_parent(&key, 0, json, ap);
        va_end(ap);
        
        if (key && j && json_is_object(j)) {
            json_object_del(j, key);
        }
        return APR_SUCCESS;
    }
}

apr_status_t md_json_get_timeperiod(md_timeperiod_t *tp, md_json_t *json, ...)
{
    json_t *j, *jts;
    va_list ap;
    
    va_start(ap, json);
    j = jselect(json, ap);
    va_end(ap);
    
    memset(tp, 0, sizeof(*tp));
    if (!j) goto not_found;
    jts = json_object_get(j, "from");
    if (!jts || !json_is_string(jts)) goto not_found;
    tp->start = apr_date_parse_rfc(json_string_value(jts)); 
    jts = json_object_get(j, "until");
    if (!jts || !json_is_string(jts)) goto not_found;
    tp->end = apr_date_parse_rfc(json_string_value(jts)); 
    return APR_SUCCESS;
not_found:
    return APR_ENOENT;
}
