// Copyright (C) 2016, 2017  Stefan Vargyas
// 
// This file is part of Json-Type.
// 
// Json-Type is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// Json-Type is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with Json-Type.  If not, see <http://www.gnu.org/licenses/>.

#include "config.h"

#include <getopt.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <sys/time.h>
#include <errno.h>
#include <math.h>

#include "common.h"
#include "ptr-traits.h"
#include "bit-set.h"

#include "test-common.h"

const char program[] = STRINGIFY(PROGRAM);
const char verdate[] = "0.2 -- 2017-05-31 09:55"; // $ date +'%F %R'

const char help[] = 
"usage: %s [ACTION|OPTION]...\n"
"where the actions are:\n"
"  -T|--gen-type        generate json type\n"
"  -V|--gen-value       generate JSON value (default)\n"
"and the options are:\n"
"  -a|--alpha-keys      generate alphabetical keys instead\n"
"                         of numerical\n"
"  -d|--depth=NUM       depth of generated JSON objects\n"
"                         (default value: 2)\n"
"  -e|--expr=EXPR       when action is `-T|--gen-type' and\n"
"                         the types generated are \"dict\"s,\n"
"                         generate \"expr\" expressions; EXPR\n"
"                         is a comma-separated list of strings\n"
"                       when action is `-V|--gen-value' do\n"
"                         generate only the specified keys;\n"
"                         EXPR is a semicolon-separated list\n"
"                         of comma-separated list of key names\n"
"  -k|--n-key=NUM       number of keys on first generated\n"
"                         level of JSON objects (default\n"
"                         value: 4)\n"
"  -n|--div=NUM         divider for the number of keys at\n"
"                         each level of the generated JSON\n"
"                         objects (default value: 4)\n"
"  -p|--plain-types     when action is `-T|--gen-type' do\n"
"                         generate the leaf type objects\n"
"                         to be \"plain\" type objects\n"
"                         instead of \"number\"'s\n"
"  -r|--randomize       when action is `-V|--gen-value' do\n"
"                         randomize the order of arguments\n"
"                         of the generated object\n"
"  -t|--json-type=TYPE  the kind of json types generated:\n"
"     --object            either 'o[bject]' or 'd[ict]';\n"
"     --dict              the default is 'object'\n"
"     --dump-options    print options and exit\n"
"     --version         print version numbers and exit\n"
"  -?|--help            display this help info and exit\n";

enum options_action_t
{
    options_gen_type_action,
    options_gen_value_action,
};

enum options_json_type_t
{
    options_json_object_type,
    options_json_dict_type,
};

struct options_t
{
    enum options_action_t
                 action;
    enum options_json_type_t
                 json_type;
    bits_t       alpha_keys: 1;
    bits_t       plain_types: 1;
    bits_t       randomize: 1;
    const char*  expr;
    size_t       depth;
    size_t       nkey;
    size_t       div;
    size_t       argc;
    char* const *argv;
};

static void options_done(struct options_t* opts, size_t n_arg)
{
    ASSERT_SIZE_SUB_NO_OVERFLOW(opts->argc, n_arg);
    opts->argc -= n_arg;
    opts->argv += n_arg;
}

static void options_dump(const struct options_t* opts)
{
    static const char* const noyes[] = {
        [0] "no", [1] "yes"
    };
#define CASE2(n0, n1) \
    [options_ ## n0 ## _ ## n1 ## _action] = #n0 "-" #n1
    static const char* const actions[] = {
        CASE2(gen, type),
        CASE2(gen, value),
    };
#define CASE(n) \
    [options_json_ ## n ## _type] = #n
    static const char* const json_types[] = {
        CASE(object),
        CASE(dict),
    };
    char* const* p;
    size_t i;

#define NAME(x)  OPTIONS_NAME(x)
#define NNUL(x)  OPTIONS_NNUL(x)
#define NOYES(x) OPTIONS_NOYES(x)

    fprintf(stdout,
        "action:      %s\n"
        "json-type:   %s\n"
        "alpha-keys:  %s\n"
        "plain-types: %s\n"
        "randomize:   %s\n"
        "expr:        %s\n"
        "depth:       %zu\n"
        "nkey:        %zu\n"
        "div:         %zu\n"
        "argc:        %zu\n",
        NAME(action),
        NAME(json_type),
        NOYES(alpha_keys),
        NOYES(plain_types),
        NOYES(randomize),
        NNUL(expr),
        opts->depth,
        opts->nkey,
        opts->div,
        opts->argc);

    for (i = 0, p = opts->argv; i < opts->argc; i ++, p ++)
        fprintf(stdout, "argv[%zu]:     %s\n", i, *p);
}

enum {
    // stev: actions:
    options_gen_type_act    = 'T',
    options_gen_value_act   = 'V',

    // stev: options:
    options_alpha_keys_opt  = 'a',
    options_expr_opt        = 'e',
    options_depth_opt       = 'd',
    options_nkey_opt        = 'k',
    options_div_opt         = 'n',
    options_plain_types_opt = 'p',
    options_randomize_opt   = 'r',
    options_json_type_opt   = 't',

    options_object_opt      = 256,
    options_dict_opt,
};

// $ . ~/lookup-gen/commands.sh
// $ print() { printf '%s\n' "$@"; }
// $ adjust-func() { ssed -R '1s/^/static /;1s/\&/*/;s/(,\s+)/\1enum /;s/(?=t =)/*/;1s/(?<=\()/\n\t/;s/([a-z0-9_]+)(_type)_t::([a-z]+)/\1_\3\2/'; }

// $ print 'o object' 'd dict'|gen-func -f options_lookup_json_type -r options_json_type_t|adjust-func

static bool options_lookup_json_type(
    const char* n, enum options_json_type_t* t)
{
    // pattern: d[ict]|o[bject]
    switch (*n ++) {
    case 'd':
        if (*n == 0) {
            *t = options_json_dict_type;
            return true;
        }
        if (*n ++ == 'i' &&
            *n ++ == 'c' &&
            *n ++ == 't' &&
            *n == 0) {
            *t = options_json_dict_type;
            return true;
        }
        return false;
    case 'o':
        if (*n == 0) {
            *t = options_json_object_type;
            return true;
        }
        if (*n ++ == 'b' &&
            *n ++ == 'j' &&
            *n ++ == 'e' &&
            *n ++ == 'c' &&
            *n ++ == 't' &&
            *n == 0) {
            *t = options_json_object_type;
            return true;
        }
    }
    return false;
}

static enum options_json_type_t
    options_parse_json_type_optarg(
        const char* opt_name, const char* opt_arg)
{
    enum options_json_type_t r;

    ASSERT(opt_arg != NULL);
    if (!options_lookup_json_type(opt_arg, &r))
        options_invalid_opt_arg(opt_name, opt_arg);

    return r;
}

static bool options_parse(struct options_t* opts,
    int opt, const char* opt_arg)
{
    switch (opt) {
    case options_gen_type_act:
        opts->action = options_gen_type_action;
        break;
    case options_gen_value_act:
        opts->action = options_gen_value_action;
        break;
    case options_json_type_opt:
        opts->json_type = options_parse_json_type_optarg(
            "json-type", opt_arg);
        break;
    case options_object_opt:
        opts->json_type = options_json_object_type;
        break;
    case options_dict_opt:
        opts->json_type = options_json_dict_type;
        break;
    case options_alpha_keys_opt:
        opts->alpha_keys = true;
        break;
    case options_plain_types_opt:
        opts->plain_types = true;
        break;
    case options_randomize_opt:
        opts->randomize = true;
        break;
    case options_depth_opt:
        opts->depth = options_parse_size_optarg(
            "depth", opt_arg, 1, 0);
        break;
    case options_nkey_opt:
        opts->nkey = options_parse_size_optarg(
            "nkey", opt_arg, 1, 0);
        break;
    case options_div_opt:
        opts->div = options_parse_size_optarg(
            "div", opt_arg, 1, 0);
        break;
    case options_expr_opt:
        opts->expr = opt_arg;
        break;
    default:
        return false;
    }
    return true;
}

static const struct options_t* options(
    int argc, char* argv[])
{
    static struct options_t opts = {
        .action      = options_gen_value_action,
        .json_type   = options_json_object_type,
        .alpha_keys  = false,
        .plain_types = false,
        .randomize   = false,
        .expr        = NULL,
        .depth       = 2,
        .nkey        = 4,
        .div         = 4,
    };

    static const struct option longs[] = {
        { "gen-type",    0, 0, options_gen_type_act },
        { "gen-value",   0, 0, options_gen_value_act },
        { "alpha-keys",  0, 0, options_alpha_keys_opt },
        { "plain-types", 0, 0, options_plain_types_opt },
        { "randomize",   0, 0, options_randomize_opt },
        { "json-type",   1, 0, options_json_type_opt },
        { "object",      0, 0, options_object_opt },
        { "dict",        0, 0, options_dict_opt },
        { "expr",        1, 0, options_expr_opt },
        { "depth",       1, 0, options_depth_opt },
        { "nkey",        1, 0, options_nkey_opt },
        { "div",         1, 0, options_div_opt },
    };
    static const char shorts[] = "TV" "ad:e:k:n:prt:";

    static const struct options_funcs_t funcs = {
        .done  = (options_done_func_t)  options_done,
        .parse = (options_parse_func_t) options_parse,
        .dump  = (options_dump_func_t)  options_dump,
    };

    opts.argc = INT_AS_SIZE(argc);
    opts.argv = argv;

    options_parse_args(
        &opts, &funcs,
        shorts, ARRAY_SIZE(shorts) - 1,
        longs, ARRAY_SIZE(longs),
        argc, argv);

    return &opts;
}

// stev: declaring the constant
// below as member of an anonymous
// 'enum' makes that an 'int', but
// need it to be of type 'size_t'
#define GEN_ALPHA_BASE SZ(26)

// stev: TODO: promote SIZE_MUL_DOUBLE
// to the global scope of 'int-traits.h'
#define SIZE_MUL_DOUBLE(x, d)            \
    ({                                   \
        double __r;                      \
        STATIC(TYPEOF_IS_SIZET(x));      \
        STATIC(TYPEOF_IS(d, double));    \
        __r = (double) (x) * (d);        \
        VERIFY(__r < (double) SIZE_MAX); \
        (size_t) __r;                    \
    })

// stev: Knuth, TAOCP, vol 2, 3rd edition,
// 3.4.2 Random Sampling and Shuffling, p. 145
// Algorithm P (Shuffling)

static void shuffle(size_t keys[], size_t n)
{
    struct timeval t;
    size_t j;

    if (n <= 1)
        return;

    if (gettimeofday(&t, NULL) < 0)
        t.tv_usec = 0;
    srand48(INT_AS_SIZE(t.tv_usec));

    for (j = n - 1; j > 0; j --) {
        size_t k, t;
        double u;

        // knuth: generate a random number 'u',
        // uniformly distributed between 0 and 1

        // stev: drand48(3) man page claims 'drand48'
        // is generating values uniformly distributed
        // in [0.0, 1.0)
        u = drand48();
        ASSERT(u >= 0.0 && u < 1.0);

        // stev: make 'k' be a random integer,
        // between 0 and 'j - 1'
        k = SIZE_MUL_DOUBLE(j, u);
        VERIFY(k < j);

        // stev: swap 'keys[k]' and 'keys[j]'
        t = keys[k];
        keys[k] = keys[j];
        keys[j] = t;
    }
}

size_t str_count(
    const char* str, char ch)
{
    const char* p = str;
    size_t r = 0;

    while ((p = strchr(p, ch)) != NULL) {
        p ++;
        r ++;
    }

    return r;
}

static size_t str_split(
    const char* str, char sep,
    const char*** res)
{
    size_t s = sizeof(char*), n, m;
    const char **p, **f, **l;
    char* q;

    n = str_count(str, sep);
    ASSERT_SIZE_INC_NO_OVERFLOW(n);
    n ++;

    m = strlen(str);
    ASSERT_SIZE_INC_NO_OVERFLOW(m);
    m ++;

    // stev: memory layout of returned 'res':
    // (1) char* [0..n-1]: table of pointers
    //     inside the next area
    // (2) char [0..m-1]: copy of input 'str',
    //     with NUL chars replacing all 'sep'

    SIZE_MUL_EQ(s, n);
    SIZE_ADD_EQ(s, m);

    f = malloc(s);
    VERIFY(f != NULL);

    q = (char*) (f + n);
    strcpy(q, str);

    for (p = f,
         l = p + (n - 1);
         p < l;
         p ++,
         q ++) {
        *p = q;

        if (!(q = strchr(q, sep)))
            ASSERT(false);

        *q = 0;
    }
    *p = q;

    *res = f;
    return n;
}

static void reverse(
    char* ptr, size_t len)
{
    char *p, *q;

    if (len < 2)
        return;

    for (p = ptr,
         q = ptr + (len - 1);
         p < q;
         p ++,
         q --) {
        char c = *p;
        *p = *q;
        *q = c;
    }
}

#define SIZE_LOGN(n, x)                  \
    ({                                   \
        double __r;                      \
        STATIC(TYPEOF_IS_SIZET(n));      \
        STATIC(TYPEOF_IS_SIZET(x));      \
        ASSERT(n > 0);                   \
        ASSERT(x > 0);                   \
        __r = ceil(log(x) / log(n));     \
        VERIFY(__r < (double) SIZE_MAX); \
        (size_t) __r;                    \
    })

struct gen_type_expr_t
{
    size_t       size;
    const char** args;
};

static struct gen_type_expr_t*
    gen_type_expr_create(const char* expr)
{
    struct gen_type_expr_t* e;

    e = malloc(sizeof(*e));
    VERIFY(e != NULL);

    e->size = str_split(expr, ',', &e->args);

    return e;
}

static void gen_type_expr_destroy(
    struct gen_type_expr_t* expr)
{
    free(expr->args);
    free(expr);
}

static const char* gen_type_expr_get(
    struct gen_type_expr_t* expr,
    size_t index)
{
    if (index < expr->size) {
        // size > 0 => args != NULL
        ASSERT(expr->args != NULL);

        return expr->args[index];
    }
    return NULL;
}

struct gen_value_expr_t
{
    size_t            size;
    struct bit_set_t* args;
};

#define GEN_VALUE_BIT_SET_ALLOC(s)  \
    ({                              \
        STATIC(TYPEOF_IS_SIZET(s)); \
        VERIFY(false);              \
        NULL;                       \
    })

typedef bool (*gen_parse_key_func_t)(
    const char*, size_t*);

static struct gen_value_expr_t*
    gen_value_expr_create(
        const char* expr,
        gen_parse_key_func_t func)
{
    struct gen_value_expr_t* r;
    const char **t, **q, **f;
    struct bit_set_t *p, *e;

    ASSERT(expr != NULL);
    ASSERT(func != NULL);

    r = malloc(sizeof(*r));
    VERIFY(r != NULL);

    r->size = str_split(expr, ';', &t);

    r->args = calloc(
        r->size, sizeof(r->args[0]));
    VERIFY(r->args != NULL);

    for (p = r->args,
         e = p + r->size,
         q = t,
         f = q + r->size;
         p < e;
         p ++,
         q ++) {
        const char **u, **v, **w;
        size_t n;

        BIT_SET_INIT(
            p, GEN_VALUE_BIT_SET_ALLOC,
            BIT_SET_VAL_BITS);

        ASSERT(q < f);
        n = str_split(*q, ',', &u);

        for (v = u,
             w = v + n;
             v < w;
             v ++) {
            size_t k;

            if (!func(*v, &k))
                error("invalid key \"%s\"", *v);

            if (k >= BIT_SET_VAL_BITS)
                error("invalid key \"%s\": max is %zu",
                    *v, BIT_SET_VAL_BITS - 1);

            BIT_SET_SET(p, k);
        }

        free(u);
    }

    free(t);

    return r;
}

static void gen_value_expr_destroy(
    struct gen_value_expr_t* expr)
{
    free(expr->args);
    free(expr);
}

static bool gen_value_expr_get(
    struct gen_value_expr_t* expr,
    size_t index, size_t bit)
{
    ASSERT(bit < BIT_SET_VAL_BITS);

    if (index < expr->size) {
        // size > 0 => args != NULL
        ASSERT(expr->args != NULL);

        return BIT_SET_TEST(
            &expr->args[index], bit);
    }
    return false;
}

static bool gen_expr_parse_alpha_key(
    const char* str, size_t* res)
{
    size_t r = 0;

    ASSERT(str != NULL);

    if (str[0] == 'a' &&
        str[1] != '\0')
        return false;

    do {
        uchar_t c = *str;
        size_t v;

        if (c < 'a' || c > 'z')
            return false;
        v = c - 'a';

        if (!SIZE_MUL_NO_OVERFLOW(r, GEN_ALPHA_BASE))
            return false;
        r *= GEN_ALPHA_BASE;

        if (!SIZE_ADD_NO_OVERFLOW(r, v))
            return false;
        r += v;
    } while (*++ str);

    *res = r;
    return true;
}

static bool gen_expr_parse_num_key(
    const char* str, size_t* res)
{
    const char* p = NULL;
    size_t r;

    if (str[0] == '0' &&
        str[1] != '\0')
        return false;

    r = parse_num(str, &p, 10);
    if (errno || *p)
        return false;

    *res = r;
    return true;
}

enum gen_expr_type_t
{
    gen_expr_none_type,
    gen_expr_type_type,
    gen_expr_value_type
};

struct gen_expr_t
{
    enum gen_expr_type_t type;
    union {
        // ...                   none;
        struct gen_type_expr_t*  type;
        struct gen_value_expr_t* value;
    } val;
};

static void gen_expr_init(
    struct gen_expr_t* expr,
    const struct options_t* opt)
{
    memset(expr, 0, sizeof(*expr));

    if (opt->expr == NULL ||
        opt->json_type != options_json_dict_type)
        expr->type = gen_expr_none_type;
    else
    if (opt->action == options_gen_type_action) {
        expr->type = gen_expr_type_type;
        expr->val.type = gen_type_expr_create(
            opt->expr);
    }
    else
    if (opt->action == options_gen_value_action) {
        expr->type = gen_expr_value_type;
        expr->val.value = gen_value_expr_create(
            opt->expr, opt->alpha_keys
            ? gen_expr_parse_alpha_key
            : gen_expr_parse_num_key);
    }
    else
        UNEXPECT_VAR("%d", opt->action);
}

static void gen_expr_done(
    struct gen_expr_t* expr)
{
    if (expr->type == gen_expr_none_type)
        ; // stev: nop
    else
    if (expr->type == gen_expr_type_type)
        gen_type_expr_destroy(expr->val.type);
    else
    if (expr->type == gen_expr_value_type)
        gen_value_expr_destroy(expr->val.value);
    else
        UNEXPECT_VAR("%d", expr->type);
}

#define GEN_EXPR_IS_(q, e, t)                       \
    (                                               \
        STATIC(TYPEOF_IS(e, q struct gen_expr_t*)), \
        (e)->type == gen_expr_ ## t ## _type        \
    )
#define GEN_EXPR_AS_IF_(q, e, t) \
    ({                           \
        GEN_EXPR_IS_(q, e, t)    \
        ? (e)->val.t : NULL;     \
    })
#define GEN_EXPR_AS_IF_CONST(e, t) \
    GEN_EXPR_AS_IF_(const, e, t)

struct gen_key_t;

typedef void (*gen_key_func_t)(
    const struct gen_key_t*,
    size_t, FILE*);

struct gen_key_t
{
    gen_key_func_t func;
    size_t         size;
    char*          buf;
};

static void gen_key_alpha_gen(
    const struct gen_key_t* key,
    size_t num, FILE* file)
{
    size_t n = 0;
    char* s;

    ASSERT(key->size > 0);
    ASSERT(key->buf != NULL);
    s = key->buf;

    do {
        ASSERT(n < key->size);
        *s ++ = 'a' + (num % GEN_ALPHA_BASE);
        n ++;
    } while (num /= GEN_ALPHA_BASE);

    reverse(key->buf, n);

    fprintf(file, "%.*s",
        SIZE_AS_INT(n), key->buf);
}

static void gen_key_num_gen(
    const struct gen_key_t* key UNUSED,
    size_t num, FILE* file)
{
    fprintf(file, "%zu", num);
}

static void gen_key_init(
    struct gen_key_t* key,
    const struct options_t* opt)
{
    memset(key, 0, sizeof(*key));

    if (opt->alpha_keys) {
        key->size = SIZE_LOGN(
            GEN_ALPHA_BASE, opt->nkey);

        key->buf = malloc(key->size);
        VERIFY(key->buf != NULL);

        key->func = gen_key_alpha_gen;
    }
    else
        key->func = gen_key_num_gen;
}

static void gen_key_done(
    struct gen_key_t* key)
{
    if (key->buf != NULL)
        free(key->buf);
}

static void gen_key_gen(
    const struct gen_key_t* key,
    size_t num, FILE* file)
{
    key->func(key, num, file);
}

struct gen_t
{
    FILE*        output;
    enum options_json_type_t
                 json_type;
    bits_t       plain_types: 1;
    bits_t       randomize: 1;
    size_t       nkey_div;
    struct gen_expr_t
                 expr;
    struct gen_key_t
                 key;
};

static void gen_init(
    struct gen_t* gen,
    const struct options_t* opt)
{
    memset(gen, 0, sizeof(*gen));

    ASSERT(opt->div > 0);

    gen->output = stdout;
    gen->json_type = opt->json_type;
    gen->plain_types = opt->plain_types;
    gen->randomize = opt->randomize;
    gen->nkey_div = opt->div;

    gen_expr_init(&gen->expr, opt);
    gen_key_init(&gen->key, opt);
}

static void gen_done(struct gen_t* gen)
{
    gen_key_done(&gen->key);
    gen_expr_done(&gen->expr);
}

#define CASE(n) [options_json_ ## n ## _type] = #n
static const char* const gen_json_types[] = {
    CASE(object),
    CASE(dict)
};

#define SIZE_DIV(x, y)              \
    ({                              \
        STATIC(TYPEOF_IS_SIZET(x)); \
        STATIC(TYPEOF_IS_SIZET(y)); \
        ASSERT(y > 0);              \
        (x) / (y);                  \
    })

#define print_char(c) \
    fputc(c, gen->output)
#define print_str(s) \
    fputs(s, gen->output)
#define print_fmt(f, ...) \
    fprintf(gen->output, f, ## __VA_ARGS__)

static void gen_type_gen(
    const struct gen_t* gen,
    size_t nkey, size_t depth,
    size_t level)
{
    struct gen_type_expr_t* e;
    const char* s;
    size_t i;

    print_fmt("{\"type\":\"%s\",\"args\":[",
        ARRAY_CHAR_NULL_ELEM(gen_json_types,
            gen->json_type));

    for (i = 0; i < nkey; i ++) {
        print_str("{\"name\":\"");
        gen_key_gen(&gen->key, i, gen->output);
        print_str("\",\"type\":");

        if (depth > 1)
            gen_type_gen(
                gen, SIZE_DIV(nkey, gen->nkey_div),
                depth - 1, level + 1);
        else
        if (gen->plain_types)
            print_fmt("{\"plain\":%zu}", i);
        else
            print_str("\"number\"");

        print_char('}');

        if (i < nkey - 1)
            print_char(',');
    }

    print_char(']');

    if ((e = GEN_EXPR_AS_IF_CONST(&gen->expr, type)) &&
        (s = gen_type_expr_get(e, level)))
        print_fmt(",\"expr\":\"%s\"", s);

    print_char('}');
}

static void gen_keys_gen(
    const struct gen_t* gen,
    size_t keys[], size_t n)
{
    size_t i;

    for (i = 0; i < n; i ++)
        keys[i] = i;

    if (gen->randomize)
        shuffle(keys, n);
}

static void gen_value_gen(
    const struct gen_t* gen,
    size_t nkey, size_t depth,
    size_t level)
{
    struct gen_value_expr_t* e;
    size_t s[nkey];
    size_t i, n;

    e = GEN_EXPR_AS_IF_CONST(&gen->expr, value);

    gen_keys_gen(gen, s, nkey);

    print_char('{');

    for (i = 0, n = 0; i < nkey; i ++) {
        if (e && !gen_value_expr_get(e, level, s[i]))
            continue;

        if (n ++)
            print_char(',');

        print_char('"');
        gen_key_gen(&gen->key, s[i], gen->output);
        print_str("\":");

        if (depth > 1)
            gen_value_gen(
                gen, SIZE_DIV(nkey, gen->nkey_div),
                depth - 1, level + 1);
        else
            print_fmt("%zu", s[i]);
    }

    print_char('}');
}

#undef print_fmt
#undef print_str
#undef print_char

int main(int argc, char* argv[])
{
    typedef void (*gen_action_func_t)(
        const struct gen_t*, size_t, size_t, size_t);

    static const gen_action_func_t actions[] = {
        [options_gen_type_action]  = gen_type_gen,
        [options_gen_value_action] = gen_value_gen,
    };

    const struct options_t* opt = options(argc, argv);
    gen_action_func_t act = ARRAY_NULL_ELEM(
        actions, opt->action);
    struct gen_t gen;

    gen_init(&gen, opt);

    ASSERT(act != NULL);
    ASSERT(opt->nkey > 0);
    ASSERT(opt->depth > 0);
    act(&gen, opt->nkey, opt->depth, 0);
    fputc('\n', stdout);

    gen_done(&gen);

    return 0;
}


