/*
    ELAPI

    Module contains functions related to format substitution

    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 <string.h>
#include <stdio.h>
#include "elapi_priv.h"
#include "trace.h"
#include "config.h"

/* Reasonable size for one event */
/* FIXME: may be it would make sense to make it configurable ? */
#define ELAPI_SUBST_BLOCK      256

/* Calculate the potential size of the item */
static unsigned elapi_get_item_len(int type, int raw_len)
{
    int serialized_len = 0;

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

    switch (type) {
    case COL_TYPE_INTEGER:
    case COL_TYPE_UNSIGNED:
    case COL_TYPE_LONG:
    case COL_TYPE_ULONG:
        serialized_len = MAX_LONG_STRING_LEN;
        break;

    case COL_TYPE_STRING:
        serialized_len = raw_len;
        break;

    case COL_TYPE_BINARY:
        serialized_len = raw_len * 2;
        break;

    case COL_TYPE_DOUBLE:
        serialized_len = MAX_DOUBLE_STRING_LEN;
        break;

    case COL_TYPE_BOOL:
        serialized_len = MAX_BOOL_STRING_LEN;
        break;

    default:
        serialized_len = 0;
        break;
    }

    TRACE_FLOW_STRING("elapi_get_item_len", "Exit point");
    return (uint32_t)serialized_len;
}


/* Function to serialize one item */
static int elapi_sprintf_item(struct elapi_data_out *out_data,
                              struct collection_item *item)
{
    int error = EOK;
    uint32_t projected_len;
    uint32_t used_len;
    uint32_t item_len;
    void *data;
    int type;
    int i;

    TRACE_FLOW_STRING("elapi_sprintf_item", "Entry");

    /* Get projected length of the item */
    item_len = col_get_item_length(item);
    type = col_get_item_type(item);
    projected_len = elapi_get_item_len(type, item_len);

    TRACE_INFO_NUMBER("Expected data length: ", projected_len);

    /* Make sure we have enough space */
    if (out_data->buffer == NULL) {
        TRACE_INFO_STRING("First time use.", "");
        /* Add null terminating zero */
        projected_len++;
    }

    /* Grow buffer if needed */
    error = elapi_grow_data(out_data,
                            projected_len,
                            ELAPI_SUBST_BLOCK);
    if (error) {
        TRACE_ERROR_NUMBER("Error. Failed to allocate memory.", error);
        return error;
    }

    data = col_get_item_data(item);

    /* Add the value */
    switch (type) {
    case COL_TYPE_STRING:

        /* Item's length includes trailing 0 for data items */
        used_len = item_len - 1;
        memcpy(&out_data->buffer[out_data->length],
                (const char *)(data),
                used_len);
        out_data->buffer[out_data->length + used_len] = '\0';
        break;

    case COL_TYPE_BINARY:

        for (i = 0; i < item_len; i++) {
            sprintf((char *)&out_data->buffer[out_data->length + i * 2],
                    "%02X", (unsigned int)(((const unsigned char *)(data))[i]));
        }
        used_len = item_len * 2;
        /* We need it here for the case item_len = 0 */
        out_data->buffer[out_data->length + used_len] = '\0';
        break;

    case COL_TYPE_INTEGER:
        used_len = sprintf((char *)&out_data->buffer[out_data->length],
                           "%d", *((const int *)(data)));
        break;

    case COL_TYPE_UNSIGNED:
        used_len = sprintf((char *)&out_data->buffer[out_data->length],
                           "%u", *((const unsigned int *)(data)));
        break;

    case COL_TYPE_LONG:
        used_len = sprintf((char *)&out_data->buffer[out_data->length],
                           "%ld", *((const long *)(data)));
        break;

    case COL_TYPE_ULONG:
        used_len = sprintf((char *)&out_data->buffer[out_data->length],
                           "%lu", *((const unsigned long *)(data)));
        break;

    case COL_TYPE_DOUBLE:
        used_len = sprintf((char *)&out_data->buffer[out_data->length],
                           "%.4f", *((const double *)(data)));
        break;

    case COL_TYPE_BOOL:
        used_len = sprintf((char *)&out_data->buffer[out_data->length],
                           "%s",
                           (*((const unsigned char *)(data))) ? "true" : "false");
        break;

    default:
        out_data->buffer[out_data->length] = '\0';
        used_len = 0;
        break;
    }

    /* Adjust length */
    out_data->length += used_len;

    TRACE_INFO_STRING("Data: ", (char *)out_data->buffer);

    TRACE_FLOW_STRING("elapi_sprintf_item", "Exit");
    return error;

}

/* Lookup item hoping that items in message are somewhat ordered.
 * If there is some ordering we will save on performance.
 * If not we will not loos against a standard lookup.
 */
static struct collection_item *elapi_lookup_item(const char *start,
                                                 int length,
                                                 struct collection_iterator *iterator)
{
    int error = EOK;
    struct collection_item *found = NULL;
    const char *property;
    int property_len;
    uint64_t hash;

    TRACE_FLOW_STRING("elapi_lookup_item", "Entry");

    /* Prepare hash */
    hash = col_make_hash(start, length, NULL);

    /* 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(iterator,
                                       &found);
        if (error) {
            TRACE_ERROR_NUMBER("Failed to iterate collection", error);
            return NULL;
        }

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

        property = col_get_item_property(found, &property_len);

        /* Compare item and the property */
        if ((hash == col_get_item_hash(found)) &&
            (length == property_len) &&
            (strncasecmp(start, property, length) == 0)) {
            /* This is our item !!! */
            /* Pin down the iterator here */
            col_pin_iterator(iterator);

            /* Break out of loop */
            break;
        }
    }

    TRACE_FLOW_STRING("elapi_lookup_item", "Exit");
    return found;
}


/* Function to parse format string */
static const char *elapi_parse_format(const char *start,
                                      int *length,
                                      struct collection_item **item,
                                      struct collection_iterator *iterator)
{
    const char *runner;
    const char *bracket;
    const char *name_start;

    TRACE_FLOW_STRING("elapi_parse_format", "Entry");
    if ((start == NULL) || (*start == '\0')) {
        TRACE_FLOW_STRING("String is empty", "Return");
        return NULL;
    }

    runner = start;

    while (1) {
        /* First check for end of the string */
        if (*runner == '\0') {
            TRACE_FLOW_STRING("Found last token", start);
            *length = runner - start;
            return runner;
        }

        /* Is it the beginning of the field substitution? */
        if (*runner == '%') {
            /* Check for bracket */
            if (*(runner + 1) == '(') {
                /* Search for closing one */
                name_start = runner + 2;
                bracket = name_start;
                while (1) {
                    /* Check for the end */
                    if (*bracket == '\0') {
                        TRACE_FLOW_STRING("No closing bracket", start);
                        *length = bracket - start;
                        return bracket;
                    }
                    /* Did we find closing backet? */
                    if (*bracket == ')') {
                        TRACE_FLOW_STRING("Bracket is found: ", name_start);
                        /* There might be specific format specifiers */
                        if (*name_start == '!') {
                            /* Force rewind of the
                             * iterator */
                            col_rewind_iterator(iterator);
                            name_start++;
                        }

                        /* FIXME: Add other specifiers here...
                         * Specifier that can be supported in future
                         * might expand multi value property
                         * to a list of values separated by
                         * provided symbol.
                         */

                        *item = elapi_lookup_item(name_start,
                                                  bracket - name_start,
                                                  iterator);
                        bracket++;
                        if (*item == NULL) {
                            /* The item is not known (or error) */
                            TRACE_FLOW_STRING("No item in event", name_start);
                            *length = bracket - start;
                            return bracket;
                        }

                        /* Item is found */
                        TRACE_FLOW_STRING("Item found: ", name_start);
                        *length = runner - start;
                        return bracket;
                    }
                    bracket++;
                }
            }
        }
        runner++;
    }
    /* This point is unreachable */
}

/* Function to place the event items into the formatted string */
int elapi_sprintf(struct elapi_data_out *out_data,
                  const char *format_str,
                  struct collection_item *event)
{
    const char *start;
    int length;
    struct collection_item *item;
    struct collection_iterator *iterator = NULL;
    const char *result;
    int error;

    TRACE_FLOW_STRING("elapi_sprintf", "Entry");

    /* Create iterator - by thus time cevent is resolved and should
     * be a flattened collection. At least this is the assumption.
     */
    error = col_bind_iterator(&iterator, event, COL_TRAVERSE_IGNORE);
    if (error) {
        TRACE_ERROR_NUMBER("Failed to bind iterator", error);
        return error;
    }

    start = format_str;

    while(1) {

        item = NULL;
        length = 0;

        /* Parse format definition */
        result = elapi_parse_format(start, &length, &item, iterator);
        if (result == NULL) {
            TRACE_INFO_STRING("Done parsing string", "");
            break;
        }

        /* Apply parsed data */
        if (length > 0) {
            error = elapi_grow_data(out_data,
                                    length + 1,
                                    ELAPI_SUBST_BLOCK);
            if (error) {
                TRACE_ERROR_NUMBER("Error. Failed to allocate memory.", error);
                col_unbind_iterator(iterator);
                return error;
            }

            memcpy(&out_data->buffer[out_data->length],
                    (const char *)(start),
                    length);

            out_data->length += length;
            /* We asked for this one extra byte above */
            out_data->buffer[out_data->length] = '\0';
        }

        if (item != NULL) {
            TRACE_INFO_NUMBER("Need to output item", error);
            error = elapi_sprintf_item(out_data, item);
            if (error) {
                TRACE_ERROR_NUMBER("Error. Failed to allocate memory.", error);
                col_unbind_iterator(iterator);
                return error;
            }
        }

        start = result;
    }

    col_unbind_iterator(iterator);

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