#include "ManifestGeneration.h"
#include "../Configuration.h"
#include "cxx_supportlib/Constants.h"
#include "cxx_supportlib/FileTools/PathManipCBindings.h"


static PsgJsonValue *
generate_config_manifest(ngx_conf_t *cf, passenger_loc_conf_t *toplevel_plcf) {
    manifest_gen_ctx_t ctx;
    PsgJsonValue *result;

    init_config_manifest_generation(cf, &ctx);

    generate_config_manifest_for_autogenerated_main_conf(&ctx, &passenger_main_conf);
    recursively_generate_config_manifest_for_loc_conf(&ctx, toplevel_plcf);

    reverse_manifest_value_hierarchies(&ctx);
    set_manifest_autogenerated_global_conf_defaults(&ctx);
    set_manifest_autogenerated_app_conf_defaults(&ctx,
        ctx.default_app_config_container);
    set_manifest_autogenerated_loc_conf_defaults(&ctx,
        ctx.default_loc_config_container);
    manifest_inherit_application_value_hierarchies(&ctx);
    manifest_inherit_location_value_hierarchies(&ctx);

    result = ctx.manifest;
    deinit_config_manifest_generation(&ctx);
    return result;
}


static void
init_config_manifest_generation(ngx_conf_t *cf, manifest_gen_ctx_t *ctx) {
    ngx_memzero(ctx, sizeof(manifest_gen_ctx_t));

    ctx->cf = cf;
    ctx->manifest = psg_json_value_new_with_type(PSG_JSON_VALUE_TYPE_OBJECT);

    ctx->empty_object = psg_json_value_new_with_type(PSG_JSON_VALUE_TYPE_OBJECT);
    ctx->empty_array  = psg_json_value_new_with_type(PSG_JSON_VALUE_TYPE_ARRAY);

    ctx->it   = psg_json_value_iterator_new();
    ctx->end  = psg_json_value_iterator_new();
    ctx->it2  = psg_json_value_iterator_new();
    ctx->end2 = psg_json_value_iterator_new();
    ctx->it3  = psg_json_value_iterator_new();
    ctx->end3 = psg_json_value_iterator_new();
    ctx->it4  = psg_json_value_iterator_new();
    ctx->end4 = psg_json_value_iterator_new();

    ctx->global_config_container = psg_json_value_set_value(ctx->manifest,
        "global_configuration", -1, ctx->empty_object);
    ctx->default_app_config_container = psg_json_value_set_value(ctx->manifest,
        "default_application_configuration", -1, ctx->empty_object);
    ctx->default_loc_config_container = psg_json_value_set_value(ctx->manifest,
        "default_location_configuration", -1, ctx->empty_object);
    ctx->app_configs_container = psg_json_value_set_value(ctx->manifest,
        "application_configurations", -1, ctx->empty_object);
}

static void
deinit_config_manifest_generation(manifest_gen_ctx_t *ctx) {
    psg_json_value_free(ctx->empty_object);
    psg_json_value_free(ctx->empty_array);
    psg_json_value_iterator_free(ctx->it);
    psg_json_value_iterator_free(ctx->end);
    psg_json_value_iterator_free(ctx->it2);
    psg_json_value_iterator_free(ctx->end2);
    psg_json_value_iterator_free(ctx->it3);
    psg_json_value_iterator_free(ctx->end3);
    psg_json_value_iterator_free(ctx->it4);
    psg_json_value_iterator_free(ctx->end4);
}


static void
recursively_generate_config_manifest_for_loc_conf(manifest_gen_ctx_t *ctx,
    passenger_loc_conf_t *plcf)
{
    passenger_loc_conf_t **children;
    ngx_http_core_srv_conf_t *cscf;
    ngx_http_core_loc_conf_t *clcf;
    ngx_uint_t i;

    cscf = plcf->cscf;
    clcf = plcf->clcf;

    if (cscf != NULL && clcf != NULL && plcf->autogenerated.enabled) {
        generate_config_manifest_for_loc_conf(ctx, plcf, cscf, clcf);
    }

    children = plcf->children.elts;
    for (i = 0; i < plcf->children.nelts; i++) {
        recursively_generate_config_manifest_for_loc_conf(ctx, children[i]);
    }
}

static int
infer_loc_conf_app_group_name(manifest_gen_ctx_t *ctx, passenger_loc_conf_t *plcf,
    ngx_http_core_loc_conf_t *clcf, ngx_str_t *result)
{
    ngx_str_t app_root, app_env;
    char *abs_path;
    u_char *buf;
    void *_unused;
    size_t buf_size;

    if (plcf->autogenerated.app_group_name.data == NULL) {
        if (plcf->autogenerated.app_root.data == NULL) {
            buf_size = clcf->root.len + sizeof("/..") - 1;

            buf = (u_char *) ngx_pnalloc(ctx->cf->pool, buf_size);
            if (buf == NULL) {
                return 0;
            }
            app_root.data = buf;
            app_root.len = ngx_snprintf(buf, buf_size, "%V/..", &clcf->root) - buf;
        } else {
            app_root = plcf->autogenerated.app_root;
        }

        abs_path = psg_absolutize_path(
            (const char *) app_root.data, app_root.len,
            (const char *) ctx->cf->cycle->prefix.data, ctx->cf->cycle->prefix.len,
            &app_root.len);
        app_root.data = (u_char *) ngx_pnalloc(ctx->cf->pool, app_root.len);
        _unused = ngx_copy(app_root.data, abs_path, app_root.len);
        (void) _unused; /* Shut up compiler warning */
        free(abs_path);

        if (plcf->autogenerated.environment.data == NULL) {
            app_env.data = (u_char *) DEFAULT_APP_ENV;
            app_env.len = sizeof(DEFAULT_APP_ENV) - 1;
        } else {
            app_env = plcf->autogenerated.environment;
        }

        buf_size = app_root.len + app_env.len + sizeof(" ()") - 1;
        buf = (u_char *) ngx_pnalloc(ctx->cf->pool, buf_size);
        result->data = buf;
        result->len = ngx_snprintf(buf, buf_size, "%V (%V)", &app_root, &app_env) - buf;
    } else {
        *result = plcf->autogenerated.app_group_name;
    }

    return 1;
}

static u_char *
infer_default_app_root(manifest_gen_ctx_t *ctx, ngx_http_core_loc_conf_t *clcf,
    size_t *len)
{
    u_char *path, *end;

    path = ngx_pnalloc(ctx->cf->temp_pool, clcf->root.len + 3);
    end = ngx_snprintf(path, clcf->root.len + 3,
        "%V/..", &clcf->root);
    return (u_char *) psg_absolutize_path((const char *) path,
        end - path, NULL, 0, len);
}

static PsgJsonValue *
find_or_create_manifest_app_config_container(manifest_gen_ctx_t *ctx,
    ngx_str_t *app_group_name)
{
    PsgJsonValue *result;

    result = psg_json_value_get_or_create_null(ctx->app_configs_container,
        (const char *) app_group_name->data, app_group_name->len);
    if (psg_json_value_is_null(result)) {
        psg_json_value_set_value(result, "options", -1, ctx->empty_object);
        psg_json_value_set_value(result, "default_location_configuration", -1, ctx->empty_object);
        psg_json_value_set_value(result, "location_configurations", -1, ctx->empty_array);
    }
    return result;
}

static PsgJsonValue *
find_or_create_manifest_loc_config_container(manifest_gen_ctx_t *ctx,
    PsgJsonValue *app_config_container, ngx_http_core_srv_conf_t *cscf,
    ngx_http_core_loc_conf_t *clcf)
{
    PsgJsonValue *loc_configs_container;
    PsgJsonValue *loc_config_container;

    loc_configs_container = psg_json_value_get(app_config_container,
        "location_configurations", -1);
    loc_config_container = find_manifest_loc_config_container(ctx,
        loc_configs_container, cscf, clcf);
    if (loc_config_container == NULL) {
        loc_config_container = create_manifest_loc_config_container(ctx,
            loc_configs_container, cscf, clcf);
    }

    return loc_config_container;
}

static int
matches_any_server_names(manifest_gen_ctx_t *ctx, ngx_http_core_srv_conf_t *cscf,
    PsgJsonValue *server_names_doc)
{
    ngx_http_server_name_t *server_names = cscf->server_names.elts;
    PsgJsonValue *server_name_doc;
    ngx_str_t server_name;
    ngx_uint_t i;

    psg_json_value_begin(server_names_doc, ctx->it2);
    psg_json_value_end(server_names_doc, ctx->end2);

    while (!psg_json_value_iterator_eq(ctx->it2, ctx->end2)) {
        server_name_doc = psg_json_value_iterator_get_value(ctx->it2);
        server_name.data = (u_char *) psg_json_value_get_str(server_name_doc, &server_name.len);

        for (i = 0; i < cscf->server_names.nelts; i++) {
            if (server_names[i].name.len == server_name.len
                && ngx_strncasecmp(server_names[i].name.data, server_name.data,
                                   server_name.len) == 0)
            {
                return 1;
            }
        }

        psg_json_value_iterator_advance(ctx->it2);
    }

    return 0;
}

static PsgJsonValue *
find_manifest_loc_config_container(manifest_gen_ctx_t *ctx,
    PsgJsonValue *loc_configs_container, ngx_http_core_srv_conf_t *cscf,
    ngx_http_core_loc_conf_t *clcf)
{
    PsgJsonValue *loc_config_container;
    PsgJsonValue *vhost_doc;
    PsgJsonValue *server_names_doc;
    PsgJsonValue *location_matcher_doc;
    ngx_str_t json_location_matcher_type, json_location_matcher_value;

    psg_json_value_begin(loc_configs_container, ctx->it);
    psg_json_value_end(loc_configs_container, ctx->end);

    while (!psg_json_value_iterator_eq(ctx->it, ctx->end)) {
        loc_config_container = psg_json_value_iterator_get_value(ctx->it);
        vhost_doc = psg_json_value_get(loc_config_container, "web_server_virtual_host", -1);
        location_matcher_doc = psg_json_value_get(loc_config_container, "location_matcher", -1);

        json_location_matcher_type.data = (u_char *) psg_json_value_get_str(
            psg_json_value_get(location_matcher_doc, "type", -1),
            &json_location_matcher_type.len);
        #if (NGX_PCRE) || (NGX_PCRE2)
            if (clcf->regex != NULL) {
                if (json_location_matcher_type.len != sizeof("regex") - 1
                    || ngx_memcmp(json_location_matcher_type.data, "regex", sizeof("regex") - 1) != 0)
                {
                    goto no_match;
                }
            } else
        #endif
        if (clcf->exact_match) {
            if (json_location_matcher_type.len != sizeof("exact") - 1
                || ngx_memcmp(json_location_matcher_type.data, "exact", sizeof("exact") - 1) != 0)
            {
                goto no_match;
            }
        } else {
            if (json_location_matcher_type.len != sizeof("prefix") - 1
                || ngx_memcmp(json_location_matcher_type.data, "prefix", sizeof("prefix") - 1) != 0)
            {
                goto no_match;
            }
        }

        json_location_matcher_value.data = (u_char *) psg_json_value_get_str(
            psg_json_value_get(location_matcher_doc, "value", -1),
            &json_location_matcher_value.len);
        if (ngx_memn2cmp(clcf->name.data, json_location_matcher_value.data,
                         clcf->name.len, json_location_matcher_value.len)
            != 0)
        {
            goto no_match;
        }

        server_names_doc = psg_json_value_get(vhost_doc, "server_names", -1);
        if (!matches_any_server_names(ctx, cscf, server_names_doc)) {
            goto no_match;
        }

        return loc_config_container;

        no_match:
        psg_json_value_iterator_advance(ctx->it);
    }

    return NULL;
}

static PsgJsonValue *
create_manifest_loc_config_container(manifest_gen_ctx_t *ctx,
    PsgJsonValue *loc_configs_container, ngx_http_core_srv_conf_t *cscf,
    ngx_http_core_loc_conf_t *clcf)
{
    PsgJsonValue *loc_config_container = psg_json_value_new_with_type(PSG_JSON_VALUE_TYPE_OBJECT);
    PsgJsonValue *vhost_doc = psg_json_value_new_with_type(PSG_JSON_VALUE_TYPE_OBJECT);
    PsgJsonValue *server_names_doc = psg_json_value_new_with_type(PSG_JSON_VALUE_TYPE_ARRAY);
    PsgJsonValue *location_matcher_doc = psg_json_value_new_with_type(PSG_JSON_VALUE_TYPE_OBJECT);
    PsgJsonValue *options_doc = psg_json_value_new_with_type(PSG_JSON_VALUE_TYPE_OBJECT);
    PsgJsonValue *server_name_doc, *result;
    ngx_http_server_name_t *server_names = cscf->server_names.elts;
    ngx_uint_t i;

    for (i = 0; i < cscf->server_names.nelts; i++) {
        server_name_doc = psg_json_value_new_str((const char *) server_names[i].name.data,
            server_names[i].name.len);
        psg_json_value_append_val(server_names_doc, server_name_doc);
        psg_json_value_free(server_name_doc);
    }
    psg_json_value_set_value(vhost_doc, "server_names", -1, server_names_doc);

    psg_json_value_set_str(location_matcher_doc, "value",
        (const char *) clcf->name.data, clcf->name.len);
    #if (NGX_PCRE) || (NGX_PCRE2)
        if (clcf->regex != NULL) {
            psg_json_value_set_str(location_matcher_doc, "type",
                "regex", -1);
        } else
    #endif
    if (clcf->exact_match) {
        psg_json_value_set_str(location_matcher_doc, "type",
            "exact", -1);
    } else {
        psg_json_value_set_str(location_matcher_doc, "type",
            "prefix", -1);
    }

    psg_json_value_set_value(loc_config_container, "web_server_virtual_host", -1, vhost_doc);
    psg_json_value_set_value(loc_config_container, "location_matcher", -1, location_matcher_doc);
    psg_json_value_set_value(loc_config_container, "options", -1, options_doc);
    result = psg_json_value_append_val(loc_configs_container, loc_config_container);
    psg_json_value_free(loc_config_container);
    psg_json_value_free(vhost_doc);
    psg_json_value_free(server_names_doc);
    psg_json_value_free(location_matcher_doc);
    psg_json_value_free(options_doc);
    return result;
}

static PsgJsonValue *
find_or_create_manifest_option_container(manifest_gen_ctx_t *ctx,
    PsgJsonValue *options_container, const char *option_name,
    size_t option_name_len)
{
    PsgJsonValue *result;

    result = psg_json_value_get_or_create_null(options_container,
        option_name, option_name_len);
    if (psg_json_value_is_null(result)) {
        init_manifest_option_container(ctx, result);
    }

    return result;
}

static void
init_manifest_option_container(manifest_gen_ctx_t *ctx, PsgJsonValue *doc) {
    psg_json_value_set_value(doc, "value_hierarchy", -1, ctx->empty_array);
}

static PsgJsonValue *
add_manifest_option_container_hierarchy_member(PsgJsonValue *option_container,
    ngx_str_t *source_file, ngx_uint_t source_line)
{
    PsgJsonValue *value_hierarchy = psg_json_value_get(option_container, "value_hierarchy", -1);
    PsgJsonValue *hierarchy_member = psg_json_value_new_with_type(PSG_JSON_VALUE_TYPE_OBJECT);
    PsgJsonValue *source = psg_json_value_new_with_type(PSG_JSON_VALUE_TYPE_OBJECT);
    PsgJsonValue *result;

    psg_json_value_set_str(source, "type", "web-server-config", sizeof("web-server-config") - 1);
    psg_json_value_set_str(source, "path", (const char *) source_file->data, source_file->len);
    psg_json_value_set_uint(source, "line", source_line);
    psg_json_value_set_value(hierarchy_member, "source", -1, source);
    result = psg_json_value_append_val(value_hierarchy, hierarchy_member);

    psg_json_value_free(hierarchy_member);
    psg_json_value_free(source);

    return result;
}

static void
generate_config_manifest_for_loc_conf(manifest_gen_ctx_t *ctx,
    passenger_loc_conf_t *plcf, ngx_http_core_srv_conf_t *cscf,
    ngx_http_core_loc_conf_t *clcf)
{
    generate_config_manifest_for_autogenerated_loc_conf(ctx, plcf, cscf, clcf);
}

static void
find_or_create_manifest_app_and_loc_options_containers(manifest_gen_ctx_t *ctx,
    passenger_loc_conf_t *plcf, ngx_http_core_srv_conf_t *cscf,
    ngx_http_core_loc_conf_t *clcf, PsgJsonValue **app_options_container,
    PsgJsonValue **loc_options_container)
{
    ngx_str_t app_group_name;
    PsgJsonValue *app_config_container, *loc_config_container;
    ngx_str_t default_app_root;

    if (*app_options_container != NULL && *loc_options_container != NULL) {
        return;
    }

    if (cscf->server_name.len == 0) {
        /* We are in the global context */
        *app_options_container = ctx->default_app_config_container;
        *loc_options_container = ctx->default_loc_config_container;
    } else if (clcf->name.len == 0) {
        /* We are in a server block */
        infer_loc_conf_app_group_name(ctx, plcf, clcf, &app_group_name);
        app_config_container = find_or_create_manifest_app_config_container(ctx, &app_group_name);
        *app_options_container = psg_json_value_get(app_config_container, "options", -1);
        *loc_options_container = psg_json_value_get(app_config_container, "default_location_configuration", -1);

        /* Create a default value for passenger_app_group_name and
         * passenger_app_root if we just created this config container
         */
        if (psg_json_value_size(*app_options_container) == 0) {
            add_manifest_options_container_inferred_default_str(ctx,
                *app_options_container,
                "passenger_app_group_name", -1,
                (const char *) app_group_name.data, app_group_name.len);

            default_app_root.data = infer_default_app_root(
                ctx, clcf, &default_app_root.len);
            add_manifest_options_container_inferred_default_str(ctx,
                *app_options_container,
                "passenger_app_root", -1,
                (const char *) default_app_root.data, default_app_root.len);
            free(default_app_root.data);
        }
    } else {
        /* We are in a location/if block */
        infer_loc_conf_app_group_name(ctx, plcf, clcf, &app_group_name);
        app_config_container = find_or_create_manifest_app_config_container(ctx, &app_group_name);
        loc_config_container = find_or_create_manifest_loc_config_container(ctx, app_config_container,
            cscf, clcf);
        *app_options_container = psg_json_value_get(app_config_container, "options", -1);
        *loc_options_container = psg_json_value_get(loc_config_container, "options", -1);
    }
}


static void
reverse_manifest_value_hierarchies(manifest_gen_ctx_t *ctx) {
    PsgJsonValue *app_config_container;
    PsgJsonValue *options_container;
    PsgJsonValue *location_configs_container, *location_config_container;

    reverse_value_hierarchies_in_options_container(ctx->global_config_container,
        ctx->it, ctx->end);
    reverse_value_hierarchies_in_options_container(ctx->default_app_config_container,
        ctx->it, ctx->end);
    reverse_value_hierarchies_in_options_container(ctx->default_loc_config_container,
        ctx->it, ctx->end);

    psg_json_value_begin(ctx->app_configs_container, ctx->it);
    psg_json_value_end(ctx->app_configs_container, ctx->end);
    while (!psg_json_value_iterator_eq(ctx->it, ctx->end)) {
        app_config_container = psg_json_value_iterator_get_value(ctx->it);

        options_container = psg_json_value_get(app_config_container,
            "options", -1);
        reverse_value_hierarchies_in_options_container(options_container,
            ctx->it2, ctx->end2);

        options_container = psg_json_value_get(app_config_container,
            "default_location_configuration", -1);
        reverse_value_hierarchies_in_options_container(options_container,
            ctx->it2, ctx->end2);

        location_configs_container = psg_json_value_get(app_config_container,
            "location_configurations", -1);
        if (location_configs_container != NULL) {
            psg_json_value_begin(location_configs_container, ctx->it2);
            psg_json_value_end(location_configs_container, ctx->end2);
            while (!psg_json_value_iterator_eq(ctx->it2, ctx->end2)) {
                location_config_container = psg_json_value_iterator_get_value(ctx->it2);

                options_container = psg_json_value_get(location_config_container,
                    "options", -1);
                reverse_value_hierarchies_in_options_container(options_container,
                    ctx->it3, ctx->end3);

                psg_json_value_iterator_advance(ctx->it2);
            }
        }

        psg_json_value_iterator_advance(ctx->it);
    }
}

static void
reverse_value_hierarchies_in_options_container(PsgJsonValue *options_container,
    PsgJsonValueIterator *it, PsgJsonValueIterator *end)
{
    PsgJsonValue *option_container, *value_hierarchy_doc;
    unsigned int i, len;

    psg_json_value_begin(options_container, it);
    psg_json_value_end(options_container, end);
    while (!psg_json_value_iterator_eq(it, end)) {
        option_container = psg_json_value_iterator_get_value(it);
        value_hierarchy_doc = psg_json_value_get(option_container,
            "value_hierarchy", -1);
        len = psg_json_value_size(value_hierarchy_doc);

        for (i = 0; i < len / 2; i++) {
            psg_json_value_swap(
                psg_json_value_get_at_index(value_hierarchy_doc, i),
                psg_json_value_get_at_index(value_hierarchy_doc, len - i - 1));
        }

        psg_json_value_iterator_advance(it);
    }
}

static void
add_manifest_options_container_dynamic_default(manifest_gen_ctx_t *ctx,
    PsgJsonValue *options_container,
    const char *option_name, size_t option_name_len,
    const char *desc, size_t desc_len)
{
    PsgJsonValue *option_container, *hierarchy, *hierarchy_member, *source;

    option_container = psg_json_value_get_or_create_null(options_container, option_name, option_name_len);
    if (psg_json_value_is_null(option_container)) {
        init_manifest_option_container(ctx, option_container);
    }
    hierarchy = psg_json_value_get(option_container, "value_hierarchy", -1);

    source = psg_json_value_new_with_type(PSG_JSON_VALUE_TYPE_OBJECT);
    psg_json_value_set_str(source, "type", "dynamic-default-description", -1);

    hierarchy_member = psg_json_value_new_with_type(PSG_JSON_VALUE_TYPE_OBJECT);
    psg_json_value_set_value(hierarchy_member, "source", -1, source);
    psg_json_value_set_str(hierarchy_member, "value", desc, desc_len);

    psg_json_value_append_val(hierarchy, hierarchy_member);

    psg_json_value_free(hierarchy_member);
    psg_json_value_free(source);
}

static PsgJsonValue *
add_manifest_options_container_default(manifest_gen_ctx_t *ctx,
    PsgJsonValue *options_container, const char *default_type,
    const char *option_name, size_t option_name_len)
{
    PsgJsonValue *option_container, *hierarchy, *hierarchy_member, *source, *result;

    option_container = psg_json_value_get_or_create_null(options_container, option_name, option_name_len);
    if (psg_json_value_is_null(option_container)) {
        init_manifest_option_container(ctx, option_container);
    }
    hierarchy = psg_json_value_get(option_container, "value_hierarchy", -1);

    source = psg_json_value_new_with_type(PSG_JSON_VALUE_TYPE_OBJECT);
    psg_json_value_set_str(source, "type", default_type, -1);

    hierarchy_member = psg_json_value_new_with_type(PSG_JSON_VALUE_TYPE_OBJECT);
    psg_json_value_set_value(hierarchy_member, "source", -1, source);

    result = psg_json_value_append_val(hierarchy, hierarchy_member);

    psg_json_value_free(hierarchy_member);
    psg_json_value_free(source);

    return result;
}

static void
add_manifest_options_container_static_default_str(manifest_gen_ctx_t *ctx,
    PsgJsonValue *options_container, const char *option_name, size_t option_name_len,
    const char *value, size_t value_len)
{
    PsgJsonValue *hierarchy_member = add_manifest_options_container_default(
        ctx, options_container, "default", option_name, option_name_len);
    psg_json_value_set_str(hierarchy_member, "value", value, value_len);
}

static void
add_manifest_options_container_static_default_int(manifest_gen_ctx_t *ctx,
    PsgJsonValue *options_container, const char *option_name, size_t option_name_len,
    int value)
{
    PsgJsonValue *hierarchy_member = add_manifest_options_container_default(
        ctx, options_container, "default", option_name, option_name_len);
    psg_json_value_set_int(hierarchy_member, "value", value);
}

static void
add_manifest_options_container_static_default_uint(manifest_gen_ctx_t *ctx,
    PsgJsonValue *options_container, const char *option_name, size_t option_name_len,
    unsigned int value)
{
    PsgJsonValue *hierarchy_member = add_manifest_options_container_default(
        ctx, options_container, "default", option_name, option_name_len);
    psg_json_value_set_uint(hierarchy_member, "value", value);
}

static void
add_manifest_options_container_static_default_bool(manifest_gen_ctx_t *ctx,
    PsgJsonValue *options_container, const char *option_name, size_t option_name_len,
    int value)
{
    PsgJsonValue *hierarchy_member = add_manifest_options_container_default(
        ctx, options_container, "default", option_name, option_name_len);
    psg_json_value_set_bool(hierarchy_member, "value", value);
}

static void
add_manifest_options_container_inferred_default_str(manifest_gen_ctx_t *ctx,
    PsgJsonValue *options_container, const char *option_name, size_t option_name_len,
    const char *value, size_t value_len)
{
    PsgJsonValue *hierarchy_member = add_manifest_options_container_default(
        ctx, options_container, "inferred-default", option_name, option_name_len);
    psg_json_value_set_str(hierarchy_member, "value", value, value_len);
}

static void
manifest_inherit_application_value_hierarchies(manifest_gen_ctx_t *ctx) {
    PsgJsonValue *app_config_container, *options_container, *option_container, *default_app_config;
    PsgJsonValue *value_hierarchy_doc, *value_hierarchy_from_default;
    const char *option_name;
    size_t option_name_len;

    /* Iterate through all 'application_configurations' objects */
    psg_json_value_begin(ctx->app_configs_container, ctx->it);
    psg_json_value_end(ctx->app_configs_container, ctx->end);
    while (!psg_json_value_iterator_eq(ctx->it, ctx->end)) {
        app_config_container = psg_json_value_iterator_get_value(ctx->it);

        /* Iterate through all its 'options' objects */
        options_container = psg_json_value_get(app_config_container, "options", -1);
        psg_json_value_begin(options_container, ctx->it2);
        psg_json_value_end(options_container, ctx->end2);
        while (!psg_json_value_iterator_eq(ctx->it2, ctx->end2)) {
            /* For each option, inherit the value hierarchies
             * from the 'default_application_configuration' object.
             *
             * Since the value hierarchy array is already in
             * most-to-least-specific order, simply appending
             * the 'default_application_configuration' hierarchy is
             * enough.
             */
            option_name = psg_json_value_iterator_get_name(ctx->it2,
                &option_name_len);
            option_container = psg_json_value_iterator_get_value(ctx->it2);
            default_app_config = psg_json_value_get(ctx->default_app_config_container,
                option_name, option_name_len);
            if (default_app_config != NULL) {
                value_hierarchy_doc = psg_json_value_get(option_container,
                    "value_hierarchy", -1);
                value_hierarchy_from_default = psg_json_value_get(default_app_config,
                    "value_hierarchy", -1);
                psg_json_value_append_vals(value_hierarchy_doc,
                    value_hierarchy_from_default,
                    ctx->it3, ctx->end3);
                maybe_inherit_string_array_hierarchy_values(value_hierarchy_doc,
                    ctx->it3, ctx->end3);
                maybe_inherit_string_keyval_hierarchy_values(value_hierarchy_doc,
                    ctx->it3, ctx->end3);
            }

            psg_json_value_iterator_advance(ctx->it2);
        }

        /* Iterate through all 'default_application_configuration' options */
        psg_json_value_begin(ctx->default_app_config_container, ctx->it2);
        psg_json_value_end(ctx->default_app_config_container, ctx->end2);
        while (!psg_json_value_iterator_eq(ctx->it2, ctx->end2)) {
            /* For each default app config object, if there is no object in
             * the current context's 'options' with the same name, then add
             * it there.
             */
            option_name = psg_json_value_iterator_get_name(ctx->it2,
                &option_name_len);
            if (!psg_json_value_is_member(options_container, option_name, option_name_len)) {
                option_container = psg_json_value_iterator_get_value(ctx->it2);
                psg_json_value_set_value(options_container, option_name, option_name_len,
                    option_container);
            }

            psg_json_value_iterator_advance(ctx->it2);
        }

        psg_json_value_iterator_advance(ctx->it);
    }
}

static void
manifest_inherit_location_value_hierarchies(manifest_gen_ctx_t *ctx) {
    PsgJsonValue *app_config_container, *location_configs_container, *location_config_container;
    PsgJsonValue *app_default_location_configs;
    PsgJsonValue *options_container, *option_container, *default_location_config;
    PsgJsonValue *value_hierarchy_doc, *value_hierarchy_from_default;
    const char *option_name;
    size_t option_name_len;

    /* Iterate through all 'application_configurations' objects */
    psg_json_value_begin(ctx->app_configs_container, ctx->it);
    psg_json_value_end(ctx->app_configs_container, ctx->end);
    while (!psg_json_value_iterator_eq(ctx->it, ctx->end)) {
        app_config_container = psg_json_value_iterator_get_value(ctx->it);

        /* Iterate through all its 'default_location_configuration' options */
        app_default_location_configs = psg_json_value_get(app_config_container,
            "default_location_configuration", -1);
        options_container = app_default_location_configs;
        psg_json_value_begin(options_container, ctx->it2);
        psg_json_value_end(options_container, ctx->end2);
        while (!psg_json_value_iterator_eq(ctx->it2, ctx->end2)) {
            /* For each option, inherit the value hierarchies
             * from the top-level 'default_application_configuration' object.
             *
             * Since the value hierarchy array is already in
             * most-to-least-specific order, simply appending
             * the 'default_application_configuration' hierarchy is
             * enough.
             */
            option_name = psg_json_value_iterator_get_name(ctx->it2, &option_name_len);
            option_container = psg_json_value_iterator_get_value(ctx->it2);
            default_location_config = psg_json_value_get(ctx->default_loc_config_container,
                option_name, option_name_len);
            if (default_location_config != NULL) {
                value_hierarchy_doc = psg_json_value_get(option_container,
                    "value_hierarchy", -1);
                value_hierarchy_from_default = psg_json_value_get(default_location_config,
                    "value_hierarchy", -1);
                psg_json_value_append_vals(value_hierarchy_doc,
                    value_hierarchy_from_default,
                    ctx->it3, ctx->end3);
                maybe_inherit_string_array_hierarchy_values(value_hierarchy_doc,
                    ctx->it3, ctx->end3);
                maybe_inherit_string_keyval_hierarchy_values(value_hierarchy_doc,
                    ctx->it3, ctx->end3);
            }

            psg_json_value_iterator_advance(ctx->it2);
        }

        /* Iterate through all top-level 'default_location_configuration' options */
        psg_json_value_begin(ctx->default_loc_config_container, ctx->it2);
        psg_json_value_end(ctx->default_loc_config_container, ctx->end2);
        while (!psg_json_value_iterator_eq(ctx->it2, ctx->end2)) {
            /* For each default top-level 'default_location_configuration' option,
             * if there is no object in the current context's 'default_application_configuration'
             * with the same name, then add it there.
             */
            option_name = psg_json_value_iterator_get_name(ctx->it2,
                &option_name_len);
            if (!psg_json_value_is_member(options_container, option_name, option_name_len)) {
                option_container = psg_json_value_iterator_get_value(ctx->it2);
                psg_json_value_set_value(options_container, option_name, option_name_len,
                    option_container);
            }

            psg_json_value_iterator_advance(ctx->it2);
        }

        /* Iterate through all its 'locations_configurations' options */
        location_configs_container = psg_json_value_get(app_config_container,
            "location_configurations", -1);
        if (location_configs_container != NULL) {
            psg_json_value_begin(location_configs_container, ctx->it2);
            psg_json_value_end(location_configs_container, ctx->end2);
            while (!psg_json_value_iterator_eq(ctx->it2, ctx->end2)) {
                location_config_container = psg_json_value_iterator_get_value(ctx->it2);

                options_container = psg_json_value_get(location_config_container,
                    "options", -1);
                psg_json_value_begin(options_container, ctx->it3);
                psg_json_value_end(options_container, ctx->end3);
                while (!psg_json_value_iterator_eq(ctx->it3, ctx->end3)) {
                    /* For each option, inherit the value hierarchies
                     * from the 'default_location_configuration' belonging
                     * to the current app (which also contains the global
                     * location config defaults).
                     *
                     * Since the value hierarchy array is already in
                     * most-to-least-specific order, simply appending
                     * the 'default_location_configuration' hierarchy is
                     * enough.
                     */
                    option_name = psg_json_value_iterator_get_name(ctx->it3, &option_name_len);
                    option_container = psg_json_value_iterator_get_value(ctx->it3);
                    default_location_config = psg_json_value_get(app_default_location_configs,
                        option_name, option_name_len);
                    if (default_location_config != NULL) {
                        value_hierarchy_doc = psg_json_value_get(option_container,
                            "value_hierarchy", -1);
                        value_hierarchy_from_default = psg_json_value_get(default_location_config,
                            "value_hierarchy", -1);
                        psg_json_value_append_vals(value_hierarchy_doc,
                            value_hierarchy_from_default,
                            ctx->it4, ctx->end4);
                        maybe_inherit_string_array_hierarchy_values(value_hierarchy_doc,
                            ctx->it4, ctx->end4);
                        maybe_inherit_string_keyval_hierarchy_values(value_hierarchy_doc,
                            ctx->it4, ctx->end4);
                    }

                    psg_json_value_iterator_advance(ctx->it3);
                }

                psg_json_value_iterator_advance(ctx->it2);
            }
        }

        psg_json_value_iterator_advance(ctx->it);
    }
}

static void
maybe_inherit_string_array_hierarchy_values(PsgJsonValue *value_hierarchy_doc,
    PsgJsonValueIterator *it, PsgJsonValueIterator *end)
{
    PsgJsonValue *value, *current, *next, *current_value, *next_value;
    unsigned int len;
    int i;

    if (psg_json_value_size(value_hierarchy_doc) == 0) {
        return;
    }
    value = psg_json_value_get(psg_json_value_get_at_index(value_hierarchy_doc, 0),
        "value", -1);
    if (psg_json_value_type(value) != PSG_JSON_VALUE_TYPE_ARRAY) {
        return;
    }

    len = psg_json_value_size(value_hierarchy_doc);
    for (i = len - 1; i >= 1; i--) {
        current = psg_json_value_get_at_index(value_hierarchy_doc, i);
        next = psg_json_value_get_at_index(value_hierarchy_doc, i - 1);

        current_value = psg_json_value_get(current, "value", -1);
        next_value = psg_json_value_get(next, "value", -1);

        psg_json_value_begin(current_value, it);
        psg_json_value_end(current_value, end);
        while (!psg_json_value_iterator_eq(it, end)) {
            if (!json_array_contains(next_value, psg_json_value_iterator_get_value(it))) {
                psg_json_value_append_val(next_value, psg_json_value_iterator_get_value(it));
            }
            psg_json_value_iterator_advance(it);
        }
    }
}

static void
maybe_inherit_string_keyval_hierarchy_values(PsgJsonValue *value_hierarchy_doc,
    PsgJsonValueIterator *it, PsgJsonValueIterator *end)
{
    PsgJsonValue *value, *current, *next, *current_value, *next_value;
    const char *name;
    size_t name_len;
    unsigned int len;
    int i;

    if (psg_json_value_size(value_hierarchy_doc) == 0) {
        return;
    }
    value = psg_json_value_get(psg_json_value_get_at_index(value_hierarchy_doc, 0),
        "value", -1);
    if (psg_json_value_type(value) != PSG_JSON_VALUE_TYPE_OBJECT) {
        return;
    }

    len = psg_json_value_size(value_hierarchy_doc);
    for (i = len - 1; i >= 1; i--) {
        current = psg_json_value_get_at_index(value_hierarchy_doc, i);
        next = psg_json_value_get_at_index(value_hierarchy_doc, i - 1);

        current_value = psg_json_value_get(current, "value", -1);
        next_value = psg_json_value_get(next, "value", -1);

        psg_json_value_begin(current_value, it);
        psg_json_value_end(current_value, end);
        while (!psg_json_value_iterator_eq(it, end)) {
            name = psg_json_value_iterator_get_name(it, &name_len);
            if (!psg_json_value_is_member(next_value, name, name_len)) {
                psg_json_value_set_value(next_value, name, name_len,
                    psg_json_value_iterator_get_value(it));
            }
            psg_json_value_iterator_advance(it);
        }
    }
}


static void
psg_json_value_append_vals(PsgJsonValue *doc, PsgJsonValue *doc2,
    PsgJsonValueIterator *it, PsgJsonValueIterator *end)
{
    PsgJsonValue *elem;

    psg_json_value_begin(doc2, it);
    psg_json_value_end(doc2, end);
    while (!psg_json_value_iterator_eq(it, end)) {
        elem = psg_json_value_iterator_get_value(it);
        psg_json_value_append_val(doc, elem);
        psg_json_value_iterator_advance(it);
    }
}

static int
json_array_contains(PsgJsonValue *doc, PsgJsonValue *elem) {
    unsigned int i, len;
    PsgJsonValue *current;

    len = psg_json_value_size(doc);
    for (i = 0; i < len; i++) {
        current = psg_json_value_get_at_index(doc, i);
        if (psg_json_value_eq(current, elem)) {
            return 1;
        }
    }

    return 0;
}
