/*
    ELAPI

    Implementation of the ELAPI logging interface.

    Copyright (C) Dmitri Pal <dpal@redhat.com> 2009

    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/>.
*/

#define _GNU_SOURCE
#include <errno.h>      /* for errors */
#include <stdio.h>      /* for printf() - temporarily */
#include <stdlib.h>     /* for malloc() */

#include "elapi_priv.h"
#include "elapi_event.h"
#include "elapi_sink.h"
#include "trace.h"
#include "config.h"
#include "ini_config.h"

#include "collection_tools.h" /*temporarily */

/* Buffer size for time string */
#define MAX_TIMESTR         200

/* I was told during review that I have to hard code the name.
 * So it is hardcoded now.
 */
#define ELAPI_DEFAULT_ERROR_FILE "elapiconf.err"

/* Handler for logging through the targets */
int elapi_tgt_cb(const char *target,
                 int target_len,
                 int type,
                 void *data,
                 int length,
                 void *passed_data,
                 int *stop)
{
    int error = EOK;
    struct elapi_tgt_data *target_data;
    struct elapi_tgt_ctx *context;

    TRACE_FLOW_STRING("elapi_tgt_cb", "Entry.");

    /* Skip header */
    if (type == COL_TYPE_COLLECTION) {
        TRACE_FLOW_STRING("elapi_tgt_cb - skip header", "Exit.");
        return EOK;
    }

    target_data = (struct elapi_tgt_data *)(passed_data);
    context = *((struct elapi_tgt_ctx **)(data));

    /* Check if we need to log this event into this target */
    TRACE_INFO_NUMBER("EVENT IS LOGGED INTO:", target_data->target_mask);
    TRACE_INFO_NUMBER("TARGET VALUE IS:", context->target_value);

    if ((target_data->target_mask & context->target_value) == 0) {
        TRACE_INFO_STRING("Current event will NOT be logged into the target:", target);
        return EOK;
    }

    TRACE_INFO_STRING("Current event will be logged into the target:", target);

    /* Log event */
    error = elapi_tgt_submit(target_data->handle, context, target_data->event);
    if (error) {
        TRACE_ERROR_NUMBER("Failed to submit event to target", error);
        return error;
    }

    TRACE_FLOW_STRING("elapi_tgt_cb", "Exit.");
    return EOK;
}

/* Internal target cleanup function */
int elapi_tgt_free_cb(const char *target,
                      int target_len,
                      int type,
                      void *data,
                      int length,
                      void *passed_data,
                      int *stop)
{
    TRACE_FLOW_STRING("elapi_tgt_free_cb", "Entry.");

    /* Skip header */
    if (type == COL_TYPE_COLLECTION) {
        TRACE_FLOW_STRING("elapi_tgt_free_cb - skip header", "Exit.");
        return EOK;
    }

    elapi_tgt_destroy(*((struct elapi_tgt_ctx **)(data)));

    TRACE_FLOW_STRING("elapi_tgt_free_cb", "Exit.");
    return EOK;
}

/* Function to add a sink to the collection */
/* This function belongs to this module.
 * It adds sink into the collection
 * of sinks inside dispatcher and puts
 * reference into the target's reference list.
 */
/* FIXME - other arguments might be added later */
int elapi_sink_add(struct collection_item **sink_ref,
                   const char *sink,
                   struct elapi_dispatcher *handle)
{
    int error = EOK;
    struct elapi_sink_ctx *sink_context = NULL;

    TRACE_FLOW_STRING("elapi_sink_add", "Entry");

    TRACE_INFO_STRING("Evaluating sink:", sink);
    TRACE_INFO_NUMBER("Sink reference before call:", *sink_ref);

    /* Get the sink from the list */
    error = col_get_item(handle->sink_list,
                         sink,
                         COL_TYPE_ANY,
                         COL_TRAVERSE_DEFAULT,
                         sink_ref);

    TRACE_INFO_NUMBER("Sink evaluation returned", error);
    TRACE_INFO_NUMBER("Sink reference after call:", *sink_ref);

    if (error) {
        TRACE_ERROR_NUMBER("Search returned error", error);
        return error;
    }

    if (!(*sink_ref)) {
        TRACE_FLOW_STRING("No such sink yet, adding new sink:", sink);

        /* Create a sink object */
        error = elapi_sink_create(&sink_context, sink, handle->ini_config, handle->appname);
        if (error != 0) {
            TRACE_ERROR_NUMBER("Failed to add sink data as property", error);
            /* If create failed there is nothing to destroy */
            return error;
        }

        /* If there was an internal error but sink is optional
         * no error is returned but context is NULL.
         * We need to check for this situation.
         */
        if (sink_context) {
            TRACE_FLOW_STRING("Loaded sink:", sink);
            /* We got a valid sink so add it to the collection */
            error = col_add_binary_property_with_ref(handle->sink_list,
                                                     NULL,
                                                     sink,
                                                     (void *)(&sink_context),
                                                     sizeof(struct elapi_sink_ctx *),
                                                     sink_ref);
            if (error != 0) {
                TRACE_ERROR_NUMBER("Failed to add sink data as property", error);
                elapi_sink_destroy(sink_context);
                return error;
            }
        }
        else {
            *sink_ref = NULL;
            TRACE_FLOW_STRING("Setting sink reference to NULL", "");
        }
    }

    TRACE_FLOW_NUMBER("elapi_sink_add returning", error);
    return error;
}

/* Destroy target object */
void elapi_tgt_destroy(struct elapi_tgt_ctx *context)
{
    TRACE_FLOW_STRING("elapi_tgt_destroy", "Entry.");

    TRACE_INFO_NUMBER("Target address in cleanup:", context);

    if (context) {
        TRACE_INFO_STRING("Deleting the list of references to sinks", "");
        col_destroy_collection(context->sink_ref_list);
        /* FIXME - add other cleanup for other things that will be a part
         * of the target context.
         */
        free(context);
    }

    TRACE_FLOW_STRING("elapi_tgt_destroy", "Exit.");

}

/* Allocate target context and load sinks to it */
int elapi_tgt_create(struct elapi_tgt_ctx **context,
                     const char *target,
                     struct elapi_dispatcher *handle)
{
    int error = EOK;
    struct collection_item *sink_cfg_item = NULL;
    struct collection_item *value_cfg_item = NULL;
    struct elapi_tgt_ctx *target_context;
    char **sinks;
    char **current_sink;
    struct collection_item *sink_ref;
    unsigned count;

    TRACE_FLOW_STRING("elapi_tgt_create", "Entry.");

    /* Get list of sinks for this target from config */
    error = get_config_item(target,
                            ELAPI_SINKS,
                            handle->ini_config,
                            &sink_cfg_item);
    if (error) {
        TRACE_ERROR_NUMBER("Attempt to read configuration returned error", error);
        return error;
    }

    /* Do we have sinks? */
    if (sink_cfg_item == NULL) {
        /* There is no list of targets this is bad configuration - return error */
        TRACE_ERROR_STRING("Required key is missing in the configuration.", "Fatal Error!");
        return ENOENT;
    }

    /* Allocate context */
    target_context = (struct elapi_tgt_ctx *)calloc(1, sizeof(struct elapi_tgt_ctx));
    if (target_context == NULL) {
        TRACE_ERROR_NUMBER("Memory allocation failed. Error", ENOMEM);
        return ENOMEM;
    }

    /* Assign target's value */
    error = get_config_item(target,
                            ELAPI_TARGET_VALUE,
                            handle->ini_config,
                            &value_cfg_item);
    if (error) {
        TRACE_ERROR_NUMBER("Attempt to read configuration returned error", error);
        elapi_tgt_destroy(target_context);
        return error;
    }

    /* Do we have value? */
    if (value_cfg_item == NULL) {
        TRACE_INFO_STRING("Value for target is not defined.", "Assume ANY.");
        target_context->target_value = ELAPI_TARGET_ALL;
    }
    else {
        target_context->target_value = (uint32_t)get_unsigned_config_value(value_cfg_item,
                                                                           1,
                                                                           ELAPI_TARGET_ALL,
                                                                           &error);
        /* NOTE: I will check and fail here on error rather than do a best effort
         * for now. We can switch to less rigorous checking when the INI
         * validation library/utility becomes available.
         */
        if (error) {
            TRACE_ERROR_NUMBER("Failed to convert value form INI file", error);
            elapi_tgt_destroy(target_context);
            return error;
        }
    }

    TRACE_INFO_NUMBER("Value for target is:", target_context->target_value);


    /* Allocate collection to store sink references for this target */
    error = col_create_collection(&(target_context->sink_ref_list),
                                  ELAPI_SINK_REFS,
                                  COL_CLASS_ELAPI_SINK_REF);
    if (error != 0) {
        TRACE_ERROR_NUMBER("Failed to create sink collection. Error", error);
        elapi_tgt_destroy(target_context);
        return error;
    }

    /* Get list of sinks from config. Make sure we free it later */
    sinks = get_string_config_array(sink_cfg_item, NULL, NULL, NULL);

    /* For each sink in the list create sink context object and load sink */
    current_sink = sinks;
    while (*current_sink != NULL) {

        TRACE_INFO_STRING("Current sink", *current_sink);

        /* Load sink if it is not loaded yet */
        sink_ref = NULL;
        error = elapi_sink_add(&sink_ref,
                               *current_sink,
                               handle);
        if (error) {
            /* NOTE - we might decide to lax some of the checks
             * like this later and be satisfied with at least one
             * sink in the list. Subject for discussion...
             */
            TRACE_ERROR_NUMBER("Failed to add sink", error);
            elapi_tgt_destroy(target_context);
            free_string_config_array(sinks);
            return error;
        }

        /* It might be that is was an error wit the optional sink so
         * we need to check if the reference is not NULL;
         */
        if (sink_ref) {
            /* Add reference to it into the target object */
            error = col_add_binary_property(target_context->sink_ref_list, NULL,
                                            *current_sink, (void *)(&sink_ref),
                                            sizeof(struct collection_item *));
            if (error != 0) {
                TRACE_ERROR_NUMBER("Failed to add sink reference", error);
                elapi_tgt_destroy(target_context);
                free_string_config_array(sinks);
                return error;
            }
        }
        else {
            TRACE_INFO_STRING("Sink reference is NULL.", "Skipping the sink");
        }
        current_sink++;
    }

    free_string_config_array(sinks);

    /* Get count of the references in the list */
    error = col_get_collection_count(target_context->sink_ref_list, &count);
    if (error) {
        TRACE_ERROR_NUMBER("Failed to get count", error);
        elapi_tgt_destroy(target_context);
        return error;
    }

    /* Check count */
    if (count <= 1) {
        /* Nothing but header? - Bad! */
        TRACE_ERROR_NUMBER("No sinks loaded for target!", "This is a fatal error!");
        elapi_tgt_destroy(target_context);
        return ENOENT;
    }

    *context = target_context;

    TRACE_FLOW_STRING("elapi_tgt_create", "Exit.");
    return EOK;
}


/* Function to create a list of targets */
int elapi_tgt_mklist(struct elapi_dispatcher *handle)
{
    int error = EOK;
    char **current_target;
    struct elapi_tgt_ctx *context;


    TRACE_FLOW_STRING("elapi_tgt_mklist", "Entry");

    /* Allocate collection to store target */
    error = col_create_collection(&(handle->target_list),
                                  ELAPI_TARGETS,
                                  COL_CLASS_ELAPI_TARGET);
    if (error != 0) {
        TRACE_ERROR_NUMBER("Failed to create target collection. Error", error);
        /* No cleanup here.
         * The calling function will call a cleanup
         * of the dispatcher as a whole.*/
        return error;
    }

    /* Allocate collection to store sinks */
    error = col_create_collection(&(handle->sink_list),
                                  ELAPI_SINKS,
                                  COL_CLASS_ELAPI_SINK);
    if (error != 0) {
        TRACE_ERROR_NUMBER("Failed to create sink collection. Error", error);
        /* No cleanup here.
         * The calling function will call a cleanup
         * of the dispatcher as a whole.*/
        return error;
    }

    current_target = handle->targets;
    handle->target_counter = 0;

    /* Add targets as properties to the target collection */
    while (*current_target != NULL) {

        TRACE_INFO_STRING("Current target", *current_target);

        /* Allocate target context and load sinks to it */
        context = NULL;
        error = elapi_tgt_create(&context,
                                 *current_target,
                                 handle);
        if (error) {
            TRACE_ERROR_NUMBER("Failed to create target", error);
            return error;
        }

        TRACE_INFO_NUMBER("Target address:", context);

        /* Add created target to the list of targets */
        error = col_add_binary_property(handle->target_list, NULL,
                                        *current_target, (void *)(&context),
                                        sizeof(struct elapi_tgt_ctx *));
        if (error != 0) {
            TRACE_ERROR_NUMBER("Failed to add sink data as property", error);
            /* Need to clean allocated context here if we failed to add it */
            elapi_tgt_destroy(context);
            return error;
        }

        handle->target_counter++;
        current_target++;
    }

    /* Check if we have any targets available */
    if (handle->target_counter == 0) {
        TRACE_ERROR_STRING("No targets", "");
        return ENOENT;
    }

    TRACE_FLOW_STRING("elapi_tgt_mklist", "Returning success");
    return EOK;
}

/* Submit event into the target */
/* FIXME: do we need the whole dispatcher here?
 * probably not.
 * Need to sort out what parts of it we actually
 * need and pass them explicitely.
 * The point is that the target should not
 * know or care about the dispatcher internals
 * passing it here is a violation of the
 * several desing patterns so it should be
 * eventually fixed.
 */
int elapi_tgt_submit(struct elapi_dispatcher *handle,
                     struct elapi_tgt_ctx *context,
                     struct collection_item *event)
{
    int error = EOK;
    struct collection_iterator *iterator;
    struct collection_item *sink_item;
    struct elapi_sink_ctx *ctx;

    TRACE_FLOW_STRING("elapi_tgt_submit", "Entry");

    /* FIXME: General logic of the function
     * should be the following:
     * Get the list of the sinks
     * For each sink
     *    Get its status
     *    Check if the sink is active
     *    If it is active log into it
     *        In error fail over to the next one
     *        else done
     *    else (not active) is it revivable?
     *        If so is it time to revive?
     *            If so mark as active and log into it
     *                If error fail over
     *                else done
     *            else fail over
     *        else fail over
     *    else fail over
     * End for each sink
     *
     * This logic will be implemented
     * in the later patches
     * for now we will try
     * all the sinks without checking status.
     */

    error = col_bind_iterator(&iterator, context->sink_ref_list,
                              COL_TRAVERSE_DEFAULT);
    if (error) {
        TRACE_ERROR_NUMBER("Failed to bind iterator.", error);
        return error;
    }

    while(1) {
        /* Loop through the sink references */
        error = col_iterate_collection(iterator, &sink_item);
        if (error) {
            TRACE_ERROR_NUMBER("Error iterating event:", error);
            col_unbind_iterator(iterator);
            return error;
        }

        /* Are we done ? */
        if (sink_item == NULL) break;

        /* Skip headers */
        if (col_get_item_type(sink_item) == COL_TYPE_COLLECTION) continue;


        /* Dereference the sink item to get context */
        sink_item = *((struct collection_item **)(col_get_item_data(sink_item)));
        ctx = *((struct elapi_sink_ctx **)(col_get_item_data(sink_item)));

        /* FIXME: Check the sink status */

        /* FIXME other parameters might be required... */
        error = elapi_sink_submit(ctx, event);
        if (error) {
            TRACE_ERROR_NUMBER("Error submitting event:", error);
            col_unbind_iterator(iterator);
            return error;
        }

    }

    col_unbind_iterator(iterator);

    TRACE_FLOW_STRING("elapi_tgt_submit", "Exit");
    return EOK;

}


/* If we failed to read configuration record this in the local file */
void elapi_dump_ini_err(struct collection_item *error_list)
{
    FILE *efile;
    char timestr[MAX_TIMESTR];
    time_t time_in_sec;
    struct tm *time_as_struct;
    struct tm time_data;

    TRACE_FLOW_STRING("elapi_dump_ini_err", "Entry point");

    efile = fopen(ELAPI_DEFAULT_ERROR_FILE, "a");
    if (efile == NULL) {
        TRACE_ERROR_STRING("No output available.", "Returning.");
        return;
    }

    time_in_sec = time(NULL);
    time_as_struct = localtime_r(&time_in_sec, &time_data);

    fprintf(efile, "\n\n%*s\n\n", 80, "=");

    if ((time_as_struct != NULL) &&
        (strftime(timestr, sizeof(timestr), E_TIMESTAMP_FORMAT, time_as_struct) == 0)) {
        fprintf(efile, "%s\n", timestr);
    }
    else {
        TRACE_FLOW_STRING("elapi_internal_dump_errors_to_file", "Was not able to process time.");
    }

    fprintf(efile, "\n");
    print_file_parsing_errors(efile, error_list);

    fclose(efile);
    TRACE_FLOW_STRING("elapi_dump_ini_err", "Exit");
}

/****************************************************************************/
/* Functions below are added for debugging purposes                         */
/****************************************************************************/
#ifdef ELAPI_VERBOSE

void elapi_print_sink_ctx(struct elapi_sink_ctx *sink_context)
{
    /* This will not print well on 64 bit but it is just debugging
     * so it is OK to have it.
     */
    printf("Printing sink context using address %p\n", sink_context);

    printf("Mode: %s\n", sink_context->async_mode ? "true" : "false");
    if (sink_context->in_queue) col_print_collection(sink_context->in_queue);
    else printf("Queue is not initialized.\n");

    if (sink_context->pending) col_print_collection(sink_context->pending);
    else printf("Pending list is not initialized.\n");

    if (sink_context->sink_cfg.provider) printf("Provider: %s\n",
                                                sink_context->sink_cfg.provider);
    else printf("Provider is not defined.\n");

    printf("Is provider required? %s\n", ((sink_context->sink_cfg.required > 0) ? "Yes" : "No"));
    printf("On error: %s\n", ((sink_context->sink_cfg.onerror == 0) ? "retry" : "fail"));
    printf("Timout: %d\n", sink_context->sink_cfg.timeout);
    printf("Sync configuration: %s\n", sink_context->sink_cfg.synch ? "true" : "false");

    if (sink_context->sink_cfg.priv_ctx) printf("Private context allocated.\n");
    else printf("Private context is NULL.\n");

    if (sink_context->sink_cfg.libhandle) printf("Lib handle is allocated.\n");
    else printf("Lib handle is NULL.\n");

    if (sink_context->sink_cfg.ability) printf("Capability function is present\n");
    else printf("NO capability function.\n");

    if (sink_context->sink_cfg.cpb_cb.init_cb)  printf("Init callback is OK.\n");
    else printf("Init callback is missing.\n");

    if (sink_context->sink_cfg.cpb_cb.submit_cb)  printf("Submit callback is OK.\n");
    else printf("Submit callback is missing.\n");

    if (sink_context->sink_cfg.cpb_cb.close_cb)  printf("Close callback is OK.\n");
    else printf("Close callback is missing.\n");


}

/* Handler for printing target internals */
static int elapi_sink_ref_dbg_cb(const char *sink,
                                 int sink_len,
                                 int type,
                                 void *data,
                                 int length,
                                 void *passed_data,
                                 int *stop)
{
    struct collection_item *sink_item;
    struct elapi_sink_ctx *sink_context;

    /* Skip header */
    if (type == COL_TYPE_COLLECTION) {
        return EOK;
    }

    sink_item = *((struct collection_item **)(data));

    printf("\nReferenced sink name is: %s\n", col_get_item_property(sink_item, NULL));

    sink_context = *((struct elapi_sink_ctx **)(col_get_item_data(sink_item)));

    elapi_print_sink_ctx(sink_context);


    return EOK;
}

/* Handler for printing sink internals */
static int elapi_sink_dbg_cb(const char *sink,
                             int sink_len,
                             int type,
                             void *data,
                             int length,
                             void *passed_data,
                             int *stop)
{
    struct elapi_sink_ctx *sink_context;

    /* Skip header */
    if (type == COL_TYPE_COLLECTION) {
        return EOK;
    }

    sink_context = *((struct elapi_sink_ctx **)(data));

    printf("\nSink name is: %s\n", sink);

    elapi_print_sink_ctx(sink_context);

    return EOK;
}

/* Handler for printing target internals */
static int elapi_tgt_dbg_cb(const char *target,
                            int target_len,
                            int type,
                            void *data,
                            int length,
                            void *passed_data,
                            int *stop)
{
    struct elapi_tgt_ctx *context;

    /* Skip header */
    if (type == COL_TYPE_COLLECTION) {
        return EOK;
    }

    context = *((struct elapi_tgt_ctx **)(data));

    printf("\nTarget value for target \"%s\" is %d\n", target, context->target_value);
    printf("\nReferenced sinks:\n\n");

    (void)col_traverse_collection(context->sink_ref_list,
                                  COL_TRAVERSE_ONELEVEL,
                                  elapi_sink_ref_dbg_cb,
                                  NULL);

    return EOK;
}



/* Internal function to print dispatcher internals - useful for testing */
void elapi_print_dispatcher(struct elapi_dispatcher *handle)
{
    char **current_target;

    printf("\nPRINTING DISPATCHER INTERNALS\n\n");

    printf("Application name: %s\n", handle->appname != NULL ? handle->appname : "(null)");
    printf("List of target names:\n");

    current_target = handle->targets;
    while (*current_target != NULL) {
        printf("   %s\n",*current_target);
        current_target++;
    }

    printf("Target counter: %d\n", handle->target_counter);
    printf("\n\nTarget collection:\n\n");
    if (handle->target_list) col_debug_collection(handle->target_list, COL_TRAVERSE_DEFAULT);


    printf("\n\nSink collection:\n\n");
    if (handle->sink_list) col_debug_collection(handle->sink_list, COL_TRAVERSE_DEFAULT);
    printf("\n\nConfig collection:\n\n");
    if (handle->ini_config) col_debug_collection(handle->ini_config, COL_TRAVERSE_DEFAULT);
    printf("\nDefault template:\n\n");
    if (handle->default_tpl) col_debug_collection(handle->default_tpl, COL_TRAVERSE_DEFAULT);

    printf("\n\nDeep target inspection:\n\n");
    if (handle->target_list) {
        (void)col_traverse_collection(handle->target_list,
                                      COL_TRAVERSE_ONELEVEL,
                                      elapi_tgt_dbg_cb,
                                      NULL);
    }
    printf("\n\nDeep sink inspection:\n\n");
    if (handle->sink_list) {
        (void)col_traverse_collection(handle->sink_list,
                                      COL_TRAVERSE_ONELEVEL,
                                      elapi_sink_dbg_cb,
                                      NULL);
    }
    /* FIXME: Async data... */

    printf("DISPATCHER END\n\n");
    fflush(stdout);
}

#endif