// Copyright (C) 2016, 2017, 2018, 2019  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 <stdlib.h>
#include <string.h>
#include <dlfcn.h>

#include "lib/dyn-lib.h"
#include "lib/json-filter.h"

#include "json-filters.h"
#include "int-traits.h"
#include "common.h"

enum json_filter_error_type_t
{
    json_filter_error_none,
    json_filter_error_dyn_lib,
    json_filter_error_peer
};

struct json_filter_error_t
{
    enum json_filter_error_type_t   type;
    union {
        struct dyn_lib_error_info_t dyn_lib;
    };
    char                           *msg;
};

#define JSON_FILTER_ERROR(n, v, m, f)     \
    ({                                    \
        STATIC(json_filter_error_ ## n != \
               json_filter_error_none);   \
        filter->error.type =              \
               json_filter_error_ ## n;   \
        filter->error.n = (v);            \
        if (filter->error.msg != NULL)    \
            free(filter->error.msg);      \
        filter->error.msg = (m);          \
        filter->file.name = (f);          \
        false;                            \
    })

#define JSON_FILTER_PEER_ERROR(r)        \
    ({                                   \
        filter->error.type =             \
            json_filter_error_peer;      \
        if (filter->error.msg != NULL) { \
            free(filter->error.msg);     \
            filter->error.msg = NULL;    \
        }                                \
        filter->file.name = NULL;        \
        r;                               \
    })

enum json_filter_state_t
{
    json_filter_state_initial,
    json_filter_state_loaded,
    json_filter_state_created
};

struct json_filter_lib_t
{
    enum json_filter_state_t   state;
    struct json_filter_error_t error;
    struct json_file_info_t    file;
    struct dyn_lib_t           lib;
    struct json_filter_funcs_t funcs;
    void*                      ptr;
    bits_t                     is_exec: 1;
    bits_t                     verbose: 1;
};

#undef  CASE
#define CASE(n) DYN_LIB_ENTRY(json_filter_funcs_t, n)

static const struct dyn_lib_entry_t json_filter_entries[] = {
    CASE(get_version),
    CASE(create_filter),
    CASE(create_exec),
    CASE(destroy),
    CASE(get_is_error),
    CASE(get_error_pos),
    CASE(get_error_file),
    CASE(print_error_desc),
    CASE(exec),
};

static const struct dyn_lib_def_t json_filter_def = {
    .ver_major = JSON_FILTER_VERSION_MAJOR,
    .ver_minor = JSON_FILTER_VERSION_MINOR,
    .n_entries = ARRAY_SIZE(json_filter_entries),
    .entries = json_filter_entries,
    .prefix = "filter",
};

static bool json_filter_lib_init(
    struct json_filter_lib_t* filter,
    const struct options_filter_t* opts)
{
    struct dyn_lib_error_info_t e;
    char *m = NULL, *l;

    ASSERT(opts->argc > 0);
    l = opts->argv[0];

    if (!dyn_lib_init(
            &filter->lib, l, &json_filter_def,
            &filter->funcs, &e, &m))
        return JSON_FILTER_ERROR(
            dyn_lib, e, m, l);

    filter->state = json_filter_state_loaded;

    return true;
}

static void json_filter_lib_done(
    struct json_filter_lib_t* filter)
{
    if (filter->state >= json_filter_state_created)
        filter->funcs.destroy(filter->ptr);

    if (filter->state >= json_filter_state_loaded)
        dyn_lib_done(&filter->lib);
}

#define OPTS() \
    { .argc = opts->argc, .argv = opts->argv }

struct json_filter_lib_t* json_filter_lib_create(
    const struct options_filter_t* opts)
{
    const size_t n =
        sizeof(struct json_filter_lib_t);

    struct json_filter_options_t o = OPTS();
    struct json_filter_lib_t* filter;

    filter = malloc(n);
    VERIFY(filter != NULL);

    memset(filter, 0, n);
    filter->is_exec = true;

    if (!json_filter_lib_init(filter, opts))
        return filter;

    filter->ptr = filter->funcs.create_exec(&o);
    filter->state = json_filter_state_created;

    return filter;
}

static bool json_filter_lib_create_filter(
    struct json_filter_lib_t* filter,
    const struct options_filter_t* opts,
    struct json_handler_obj_t* obj)
{
    struct json_filter_options_t o = OPTS();
    const struct json_handler_t* h;

    filter->ptr = filter->funcs.create_filter(&o, obj, &h);
    filter->state = json_filter_state_created;

    if (filter->funcs.get_is_error(filter->ptr))
        return JSON_FILTER_PEER_ERROR(false);

    obj->ptr = filter->ptr;
    obj->handler = h;

    return true;
}

void json_filter_lib_destroy(
    struct json_filter_lib_t* filter)
{
    json_filter_lib_done(filter);
    free(filter->error.msg);
    free(filter);
}

void json_filter_lib_config_set_param(
    struct json_filter_lib_t* filter,
    enum json_filter_config_param_t param,
    bool val)
{
    switch (param) {
    case json_filter_verbose_error_config:
        filter->verbose = val;
        break;
    default:
        UNEXPECT_ERR("%d", param);
    }
}

bool json_filter_lib_exec(
    struct json_filter_lib_t* filter)
{
    ASSERT(filter->state >= json_filter_state_created);
    return filter->funcs.exec(filter->ptr);
}

bool json_filter_lib_get_is_error(
    struct json_filter_lib_t* filter)
{
    if (filter->error.type != json_filter_error_none)
        return true;
    ASSERT(filter->state >= json_filter_state_created);
    if (filter->funcs.get_is_error(filter->ptr))
        return JSON_FILTER_PEER_ERROR(true);
    return false;
}

struct json_error_pos_t
    json_filter_lib_get_error_pos(
        struct json_filter_lib_t* filter)
{
    return
          filter->error.type == json_filter_error_peer
        ? filter->funcs.get_error_pos(filter->ptr)
        : (struct json_error_pos_t) {0, 0};
}

#undef  CASE
#define CASE(n) case json_filter_error_ ## n

const struct json_file_info_t*
    json_filter_lib_get_error_file(
        struct json_filter_lib_t* filter)
{
    const struct json_file_info_t* f;

    switch (filter->error.type) {

    CASE(none):
        return NULL;
    CASE(dyn_lib):
        return &filter->file;
    CASE(peer):
        if ((f = filter->funcs.get_error_file(
                    filter->ptr)))
            return f;
        filter->file.name = filter->lib.lib_name;
        return &filter->file;

    default:
        UNEXPECT_VAR("%d", filter->error.type);
    }
}

#undef  CASE
#define CASE(n) case dyn_lib_error_ ## n

static void json_filter_lib_print_dyn_lib_error_desc(
    const struct dyn_lib_error_info_t* error,
    FILE* file)
{
    switch (error->type) {

    CASE(invalid_lib_name):
        fputs("invalid library name", file);
        break;
    CASE(load_lib_failed):
        fputs("failed loading library", file);
        break;
    CASE(symbol_not_found): {
        const struct dyn_lib_entry_t* e =
            ARRAY_NULL_ELEM_REF(
                json_filter_entries,
                error->sym);

        ASSERT(e != NULL);
        fprintf(file,
            "'filter_%s' symbol not found",
            e->name);
        break;
    }
    CASE(wrong_lib_version):
        // stev: not using 'error->ver' since
        // a more detailed error message is
        // produced by the calling function
        // 'json_filter_lib_print_error_desc'
        // through 'filter->error.msg'
        fputs("wrong library version", file);
        break;

    default:
        UNEXPECT_VAR("%d", error->type);
    }
}

#undef  CASE
#define CASE(n) case json_filter_error_ ## n

void json_filter_lib_print_error_desc(
    struct json_filter_lib_t* filter, FILE* file)
{
    const struct json_filter_error_t* e =
        &filter->error;

    if (filter->is_exec)
        fputs("filter library: ", file);

    switch (e->type) {

    CASE(none):
        fputs("no error", file);
        break;
    CASE(dyn_lib):
        json_filter_lib_print_dyn_lib_error_desc(
            &e->dyn_lib, file);
        break;
    CASE(peer):
        filter->funcs.print_error_desc(filter->ptr, file);
        break;

    default:
        UNEXPECT_VAR("%d", e->type);
    }

    if (filter->verbose && e->msg != NULL)
        fprintf(file, ": %s", e->msg);
}

struct json_filter_chain_t
{
    struct json_filter_lib_t
                      *err_filter;
    size_t             n_libs;
    struct json_filter_lib_t
                       libs[0];
};

#define JSON_FILTER_CHAIN_ERROR_(p, r) \
    ({                                 \
        chain->err_filter = p;         \
        r;                             \
    })
#define JSON_FILTER_CHAIN_ERROR(p) \
    JSON_FILTER_CHAIN_ERROR_(p, false)

static bool json_filter_chain_init_libs(
    struct json_filter_chain_t* chain,
    const struct options_filter_t* optv,
    struct json_handler_obj_t* obj)
{
    struct json_filter_lib_t *p, *e;

    for (p = chain->libs,
         e = p + chain->n_libs;
         p < e;
         p ++) {
        if (!json_filter_lib_init(p, optv ++))
            return JSON_FILTER_CHAIN_ERROR(p);
    }

    while (p > chain->libs) {
        if (!json_filter_lib_create_filter(
                -- p, -- optv, obj))
            return JSON_FILTER_CHAIN_ERROR(p);
    }

    return true;
}

static void json_filter_chain_done_libs(
    struct json_filter_chain_t* chain)
{
    struct json_filter_lib_t *p, *e;

    for (p = chain->libs,
         e = p + chain->n_libs;
         p < e;
         p ++)
        json_filter_lib_done(p);
}

struct json_filter_chain_t* json_filter_chain_create(
    const struct options_filter_t* optv, size_t n_optv,
    struct json_handler_obj_t* obj)
{
    const size_t m =
        sizeof(struct json_filter_chain_t);
    const size_t n =
        sizeof(struct json_filter_lib_t);

    struct json_filter_chain_t* chain;
    size_t s = n;

    ASSERT(n_optv > 0);
    ASSERT(optv != NULL);
    ASSERT(obj != NULL);

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

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

    memset(chain, 0, s);
    chain->n_libs = n_optv;

    json_filter_chain_init_libs(
        chain, optv, obj);

    return chain;
}

void json_filter_chain_destroy(
    struct json_filter_chain_t* chain)
{
    json_filter_chain_done_libs(chain);
    free(chain);
}

void json_filter_chain_config_set_param(
    struct json_filter_chain_t* chain,
    enum json_filter_config_param_t param,
    bool val)
{
    struct json_filter_lib_t *p, *e;

    for (p = chain->libs,
         e = p + chain->n_libs;
         p < e;
         p ++) {
        json_filter_lib_config_set_param(
            p, param, val);
    }
}

#undef  JSON_FILTER_CHAIN_ERROR
#define JSON_FILTER_CHAIN_ERROR(p) \
    JSON_FILTER_CHAIN_ERROR_(p, true)

bool json_filter_chain_get_is_error(
    struct json_filter_chain_t* chain)
{
    struct json_filter_lib_t *p, *e;

    if (chain->err_filter != NULL)
        return true;

    for (p = chain->libs,
         e = p + chain->n_libs;
         p < e;
         p ++) {
        if (json_filter_lib_get_is_error(p))
            return JSON_FILTER_CHAIN_ERROR(p);
    }

    return false;
}

struct json_error_pos_t
    json_filter_chain_get_error_pos(
        struct json_filter_chain_t* chain)
{
    return chain->err_filter != NULL
        ? json_filter_lib_get_error_pos(
                chain->err_filter)
        : (struct json_error_pos_t) {0, 0};
}

const struct json_file_info_t*
    json_filter_chain_get_error_file(
        struct json_filter_chain_t* chain)
{
    return chain->err_filter != NULL
        ? json_filter_lib_get_error_file(
                chain->err_filter)
        : NULL;
}

void json_filter_chain_print_error_desc(
    struct json_filter_chain_t* chain,
    FILE* file)
{
    size_t k;

    if (chain->n_libs < 2 ||
        chain->err_filter == NULL)
        fputs("filter library: ", file);
    else {
        k = PTR_DIFF(chain->err_filter, chain->libs);
        fprintf(file, "filter library #%zu: ", k + 1);
    }

    if (chain->err_filter == NULL)
        fputs("no error", file);
    else
        json_filter_lib_print_error_desc(
            chain->err_filter, file);
}


