/*
**  OSSP l2 - Flexible Logging
**  Copyright (c) 2001-2003 Cable & Wireless Deutschland GmbH
**  Copyright (c) 2001-2003 The OSSP Project (http://www.ossp.org/)
**  Copyright (c) 2001-2003 Ralf S. Engelschall <rse@engelschall.com>
**
**  This file is part of OSSP l2, a flexible logging library which
**  can be found at http://www.ossp.org/pkg/lib/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_channel.c: channel object
**
*/

#include "l2_p.h"

/*
 * A channel is the central object for a logging stream. It is
 * implemented by a framework (the code implemented here) which provides
 * the channel API and which is equal for all channels and a particular
 * handler which implements the characteristics specific to a particular
 * channel class. The lifecycle of a channel is illustrated in the
 * following figure:
 *
 * -----BEGIN EMBEDDED OBJECT-----
 * Content-editor: xfig %s
 * Content-encoding: gzip:9 base64
 * Content-type: application/fig
 * Content-viewer: xfig %s
 * Description: L2 Channel Lifecycle
 * Filename: l2_ch_lifecycle.fig
 * Last-modified: 2000-09-28/14:40
 * Name: l2_ch_lifecycle
 * Version: eo/1.0
 * H4sIAGdztDsCA62WTW/bMAyGz/WvENBzM5KyPnwusGFAdtp1l8BVWgOZUyTuhv77
 * kfKHnC4t5CZJ7JAO3kcUKUq5/fr9m9IrKtab9uFYb55DcR/aLhyKH6E7NHWxDh17
 * ShUIsAIofjbt4y4Ud1QgASgqQGlSt3V8Fai0AoV8gTJyI76xLD6Tb35iPSpybNlS
 * PsknV5po5WC0BZZRWSlL8km+tlYsa3MwJfAP6Kdokl8iltHKwjiwJ5jJL52DbIxB
 * Z+aY5BvSVT7GEieSzISZfGN9Fa0cjAVj55iZz7XPxxg6mdTMNz5/Ug55ncwwyXdU
 * mnyMPS148p1bUHCPAPPcJN+jLOrM3GjUOMckX2PV16ygiJDL9Zi7Qa4GR36i4kYM
 * XMW6yZ1LJP160y+iofqUy4SeKW01zCtaMT2XBjSRUrdFJr1h0rmAZg3qhnV0cUAT
 * KTVcfoYsjOmN1jLxbPWJdZV6T2GkTstPb2pOh3ilek+kNN3LmWO6UuflZB0/YvYk
 * B+eYXpi4PM4YYs80g3XK/Gh1JHG0UC/p3OFIhRkhiuXNJ3aaDr0/tKExbraWqtFX
 * g1qsiyue8qDxWq0ykdI+l5/g2eE87rCX79XGj8cK6CGgMh0gyH8qYPrPoTCeGzx0
 * Jft6yUHU+3bbPL4cwi8AzFAazU+XKXlJlHL6O64uec3KQ9h0OQPK1uJRZNob9RCO
 * 3WH/mjGc5kC1HXX759Bmiixas0jkZHIgBy9wrH8PTRe+bHcvx6dMrTUOPqOVYA1x
 * sFhJQnf7Y8hUOVfCOZV/v3h+3N88W7tmG7rm9ztCXPGE/Gw4IuAgLUd67M4V3f8/
 * nPS/M9IprN/UXfMnR2b8MA4Rl6Np3wh9EtJM6OWRlkGrfrtUa1L3T5u2DTu15qnW
 * r/Wup/wDvlAM/PkMAAA=
 * -----END EMBEDDED OBJECT-----
 */

/* create channel */
l2_result_t l2_channel_create(l2_channel_t **chp, l2_env_t *env, const char *name)
{
    l2_channel_t *ch;
    l2_handler_t *h;
    int i;

    /* argument sanity check */
    if (env == NULL || name == NULL)
        return L2_ERR_ARG;

    /* lookup channel handler */
    h = NULL;
    for (i = 0; i < L2_MAX_HANDLERS && env->handlers[i] != NULL; i++) {
        if (strcmp(env->handlers[i]->name, name) == 0) {
            h = env->handlers[i];
            break;
        }
    }
    if (h == NULL)
        return L2_ERR_CH;

    /* allocate channel structure */
    if ((ch = (l2_channel_t *)malloc(sizeof(l2_channel_t))) == NULL)
        return L2_ERR_SYS;

    /* initialize channel structure */
    ch->env = env;
    ch->state = L2_CHSTATE_CREATED;
    ch->parent = NULL;
    ch->sibling = NULL;
    ch->child = NULL;
    memset(&ch->context, 0, sizeof(l2_context_t));
    memcpy(&ch->handler, h, sizeof(l2_handler_t));
    ch->levelmask = env->levelmask;
    ch->flushmask = env->flushmask;

    /* (optionally) perform create operation in handler */
    if (ch->handler.create != NULL) {
        if (ch->handler.create(&ch->context, ch) != L2_OK) {
            free(ch);
            return L2_ERR_SYS;
        }
    }

    /* pass object to caller */
    (*chp) = ch;

    return L2_OK;
}

/* link channels */
l2_result_t l2_channel_link(l2_channel_t *ch0, l2_link_t id, l2_channel_t *ch, ...)
{
    l2_channel_t *chT;
    l2_channel_t *chN;
    va_list ap;

    /* argument sanity check */
    if (ch0 == NULL || ch == NULL)
        return L2_ERR_ARG;

    /* perform either child or sibling linking operation(s) */
    if (id == L2_LINK_CHILD) {
        /* make sure child parents are filters only */
        if (ch0->handler.type != L2_CHANNEL_FILTER)
            return L2_ERR_USE;
        va_start(ap, ch);
        chT = ch;
        do {
            chN = (l2_channel_t *)va_arg(ap, l2_channel_t *);
            if (chN != NULL && chT->handler.type != L2_CHANNEL_FILTER)
                return L2_ERR_USE;
        } while ((chT = chN) != NULL);
        va_end(ap);

        /* perform link operation(s) */
        va_start(ap, ch);
        do {
            ch->parent  = ch0;
            if (ch0->child == NULL)
                ch0->child = ch;
            else {
                chT = ch0->child;
                while (chT->sibling != NULL)
                    chT = chT->sibling;
                chT->sibling = ch;
            }
            ch0 = ch;
            ch = (l2_channel_t *)va_arg(ap, l2_channel_t *);
        } while (ch != NULL);
        va_end(ap);
    }
    else if (id == L2_LINK_SIBLING) {
        /* perform link operation(s) */
        va_start(ap, ch);
        do {
            ch0->sibling = ch;
            ch->parent   = ch0->parent;
            ch0 = ch;
            ch = (l2_channel_t *)va_arg(ap, l2_channel_t *);
        } while (ch != NULL);
        va_end(ap);
    }

    return L2_OK;
}

/* unlink channels */
l2_result_t l2_channel_unlink(l2_channel_t *ch)
{
    l2_channel_t *chS;
    l2_channel_t *chP;

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

    /* make sure channel is in state "created" */
    if (ch->state != L2_CHSTATE_CREATED)
        return L2_ERR_USE;

    /* make sure channel has no childs */
    if (ch->child != NULL)
        return L2_ERR_USE;

    /* unlink the channel */
    chP = ch->parent;
    ch->parent = NULL;
    if (chP != NULL) {
        if (chP->child == ch)
            chP->child = ch->sibling;
        else {
            chS = chP->child;
            while (chS->sibling != ch)
                chS = chS->sibling;
            chS->sibling = ch->sibling;
        }
    }

    return L2_OK;
}

/* return upstream channel */
l2_result_t l2_channel_upstream(l2_channel_t *ch, l2_channel_t **chU)
{
    /* argument sanity check */
    if (ch == NULL || chU == NULL)
        return L2_ERR_ARG;

    /* determine parent/upstream channel */
    *chU = ch->parent;

    return (*chU != NULL ? L2_OK : L2_ERR_CH);
}

/* return (subsequent) downstream channel(s) */
l2_result_t l2_channel_downstream(l2_channel_t *ch, l2_channel_t **chD)
{
    /* argument sanity check */
    if (ch == NULL || chD == NULL)
        return L2_ERR_ARG;

    /* determine (next) downstream/child channel */
    if (*chD == NULL)
        *chD = ch->child;
    else
        *chD = (*chD)->sibling;

    return (*chD != NULL ? L2_OK : L2_ERR_CH);
}

/* return channel type */
l2_result_t l2_channel_type(l2_channel_t *ch, l2_chtype_t *type)
{
    /* argument sanity check */
    if (ch == NULL || type == NULL)
        return L2_ERR_ARG;

    /* return type */
    (*type) = ch->handler.type;

    return L2_OK;
}

/* set channel level masks */
l2_result_t l2_channel_levels(l2_channel_t *ch, unsigned int levelmask, unsigned int flushmask)
{
    /* argument sanity check */
    if (ch == NULL)
        return L2_ERR_ARG;

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

    return L2_OK;
}

/* configure channel */
l2_result_t l2_channel_configure(l2_channel_t *ch, const char *fmt, ...)
{
    l2_result_t rv;
    va_list ap;

    /* argument sanity check */
    if (ch == NULL || fmt == NULL)
        return L2_ERR_ARG;

    /* make sure the channel is in state "created" */
    if (ch->state != L2_CHSTATE_CREATED)
        return L2_ERR_USE;

    /* pass operation to handler */
    rv = L2_OK;
    va_start(ap, fmt);
    if (ch->handler.configure != NULL)
        rv = ch->handler.configure(&ch->context, ch, fmt, ap);
    va_end(ap);

    return rv;
}

/* open channel */
l2_result_t l2_channel_open(l2_channel_t *ch)
{
    l2_result_t rv;
    l2_result_t rvD;
    l2_channel_t *chD;

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

    /* make sure channel is in state "created" */
    if (ch->state != L2_CHSTATE_CREATED)
        return L2_ERR_USE;

    /* perform operation */
    if (ch->handler.open != NULL)
        rv = ch->handler.open(&ch->context, ch);
    else
        rv = L2_OK_PASS;

    /* optionally pass operation downstream */
    if (rv == L2_OK_PASS) {
        rv = L2_OK;
        chD = NULL;
        while (l2_channel_downstream(ch, &chD) == L2_OK)
            if ((rvD = l2_channel_open(chD)) != L2_OK)
                rv = rvD;
        if (rv != L2_OK) {
            chD = NULL;
            while (l2_channel_downstream(ch, &chD) == L2_OK)
                l2_channel_close(chD);
        }
    }

    /* mark channel as opened */
    if (rv == L2_OK)
        ch->state = L2_CHSTATE_OPENED;

    return rv;
}

/* write to channel */
l2_result_t l2_channel_write(l2_channel_t *ch, l2_level_t level, const char *buf, size_t bufsize)
{
    int l, j;
    l2_result_t rv;
    l2_result_t rvD;
    l2_channel_t *chD;

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

    /* make sure channel is in state "opened" */
    if (ch->state != L2_CHSTATE_OPENED)
        return L2_ERR_USE;

    /* 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 level mask already stops processing */
    if (!(ch->levelmask & level))
        return L2_OK;

    /* short circuiting */
    if (bufsize == 0)
        return L2_OK;

    /* perform operation */
    if (ch->handler.write != NULL)
        rv = ch->handler.write(&ch->context, ch, level, buf, bufsize);
    else
        rv = L2_OK_PASS;

    /* optionally pass operation downstream */
    if (rv == L2_OK_PASS) {
        rv = L2_OK;
        chD = NULL;
        while (l2_channel_downstream(ch, &chD) == L2_OK)
            if ((rvD = l2_channel_write(chD, level, buf, bufsize)) != L2_OK)
                rv = rvD;
    }

    return rv;
}

/* flush channel (stack) */
l2_result_t l2_channel_flush(l2_channel_t *ch)
{
    l2_result_t rv;
    l2_result_t rvD;
    l2_channel_t *chD;

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

    /* make sure channel is in state "opened" */
    if (ch->state != L2_CHSTATE_OPENED)
        return L2_ERR_USE;

    /* perform operation */
    if (ch->handler.flush != NULL)
        rv = ch->handler.flush(&ch->context, ch);
    else
        rv = L2_OK_PASS;

    /* optionally pass operation downstream */
    if (rv == L2_OK_PASS) {
        rv = L2_OK;
        chD = NULL;
        while (l2_channel_downstream(ch, &chD) == L2_OK)
            if ((rvD = l2_channel_flush(chD)) != L2_OK)
                rv = rvD;
    }

    return rv;
}

/* close channel (stack) */
l2_result_t l2_channel_close(l2_channel_t *ch)
{
    l2_result_t rv;
    l2_result_t rvD;
    l2_channel_t *chD;

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

    /* make sure channel is in state "opened" */
    if (ch->state != L2_CHSTATE_OPENED)
        return L2_ERR_USE;

    /* perform operation */
    if (ch->handler.close != NULL)
        rv = ch->handler.close(&ch->context, ch);
    else
        rv = L2_OK_PASS;

    /* optionally pass operation downstream */
    if (rv == L2_OK_PASS) {
        rv = L2_OK;
        chD = NULL;
        while (l2_channel_downstream(ch, &chD) == L2_OK)
            if ((rvD = l2_channel_close(chD)) != L2_OK)
                rv = rvD;
    }

    /* mark channel as closed */
    if (rv == L2_OK)
        ch->state = L2_CHSTATE_CREATED;

    return rv;
}

/* destroy channel */
l2_result_t l2_channel_destroy(l2_channel_t *ch)
{
    l2_result_t rv;
    l2_result_t rvD;   /* downstream */
    l2_channel_t *chD;
    l2_result_t rvL;   /* lookahead */
    l2_channel_t *chL;

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

    /* make sure channel is in state "opened" */
    if (ch->state == L2_CHSTATE_OPENED)
        if ((rv = l2_channel_close(ch)) != L2_OK)
            return rv;

    /* perform operation */
    if (ch->handler.destroy != NULL)
        rv = ch->handler.destroy(&ch->context, ch);
    else
        rv = L2_OK_PASS;

    /* optionally pass operation downstream */
    if (rv == L2_OK_PASS) {
        rv = L2_OK;
        chD = NULL;
        if (l2_channel_downstream(ch, &chD) == L2_OK) {
            chL = chD;
            do {
                rvL = l2_channel_downstream(ch, &chL);
                if ((rvD = l2_channel_destroy(chD)) != L2_OK)
                    rv = rvD;
                if (rvL == L2_OK)
                    chD = chL;
            } while ((rv == L2_OK) && (rvL == L2_OK));
        }
    }

    /* free channel structure */
    if (rv == L2_OK)
        free(ch);

    return rv;
}

/* log a message to channel */
l2_result_t l2_channel_log(l2_channel_t *ch, 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_channel_vlog(ch, level, fmt, ap);
    va_end(ap);

    return rv;
}

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

/* indirect callback function from l2_channel_vlog for formatting */
static void l2_channel_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_env_t *env = (l2_env_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 && env->formatters[i].cb != NULL; i++) {
        if (env->formatters[i].id == cId) {
            rv = env->formatters[i].cb(env->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 channel (va_list-variant) */
l2_result_t l2_channel_vlog(l2_channel_t *ch, l2_level_t level, const char *fmt, va_list ap)
{
    int l, j;
    size_t len;
    l2_result_t rv;
    l2_util_format_t vfmt;
    l2_env_t *env;

    /* argument sanity check */
    if (ch == NULL || level == 0 || fmt == 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 level mask already stops processing */
    if (!(ch->levelmask & level))
        return L2_OK;

    /* format message */
    env = ch->env;
    vfmt.curpos = env->message;
    vfmt.endpos = env->message + L2_MAX_MSGSIZE;
    vfmt.data[0].vp = env;
    vfmt.data[1].i  = L2_ERR_FMT;
    vfmt.flush  = l2_channel_vlog_flush;
    vfmt.format = l2_channel_vlog_format;
    len = l2_util_format(&vfmt, fmt, ap);

    /* check for formatting error including buffer overrun */
    if (len == -1)
        return (l2_result_t)(vfmt.data[1].i);

    /* check for formatting led to completely empty message */
    if (len == 0)
        return L2_ERR_FMT;

    /* check for formatting led to newline-only message */
    if (len == 1 && env->message[len] == '\n')
        return L2_ERR_FMT;

    /* make sure a trailing newline exists; L2_MSG_BUFSIZE has room for CR/LF */
    if (env->message[len-1] != '\n')
        env->message[len++] = '\n';

    /* make sure a trailing NUL exists; L2_MSG_BUFSIZE has room for NUL  */
    env->message[len] = '\0';

    /* write message to channel */
    rv = L2_OK;
    if ((rv = l2_channel_write(ch, level, env->message, len)) != L2_OK)
        return rv;
    if (ch->flushmask & level)
        l2_channel_flush(ch);

    return rv;
}

/* return environment object */
l2_result_t l2_channel_env(l2_channel_t *ch, l2_env_t **env)
{
    if (ch == NULL || env == NULL)
        return L2_ERR_ARG;
    *env = ch->env;
    return L2_OK;
}

