/*-
 * Copyright (c) 2013 UPLEX Nils Goroll Systemoptimierung
 * Copyright (c) 2013 Otto Gmbh & Co KG
 * All rights reserved
 * Use only with permission
 *
 * Authors: Geoffrey Simmons <geoffrey.simmons@uplex.de>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include <pthread.h>
#include <stdlib.h>
#include <syslog.h>

#include "varnishevent.h"
#include "vas.h"
#include "miniobj.h"
#include "vqueue.h"
#include "vsb.h"

static const char *statename[3] = { "EMPTY", "OPEN", "DONE" };

static pthread_mutex_t freelist_lock = PTHREAD_MUTEX_INITIALIZER;
static char *bufptr;
static int ntags;

static void
free_hdrs(hdr_t *hdrs)
{
    if (hdrs != NULL) {
        free(hdrs->record);
        free(hdrs);
    }
}

static void
data_Cleanup(void)
{
    for (int i = 0; i < config.max_data; i++) {
        free(logline[i].tag);
        free_hdrs(logline[i].rx_headers);
        free_hdrs(logline[i].tx_headers);
        free_hdrs(logline[i].vcl_log);
        free_hdrs(logline[i].vcl_call);
    }
    free(logline);
    free(bufptr);
    AZ(pthread_mutex_destroy(&freelist_lock));
}

void
DATA_Clear_Logline(logline_t *ll)
{
    CHECK_OBJ_NOTNULL(ll, LOGLINE_MAGIC);
    
    ll->state = DATA_EMPTY;
    ll->bitmap = 0;
    ll->spec = 0;
    ll->incomplete = 0;
    ll->t = 0;
    if (ll->rx_headers)
        ll->rx_headers->nrec = 0;
    if (ll->tx_headers)
        ll->tx_headers->nrec = 0;
    if (ll->vcl_log)
        ll->vcl_log->nrec = 0;
    if (ll->vcl_call)
        ll->vcl_call->nrec = 0;
    for (int i = 0; i < ntags; i++)
        ll->tag[i].len = 0;
}

#define INIT_HDR_RECORDS(tag, hdr, max) do {                            \
    if (FMT_Read_Hdr(tag)) {                                            \
        hdr = (hdr_t *) malloc(sizeof(hdr_t));                          \
        if (hdr == NULL)                                                \
            return errno;                                               \
        hdr->record                                                     \
            = (record_t *) calloc(max, sizeof(record_t));               \
        if (hdr->record == NULL)                                        \
            return errno;                                               \
        for (int j = 0; j < max; j++) {                                 \
            hdr->record[j].magic = RECORD_MAGIC;                        \
            hdr->record[j].data = &bufptr[bufidx++ * config.max_reclen]; \
        }                                                               \
    }                                                                   \
    else {                                                              \
        hdr = NULL;                                                     \
    }                                                                   \
} while(0)

int
DATA_Init(void)
{
    int bufidx = 0;
    int nrecords;

    ntags = FMT_Get_nTags();
    
    nrecords = config.max_data * ntags;
    if (FMT_Read_Hdr(SLT_RxHeader))
        nrecords += config.max_data * config.max_headers;
    if (FMT_Read_Hdr(SLT_TxHeader))
        nrecords += config.max_data * config.max_headers;
    if (FMT_Read_Hdr(SLT_VCL_Log))
        nrecords += config.max_data * config.max_vcl_log;
    if (FMT_Read_Hdr(SLT_VCL_call))
        nrecords += config.max_data * config.max_vcl_call;

    LOG_Log(LOG_DEBUG, "Allocating space for %d records (%d bytes)", nrecords,
            nrecords * config.max_reclen);
    bufptr = (char *) calloc(nrecords, config.max_reclen);
    if (bufptr == NULL)
        return errno;
    
    logline = (logline_t *) calloc(config.max_data, sizeof(logline_t));
    if (logline == NULL)
        return errno;

    VSTAILQ_INIT(&freehead);
    for (int i = 0; i < config.max_data; i++) {
        logline[i].tag
            = (record_t *) calloc(ntags, sizeof(record_t));
        if (logline[i].tag == NULL)
            return errno;
        for (int j = 0; j < ntags; j++) {
            logline[i].tag[j].magic = RECORD_MAGIC;
            logline[i].tag[j].data = &bufptr[bufidx++ * config.max_reclen];
        }

        INIT_HDR_RECORDS(SLT_RxHeader, logline[i].rx_headers,
                         config.max_headers);
        INIT_HDR_RECORDS(SLT_TxHeader, logline[i].tx_headers,
                         config.max_headers);
        INIT_HDR_RECORDS(SLT_VCL_Log,  logline[i].vcl_log, config.max_vcl_log);
        INIT_HDR_RECORDS(SLT_VCL_call, logline[i].vcl_call,
                         config.max_vcl_call);
        
        logline[i].magic = LOGLINE_MAGIC;
        DATA_Clear_Logline(&logline[i]);
	VSTAILQ_INSERT_TAIL(&freehead, &logline[i], freelist);
    }

    assert(bufidx == nrecords);

    data_open = data_done = data_occ_hi = 0;
    global_nfree = config.max_data;

    atexit(data_Cleanup);
    
    return(0);
}

/* 
 * take all free entries from the datatable for lockless allocation
 */
unsigned
DATA_Take_Freelist(struct freehead_s *dst)
{
    unsigned nfree;
    
    AZ(pthread_mutex_lock(&freelist_lock));
    VSTAILQ_CONCAT(dst, &freehead);
    nfree = global_nfree;
    global_nfree = 0;
    AZ(pthread_mutex_unlock(&freelist_lock));
    return nfree;
}

/*
 * return to global freelist
 * returned must be locked by caller, if required
 */
void
DATA_Return_Freelist(struct freehead_s *returned, unsigned nreturned)
{
    AZ(pthread_mutex_lock(&freelist_lock));
    VSTAILQ_CONCAT(&freehead, returned);
    global_nfree += nreturned;
    AZ(pthread_mutex_unlock(&freelist_lock));
}

#define DUMP_HDRS(vsb, ll, hdr) do {                    \
    if (ll->hdr)                                        \
        for (j = 0; j < ll->hdr->nrec; j++)             \
            if (ll->hdr->record[j].len) {               \
                VSB_putc(vsb, '[');                     \
                VSB_bcat(vsb, ll->hdr->record[j].data,  \
                    ll->hdr->record[j].len);            \
                VSB_cat(vsb, "] ");                     \
            }                                           \
 } while (0)

void
DATA_Dump(void)
{
    struct vsb *data;
    logline_t *ll;

    data = VSB_new_auto();
    
    for (int i = 0; i < config.max_data; i++) {
        int j;

        if (logline == NULL || logline[i].magic != LOGLINE_MAGIC)
            continue;
        
        if (logline[i].state == DATA_EMPTY)
            continue;

        ll = &logline[i];
        VSB_clear(data);
        
        VSB_printf(data, "Data entry %d: state=%s dir=%c tags={",
            i, statename[ll->state],
            C(ll->spec) ? 'c' : B(ll->spec) ? 'b' : '-');
        
        for (j = 0; j < ntags; j++)
            if (ll->tag[j].len) {
                VSB_cat(data, VSL_tags[idx2tag[j]]);
                VSB_cat(data, "=[");
                VSB_bcat(data, ll->tag[j].data, ll->tag[j].len);
                VSB_cat(data, "] ");
            }

        VSB_cat(data, "} rx_headers={");
        DUMP_HDRS(data, ll, rx_headers);

        VSB_cat(data, "} tx_headers={");
        DUMP_HDRS(data, ll, tx_headers);

        VSB_cat(data, "} vcl_log={");
        DUMP_HDRS(data, ll, vcl_log);
        VSB_putc(data, '}');

        VSB_finish(data);

        LOG_Log(LOG_INFO, "%s", VSB_data(data));
    }
}
