/*
**  L2 - OSSP Logging Library
**  Copyright (c) 2001 The OSSP Project (http://www.ossp.org/)
**  Copyright (c) 2001 Cable & Wireless Deutschland (http://www.cw.com/de/)
**
**  This file is part of OSSP L2, a flexible logging library which
**  can be found at http://www.ossp.org/pkg/l2/.
**
**  Permission to use, copy, modify, and distribute this software for
**  any purpose with or without fee is hereby granted, provided that
**  the above copyright notice and this permission notice appear in all
**  copies.
**
**  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
**  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.
**
**  l2.c: internal C implementation
**
*/

#include "l2.h"
#include "l2_p.h"

/* create stream */
l2_stream_t *l2_stream_create(void)
{
    l2_stream_t *st;
    int i;
    
    /* allocate stream structure */ 
    if ((st = (l2_stream_t *)malloc(sizeof(l2_stream_t))) == NULL)
        return NULL;

    /* initialize stream structure */
    st->levelmask = 0;
    st->flushmask = 0;
    for (i = 0; i < L2_MAX_CHANNELS; i++)
        st->channels[i].ch = NULL;
    for (i = 0; i < L2_MAX_FORMATTERS; i++)
        st->formatters[i].cb = NULL;

    return st;
}

/* attach channel to stream */
l2_result_t l2_stream_channel(l2_stream_t *st, l2_channel_t *ch, unsigned int levelmask, unsigned int flushmask)
{
    l2_channel_t *chC;
    l2_channel_t *chN;
    int i;

    /* argument sanity check */
    if (st == NULL || ch == NULL || levelmask == 0)
        return L2_ERR_ARG;

    /* make sure the stack of channels consists of zero or more
       filter channels followed by exactly one output channel */
    for (chC = ch; (chN = l2_channel_downstream(chC)) != NULL; chC = chN)
        if (l2_channel_type(chC) != L2_CHANNEL_FILTER)
            return L2_ERR_USE;
    if (l2_channel_type(chC) != L2_CHANNEL_OUTPUT)
        return L2_ERR_USE;

    /* find next free channel position in channel array */
    for (i = 0; i < L2_MAX_CHANNELS && st->channels[i].ch != NULL; i++)
        ;
    if (i == L2_MAX_CHANNELS)
        return L2_ERR_MEM;

    /* attach channel to stream */
    st->channels[i].ch = ch;
    st->channels[i].levelmask = levelmask;
    st->channels[i].flushmask = flushmask;

    return L2_OK;
}

/* attach formatter to stream */
l2_result_t l2_stream_formatter(l2_stream_t *st, char id, l2_formatter_t cb, l2_context_t *ctx)
{
    int i;

    /* argument sanity check */
    if (st == NULL || id == '\0' || cb == NULL)
        return L2_ERR_ARG;

    /* find next free formatter position in formatter array */
    for (i = 0; i < L2_MAX_FORMATTERS && st->formatters[i].cb != NULL; i++)
        ;
    if (i == L2_MAX_FORMATTERS)
        return L2_ERR_MEM;

    /* attach formatter to stream */
    st->formatters[i].id   = id;
    st->formatters[i].ctx  = ctx;
    st->formatters[i].cb   = cb;

    return L2_OK;
}

/* set stream level mask */
l2_result_t l2_stream_levels(l2_stream_t *st, unsigned int levelmask, unsigned int flushmask)
{
    /* argument sanity check */
    if (st == NULL)
        return L2_ERR_ARG;

    /* override global level mask */
    st->levelmask = levelmask;
    st->flushmask = flushmask;

    return L2_OK;
}

/* log a message to stream */
l2_result_t l2_stream_log(l2_stream_t *st, l2_level_t level, const char *fmt, ...)
{
    va_list ap;
    l2_result_t rv;

    /* pass-through to va_list-based variant */
    va_start(ap, fmt);
    rv = l2_stream_vlog(st, level, fmt, ap);
    va_end(ap);

    return rv;
}

/* indirect callback function from l2_stream_vlog for flushing */
static int l2_stream_vlog_flush(l2_util_format_t *vfmt)
{
    /* we do no format buffer flushing */
    return -1;
}

/* indirect callback function from l2_stream_vlog for formatting */
static void l2_stream_vlog_format(
    l2_util_format_t *vfmt,
    char *cPrefix, char *cPad, char **cppOut, size_t *npOutLen,
    char *cpBuf, int nBufLenMax, char *cpParam, char cId, va_list *apArgs)
{
    l2_stream_t *st = (l2_stream_t *)(vfmt->data[0].vp);
    l2_result_t rv;
    int i;

    /* init formatting result */
    *cPrefix = '\0';
    *cPad = ' ';
    *cppOut = NULL;
    *npOutLen = 0;

    /* iterate over all configured L2 formatters */
    for (i = 0; i < L2_MAX_FORMATTERS && st->formatters[i].cb != NULL; i++) {
        if (st->formatters[i].id == cId) {
            rv = st->formatters[i].cb(st->formatters[i].ctx, cId, cpParam, 
                                      cpBuf, nBufLenMax, npOutLen, apArgs);
            vfmt->data[1].i = (int)rv;
            if (rv == L2_OK) {
                *cppOut = cpBuf;
                break;
            }
        }
    }
    return;
}

/* log a message to stream (va_list-variant) */
l2_result_t l2_stream_vlog(l2_stream_t *st, l2_level_t level, const char *fmt, va_list ap)
{
    int i;
    int l, j;
    size_t len;
    l2_result_t rv;
    l2_util_format_t vfmt;

    /* argument sanity check */
    if (st == NULL || level == 0 || fmt == NULL || ap == NULL)
        return L2_ERR_ARG;

    /* make sure only a single level is specified */
    for (l = level, j = 0; l != 0; l = (l >> 1))
        if (l & 0x1)
            j++;
    if (j != 1)
        return L2_ERR_ARG;

    /* check whether global level mask already stops processing */
    if (!(st->levelmask & level))
        return L2_OK;

    /* format message */
    vfmt.curpos = st->message;
    vfmt.endpos = st->message + L2_MAX_MSGSIZE;
    vfmt.data[0].vp = st;
    vfmt.data[1].i  = L2_ERR_FMT;
    vfmt.flush  = l2_stream_vlog_flush;
    vfmt.format = l2_stream_vlog_format;
    if ((len = l2_util_format(&vfmt, fmt, ap)) == -1)
        return (l2_result_t)(vfmt.data[1].i);
    if (len == 0)
        return L2_ERR_FMT;

    /* make sure a trailing newline exists */
    if (st->message[len-1] != '\n') {
        if (len == L2_MAX_MSGSIZE)
            return L2_ERR_MEM;
        st->message[len++] = '\n';
        st->message[len]   = '\0';
    }

    /* write message to zero or more channels */
    rv = L2_OK;
    for (i = 0; i < L2_MAX_CHANNELS && st->channels[i].ch != NULL; i++) {
        if (st->channels[i].levelmask & level) {
            if ((rv = l2_channel_write(st->channels[i].ch, level, st->message, len)) != L2_OK)
                break;
            if ((st->flushmask & level) || (st->channels[i].flushmask & level))
                if ((rv = l2_channel_flush(st->channels[i].ch)) != L2_OK)
                    break;
        }
    }
    return rv;
}

/* flush stream */
l2_result_t l2_stream_flush(l2_stream_t *st)
{
    int i;
    l2_result_t rv;

    /* argument sanity check */
    if (st == NULL)
        return L2_ERR_ARG;

    /* flush all attached channels */
    for (i = 0; i < L2_MAX_CHANNELS && st->channels[i].ch != NULL; i++)
        if ((rv = l2_channel_flush(st->channels[i].ch)) != L2_OK)
            return rv;

    return L2_OK;
}

/* destroy stream */
l2_result_t l2_stream_destroy(l2_stream_t *st)
{
    int i;
    l2_result_t rv;

    /* argument sanity check */
    if (st == NULL)
        return L2_ERR_ARG;

    /* destroy all attached channels */
    for (i = 0; i < L2_MAX_CHANNELS && st->channels[i].ch != NULL; i++)
        if ((rv = l2_channel_destroy(st->channels[i].ch)) != L2_OK)
            return rv;

    /* free stream structure */
    free(st);

    return L2_OK;
}

