/*
    Authors:
        Pavel Březina <pbrezina@redhat.com>

    Copyright (C) 2016 Red Hat

    This program 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.

    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef _DP_PRIVATE_H_
#define _DP_PRIVATE_H_

#include <tevent.h>
#include <dhash.h>
#include "sbus/sssd_dbus.h"
#include "providers/data_provider/dp.h"
#include "util/util.h"

#define DP_REQ_DEBUG(level, name, fmt, ...) \
    DEBUG(level, "DP Request [%s]: " fmt "\n", (name ?: "Unknown"), ##__VA_ARGS__)

enum dp_clients {
    DPC_NSS,
    DPC_PAM,
    DPC_IFP,
    DPC_PAC,
    DPC_SUDO,
    DPC_HOST,
    DPC_AUTOFS,

    DP_CLIENT_SENTINEL
};

struct dp_req;
struct dp_client;

struct dp_module {
    bool initialized;
    const char *name;
    void *module_data;
    void *libhandle;
};

struct dp_target {
    const char *name;
    const char *module_name;
    bool explicitly_configured;

    bool initialized;
    enum dp_targets target;
    struct dp_module *module;
    struct dp_method *methods;
};

struct dp_method {
    dp_req_send_fn send_fn;
    dp_req_recv_fn recv_fn;
    void *method_data;
    const char *method_dtype;
    const char *request_dtype;
    const char *output_dtype;
    uint32_t output_size;
};

struct data_provider {
    uid_t uid;
    gid_t gid;
    struct be_ctx *be_ctx;
    struct tevent_context *ev;
    struct sbus_connection *srv_conn;
    struct dp_client *clients[DP_CLIENT_SENTINEL];
    bool terminating;

    struct {
        /* Numeric identificator that will be assigned to next request. */
        uint32_t index;

        /* List of all ongoing requests. */
        uint32_t num_active;
        struct dp_req *active;

        /* Table containing list of sbus_requests where DP sends reply when
         * a request is finished. Value of this table is pair
         * <tevent_req, list of sbus_request>
         */
        hash_table_t *reply_table;
    } requests;

    struct dp_module **modules;
    struct dp_target **targets;
};

errno_t dp_find_method(struct data_provider *provider,
                       enum dp_targets target,
                       enum dp_methods method,
                       struct dp_method **_execute);

struct dp_module *dp_load_module(TALLOC_CTX *mem_ctx,
                                 struct be_ctx *be_ctx,
                                 struct data_provider *provider,
                                 struct dp_module **modules,
                                 const char *name);

errno_t dp_init_modules(TALLOC_CTX *mem_ctx, struct dp_module ***_modules);

const char *dp_target_to_string(enum dp_targets target);

bool dp_target_initialized(struct dp_target **targets, enum dp_targets type);

errno_t dp_init_targets(TALLOC_CTX *mem_ctx,
                        struct be_ctx *be_ctx,
                        struct data_provider *provider,
                        struct dp_module **modules);

/* Reply callbacks. */

typedef void (*dp_req_post_fn)(const char *req_name,
                               struct data_provider *provider,
                               void *post_data,
                               void *reply_data);

typedef void (*dp_req_reply_fn)(const char *req_name,
                                struct sbus_request *sbus_req,
                                void *data);

void dp_req_reply_default(const char *req_name,
                          struct sbus_request *sbus_req,
                          void **data);

/* Data provider request table. */

struct dp_sbus_req_item;

struct dp_table_value {
    hash_table_t *table;
    const char *key;

    struct tevent_req *req;
    struct dp_sbus_req_item *list;
};

struct dp_sbus_req_item {
    struct dp_table_value *parent;
    struct sbus_request *sbus_req;

    struct dp_sbus_req_item *prev;
    struct dp_sbus_req_item *next;
};

char *dp_req_table_key(TALLOC_CTX *mem_ctx,
                       enum dp_targets target,
                       enum dp_methods method,
                       uint32_t dp_flags,
                       const char *custom_part);

errno_t dp_req_table_init(TALLOC_CTX *mem_ctx, hash_table_t **_table);

struct dp_table_value *dp_req_table_lookup(hash_table_t *table,
                                           const char *key);

errno_t dp_req_table_add(hash_table_t *table,
                         const char *key,
                         struct tevent_req *req,
                         struct sbus_request *sbus_req);

void dp_req_table_del(hash_table_t *table,
                      const char *key);

void dp_req_table_del_and_free(hash_table_t *table,
                               const char *key);

bool dp_req_table_has_key(hash_table_t *table,
                          const char *key);

/* Data provider request. */

void dp_terminate_active_requests(struct data_provider *provider);

void dp_req_reply_error(struct sbus_request *sbus_req,
                        const char *req_name,
                        errno_t ret);

void _dp_req_with_reply(struct dp_client *dp_cli,
                        const char *domain,
                        const char *request_name,
                        const char *custom_key,
                        struct sbus_request *sbus_req,
                        enum dp_targets target,
                        enum dp_methods method,
                        uint32_t dp_flags,
                        void *request_data,
                        dp_req_post_fn postprocess_fn,
                        void *postprocess_data,
                        dp_req_reply_fn reply_fn,
                        const char *output_dtype);

/**
 * If @domain is NULL, be_ctx->domain is used.
 * If req_key is NULL, address of sbus_req is used.
 *
 * If @pp_fn (post process function) is set it is call on a successful
 * DP request before reply is sent.
 */
#define dp_req_with_reply_pp(dp_cli, domain, req_name, req_key, sbus_req,     \
                             target, method, dp_flags, req_data, pp_fn,       \
                             pp_data, pp_dtype, reply_fn, output_dtype)       \
    do {                                                                      \
        /* Check postprocess function parameter types. */                     \
        void (*__pp_fn)(const char *, struct data_provider *,                 \
            pp_dtype *, output_dtype *) = (pp_fn);                            \
        pp_dtype *__pp_data = (pp_data);                                      \
                                                                              \
        /* Check reply function parameter types. */                           \
        void (*__reply_fn)(const char *, struct sbus_request *,               \
            output_dtype *) = (reply_fn);                                     \
                                                                              \
        _dp_req_with_reply(dp_cli, domain, req_name, req_key, sbus_req,       \
                           target, method, dp_flags, req_data,                \
                           (dp_req_post_fn)__pp_fn, __pp_data,                \
                           (dp_req_reply_fn)__reply_fn, #output_dtype);       \
    } while(0)

#define dp_req_with_reply(dp_cli, domain, req_name, req_key, sbus_req, target,\
                          method, dp_flags, req_data, reply_fn,               \
                          output_dtype)                                       \
    dp_req_with_reply_pp(dp_cli, domain, req_name, req_key, sbus_req, target, \
                         method, dp_flags, req_data, NULL, NULL, void,        \
                         reply_fn, output_dtype)

/* Client shared functions. */

errno_t dp_client_init(struct sbus_connection *conn, void *data);
struct data_provider *dp_client_provider(struct dp_client *dp_cli);
struct be_ctx *dp_client_be(struct dp_client *dp_cli);
struct sbus_connection *dp_client_conn(struct dp_client *dp_cli);

#endif /* _DP_PRIVATE_H_ */
