/*
    ELAPI

    Module contains functions to resolve the event.

    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 <string.h> /* for strcmp() */

#include "elapi_priv.h"
#include "elapi_event.h"
#include "elapi_basic.h"
#include "trace.h"
#include "config.h"

/*****************************************/
/* Individual callbacks are defined here */
/*****************************************/
/* Timestamp resolution callback */
static int elapi_timestamp_cb(struct elapi_resolve_data *resolver,
                              struct collection_item *item,
                              int *skip)
{
    int error = EOK;
    char timestamp[TIME_ARRAY_SIZE + 1];
    int length;

    TRACE_FLOW_STRING("elapi_timestamp_cb", "Entry");

    /* Construct the time stamp */
    length = strftime(timestamp,
                        TIME_ARRAY_SIZE,
                        (const char *)(col_get_item_data(item)),
                        &(resolver->local_time));

    /* Update the time stamp item */
    error = col_modify_str_item(item,
                                NULL,
                                timestamp,
                                length + 1);

    TRACE_FLOW_NUMBER("elapi_timestamp_cb. Exit. Returning", error);
    return error;
}

/* UTC time resolution callback */
static int elapi_utctime_cb(struct elapi_resolve_data *resolver,
                            struct collection_item *item,
                            int *skip)
{
    int error = EOK;

    TRACE_FLOW_STRING("elapi_utctime_cb", "Entry");

    /* Update the UTC item */
    error = col_modify_int_item(item,
                                NULL,
                                (int)(resolver->tm));

    TRACE_FLOW_NUMBER("elapi_utctime_cb. Exit. Returning", error);
    return error;
}

/* Offset resolution callback */
static int elapi_offset_cb(struct elapi_resolve_data *resolver,
                           struct collection_item *item,
                           int *skip)
{
    int error = EOK;

    TRACE_FLOW_STRING("elapi_offset_cb", "Entry");

    /* Update the offset item */
    error = col_modify_int_item(item,
                                NULL,
                                (int)(resolver->offset));

    TRACE_FLOW_NUMBER("elapi_offset_cb. Exit. Returning", error);
    return error;
}

/* Message resolution callback */
static int elapi_message_cb(struct elapi_resolve_data *resolver,
                            struct collection_item *item,
                            int *skip)
{

    TRACE_FLOW_STRING("elapi_message_cb", "Entry");

    /* Save pointer to message item */
    resolver->message = item;

    TRACE_FLOW_NUMBER("elapi_message_cb.", "Exit");
    return EOK;
}



/*****************************************/
/* Array of structures for resolution of
 * the different event properties.
 */
struct elapi_resolve_list elapi_known_fields[] = {
    { E_TIMESTAMP, { COL_TYPE_STRING,   elapi_timestamp_cb }},
    { E_UTCTIME,   { COL_TYPE_INTEGER,  elapi_utctime_cb   }},
    { E_OFFSET,    { COL_TYPE_INTEGER,  elapi_offset_cb    }},
    { E_MESSAGE,   { COL_TYPE_STRING,   elapi_message_cb   }},
    /* ADD NEW CALLBACKS HERE */
    { NULL,        { COL_TYPE_ANY,      NULL }}
};




/*****************************************/
/* A callback function to do substitutions
 * of different properties as we copy the event.
 */
static int elapi_resolve_item(struct collection_item *item,
                              void *ext_data,
                              int *skip)
{
    int error = EOK;
    struct elapi_resolve_data *resolver;
    struct collection_item *res_item;
    struct elapi_rslv_item_data *rslv_pair;
    int res;

    TRACE_FLOW_STRING("elapi_resolve_item", "Entry");

    /* Do we care about this field ? */
    if (strncmp(col_get_item_property(item, NULL),
                E_PREFIX,
                E_PREFIX_LEN) != 0) {
        TRACE_FLOW_STRING("elapi_resolve_item. Skipping resoltion.", "Exit");
        return EOK;
    }

    TRACE_FLOW_STRING("Item to resolve: ", col_get_item_property(item, NULL));

    /* This is an internal field that might need resolution */
    resolver = (struct elapi_resolve_data *)ext_data;

    /* NOTE: This iteration loop uses advanced iterator
     * capabilities. Read more about it before you decide
     * to use this code as an example.
     */
    while (1) {

        /* Advance to next item in the list */
        error = col_iterate_collection(resolver->handle->resolve_list,
                                       &res_item);
        if (error) {
            TRACE_ERROR_NUMBER("Failed to iterate collection", error);
            return error;
        }

        /* Are we done ? This means we looped and did not find
         * the item. */
        if (res_item == NULL) break;

        /* Compare items */
        res = col_compare_items(item,
                                res_item,
                                COL_CMPIN_PROP_EQU,
                                NULL);
        if (res == 0) {
            /* Item names are the same, so drill down and get expected type. */
            rslv_pair = *((struct elapi_rslv_item_data **)col_get_item_data(res_item));
            /* Make sure that types matched too */
            if (rslv_pair->type == col_get_item_type(item)) {
                /* This is the item we need to resolve so resolve */
                error = rslv_pair->resolve_cb(resolver,
                                              item,
                                              skip);
                if (error) {
                    TRACE_ERROR_NUMBER("Failed to resolve item", error);
                    return error;
                }

                /* Pin down the iterator here */
                col_pin_iterator(resolver->handle->resolve_list);

                /* Break out of loop */
                break;
            }
        }
    }
    TRACE_FLOW_STRING("elapi_resolve_item", "Exit");
    return error;
}

/* Message resolution function */
int elapi_resolve_message(struct collection_item *item,
                          struct collection_item *event)
{
    int error = EOK;
    struct elapi_data_out *serialized;

    TRACE_FLOW_STRING("elapi_resolve_message", "Entry");

    /* I prefer to allocate it rather than do it on stack.
     * The main reason is that I would have to memset
     * the struct to init it.
     * So why not just use the interface we already have.
     */
    error = elapi_alloc_serialized_data(&serialized);
    if (error) {
        TRACE_ERROR_NUMBER("Failed to allocate serialized data", error);
        return error;
    }

    /* Call string substitution */
    error = elapi_sprintf(serialized,
                          (const char *)col_get_item_data(item),
                          event);
    if (error) {
        TRACE_ERROR_NUMBER("Failed to build message", error);
        elapi_free_serialized_data(serialized);
        return error;
    }

    /* Put the resolved value into the item */
    error = col_modify_str_item(item,
                                NULL,
                                (char *)serialized->buffer,
                                serialized->length + 1);

    /* Clean data regardless of the result */
    elapi_free_serialized_data(serialized);

    if (error) {
        TRACE_ERROR_NUMBER("Failed to modify message item", error);
        return error;
    }

    TRACE_FLOW_NUMBER("elapi_resolve_message. Exit. Returning", error);
    return error;
}

/* Resolve event */
int elapi_resolve_event(struct collection_item **final_event,
                        struct collection_item *event,
                        struct elapi_dispatcher *handle)
{
    int error = EOK;
    struct elapi_resolve_data resolver;
    struct collection_item *new_event;
    time_t local;
    time_t utc;

    TRACE_FLOW_STRING("elapi_create_event_ctx", "Entry");

    /* Prepare the resolver */
    resolver.message = NULL;
    resolver.handle = handle;
    /* Get seconds */
    resolver.tm = time(NULL);
    /* Convert to local and UTC structured time */
    localtime_r(&resolver.tm, &(resolver.local_time));
    gmtime_r(&resolver.tm, &(resolver.utc_time));
    /* Convert back */
    utc = mktime(&(resolver.utc_time));
    local = mktime(&(resolver.local_time));
    /* Get offset - it is safe to typecast to int here */
    resolver.offset = (int)(difftime(local, utc));

    /* NOTE: We will use FLATDOT mode.
     * We will see what people have to say
     * about this approach...
     */
    error = col_copy_collection_with_cb(&new_event,
                                        event,
                                        NULL,
                                        COL_COPY_FLATDOT,
                                        elapi_resolve_item,
                                        (void *)&resolver);
    if (error) {
        TRACE_ERROR_NUMBER("Failed to resolve the event", error);
        return error;
    }

    if (resolver.message) {
        /* Now resolve message. We need to do it last since
         * we do not know the order of the properties
         * and message can be referencing properties
         * that are later than message in the list
         * and have not been resolved yet.
         */
        error = elapi_resolve_message(resolver.message,
                                      new_event);
        if (error) {
            TRACE_ERROR_NUMBER("Failed to resolve the event", error);
            col_destroy_collection(new_event);
            return error;
        }
    }

    *final_event = new_event;

    TRACE_FLOW_STRING("elapi_create_event_ctx", "Exit");
    return error;
}

/* Function to initialize resolution list */
int elapi_init_resolve_list(struct collection_iterator **list)
{
    int error = EOK;
    struct elapi_resolve_list *current;
    struct collection_item *col = NULL;
    struct collection_iterator *iterator = NULL;
    struct elapi_rslv_item_data *bin_data;

    TRACE_FLOW_STRING("elapi_init_resolve_list", "Entry");

    /* Create collection of fields that we know how to process */
    error = col_create_collection(&col,
                                  ELAPI_RESOLVE_ITEM,
                                  COL_CLASS_ELAPI_RES_ITEM);

    if (error) {
        TRACE_ERROR_NUMBER("Failed to create collection", error);
        return error;
    }

    /* Loop through the static array and turn it into a collection */
    current = elapi_known_fields;
    while (current->name) {
        bin_data = &(current->resolve_item);
        error = col_add_binary_property(col,
                                        NULL,
                                        current->name,
                                        (void *)&bin_data,
                                        sizeof(struct elapi_rslv_item_data *));
        if (error) {
            TRACE_ERROR_NUMBER("Failed to add item resolver", error);
            col_destroy_collection(col);
            return error;
        }

        current++;
    }

    /* Now bind iterator */
    error = col_bind_iterator(&iterator, col, COL_TRAVERSE_FLAT);
    if (error) {
        TRACE_ERROR_NUMBER("Failed to bind collection", error);
        col_destroy_collection(col);
        return error;
    }

    /* We do not need the collection itself - we have iterator */
    col_destroy_collection(col);

    *list = iterator;

    TRACE_FLOW_STRING("elapi_init_resolve_list", "Exit");
    return error;
}