/*
**  OSSP al - Assembly Line
**  Copyright (c) 2002-2005 Ralf S. Engelschall <rse@engelschall.com>
**  Copyright (c) 2002-2005 The OSSP Project <http://www.ossp.org/>
**  Copyright (c) 2002-2005 Cable & Wireless <http://www.cw.com/>
**  Copyright (c) 2002-2005 Michael van Elst <mlelstv@serpens.de>
**
**  This file is part of OSSP al, an abstract datatype of a data buffer
**  that can assemble, move and truncate data but avoids actual copying
**  and which can be found at http://www.ossp.org/pkg/lib/al/.
**
**  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.
**
**  al_test.c: assembly line library test suite
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#include "ts.h"
#include "al.h"

static char label, label2, label3;
#define LABEL ((al_label_t)&label)
#define LABEL2 ((al_label_t)&label2)
#define LABEL3 ((al_label_t)&label3)

#define S(s) s, strlen(s)

#define EVAL(name,rc,rc0,block) \
    ts_test_check(TS_CTX, name); \
    block \
    if (rc != rc0) \
        ts_test_fail(TS_CTX, "%s -> %d[%s] (expected %d[%s])\n", \
            name, rc, al_error(rc), rc0, al_error(rc0))

#define CHECKLEN(name, al) \
    do { \
        size_t good, bad; \
        if (checklen(al, &good, &bad)) \
            ts_test_fail(TS_CTX, "%s -> corrupted length %d (expected %d)\n", \
                name, good, bad); \
    } while (0)

#define EVAL0(name,block) EVAL(name,rc,AL_OK,block)
#define EVAL1(name,al,block) EVAL(name,rc,AL_OK,block); CHECKLEN(name, al);

#define CHECKLEN1(name, al, n) \
    do { \
        CHECKLEN(name, al);  \
        if (al_bytes(al) != (n)) \
            ts_test_fail(TS_CTX, "%s -> invalid length %d (expected %d)\n", \
                al_bytes(al), (n)); \
    } while (0)

static int checklen(al_t *al, size_t *goodp, size_t *badp)
{
    al_rc_t rc;
    al_tx_t *tx;
    al_chunk_t *cur;
    size_t total, total2;

    total = 0;

    rc = al_txalloc(al, &tx);
    if (rc != AL_OK)
        return -1;

    rc = al_traverse(al, 0, al_bytes(al), AL_FORWARD, NULL, tx);
    if (rc != AL_OK) {
        al_txfree(al, tx);
        return -1;
    }

    while (al_traverse_next(al, tx, &cur) == AL_OK)
        total += al_chunk_len(cur);

    al_traverse_end(al, tx, 1);
    al_txfree(al, tx);

    total2 = al_bytes(al);

    if (total != total2) {
        *goodp = total;
        *badp  = total2;
        return -1;
    }

    return 0;
}


/* test: data copy */
TS_TEST(test_al_data)
{
    al_rc_t rc;
    al_t *al;

    EVAL0("al_create", {
        rc = al_create(&al);
    });
    if (rc != AL_OK) return;

    EVAL1("al_append_bytes", al, {
        rc = al_append_bytes(al, S("Hello world\n"), NULL);
    });

    EVAL0("al_destroy", {
        rc = al_destroy(al);
    });
}

/* test: splicing */
TS_TEST(test_al_splice)
{
    al_rc_t rc;
    al_t *src, *ins, *dst;
    int i;

    EVAL0("al_create(&src)", {
        rc = al_create(&src);
    });
    if (rc != AL_OK) return;
    EVAL0("al_create(&ins)", {
        rc = al_create(&ins);
    });
    if (rc != AL_OK) return;
    EVAL0("al_create(&dst)", {
        rc = al_create(&dst);
    });
    if (rc != AL_OK) return;

    for (i=0; i<500; ++i) {
        EVAL1("al_append_bytes(&src)", src, {
            rc = al_append_bytes(src, S("Huhu world\n"), LABEL);
        });
    }

    EVAL1("al_append_bytes(src)", src, {
        rc = al_append_bytes(src, S("Goodbye world\n"), LABEL);
    });
    EVAL1("al_append_bytes(src)", src, {
        rc = al_append_bytes(src, S("Goodbye world\n"), LABEL2);
    });
    EVAL1("al_prepend_byts(src)", src, {
        rc = al_prepend_bytes(src, S("Hello world\n"), LABEL2);
    });
    EVAL1("al_prepend_bytes(src)", src, {
        rc = al_prepend_bytes(src, S("Hello world\n"), LABEL);

    });

    EVAL1("al_prepend_bytes(ins)", ins, {
        rc = al_prepend_bytes(ins, S("KICK "), LABEL3);
    });
    EVAL1("al_append_bytes(ins)", ins, {
        rc = al_append_bytes(ins, S("ME\n"), LABEL3);
    });

    EVAL0("al_splice", {
        rc = al_splice(src, 4, 80, ins, dst);
    });
    CHECKLEN1("al_splice(src)",src, 5480);
    CHECKLEN1("al_splice(ins)",ins, 0);
    CHECKLEN1("al_splice(dst)",dst, 80);

    EVAL0("al_destroy(dst)", {
        rc = al_destroy(dst);
    });
    EVAL0("al_destroy(ins)", {
        rc = al_destroy(ins);
    });
    EVAL0("al_destroy(src)", {
        rc = al_destroy(src);
    });
}

/* test: labelling */
TS_TEST(test_al_label)
{
    al_rc_t rc;
    al_t *al;
    al_tx_t *tx;
    al_chunk_t *cur;
    int i, k;
    static struct { size_t len; int ln; } chunks[] = {
        { 5,  1 },
        { 2,  2 },
        { 2,  3 },
        { 5,  2 },
        { 14, 1 },
        { 0,  0 }
    };
    static al_label_t labels[] = {
        NULL,
        LABEL,
        LABEL2,
        LABEL3
    };

    EVAL0("al_create", {
        rc = al_create(&al);
    });
    if (rc != AL_OK) return;

    EVAL0("al_append_bytes", {
        rc = al_append_bytes(al, S("Hello world. Goodbye world.\n"), LABEL);
    });

    /*
     * 28 chars LABEL
     */

    EVAL1("al_setlabel", al, {
        rc = al_setlabel(al, 7, 2, NULL, LABEL3);
    });

    /*
     * 7  chars LABEL
     * 2  chars LABEL3
     * 19 chars LABEL
     */

    EVAL1("al_setlabel", al, {
        rc = al_setlabel(al, 5, 9, LABEL, LABEL2);
    });

    /*
     * 5  chars LABEL
     * 2  chars LABEL2
     * 2  chars LABEL3
     * 5  chars LABEL2
     * 14 chars LABEL
     */

    EVAL0("al_txalloc", {
        rc = al_txalloc(al, &tx);
    });

    EVAL0("al_traverse", {
        rc = al_traverse(al, 0, al_bytes(al), AL_FORWARD, NULL, tx);
    });

    i = 0;
    ts_test_check(TS_CTX, "al_traverse_next");
    while (al_traverse_next(al, tx, &cur) == AL_OK) {
        size_t n = al_chunk_len(cur);
        al_label_t l = al_chunk_label(cur);

        for (k=3; k>0; --k)
            if (labels[k] == l)
                break;

        if (chunks[i].len == 0) {
            ts_test_fail(TS_CTX,
                "al_traverse_next: found chunk %d[L%d] (none expected)\n",
                n, k);
            continue;
        }

        if (n != chunks[i].len || k != chunks[i].ln) {
            ts_test_fail(TS_CTX,
                "al_traverse_next: found chunk %d[L%d] (expected %d[L%d])\n",
                n, k, chunks[i].len, chunks[i].ln);
        }
        ++i;
    }

    EVAL0("al_traverse_end", {
        rc = al_traverse_end(al, tx, 1);
    });

    EVAL0("al_txfree", {
        rc = al_txfree(al, tx);
    });

    EVAL0("al_destroy", {
        rc = al_destroy(al);
    });
}

/* test: attach */
static char *reclaim_ptr = NULL;
static size_t reclaim_size = 0;
static void *reclaim_u = NULL;
static int reclaim_count = 0;
static void reclaim(char *p, size_t n, void *u)
{
    ++reclaim_count;

    if (reclaim_ptr != NULL ||
        reclaim_size != 0 ||
        reclaim_u != NULL)
        return;

    reclaim_ptr  = p;
    reclaim_size = n;
    reclaim_u    = u;
}
static const char *reclaim_result(const char *p, size_t n, void *u)
{
    if (reclaim_count == 0)
        return "reclaim never called";
    if (reclaim_count > 1)
        return "repeated call";

    if (p != reclaim_ptr ||
        n != reclaim_size ||
        u != reclaim_u)
        return "bad data for free";

    return NULL;
}

TS_TEST(test_al_attach)
{
    al_rc_t rc;
    al_t *al;
    char baf[] = "Mittendrin\n";
    char *context = "CONTEXT";
    const char *mess;

    EVAL0("al_create", {
        rc = al_create(&al);
    });
    if (rc != AL_OK) return;

    EVAL1("al_append_bytes", al, {
        rc = al_append_bytes(al, S("Hello world\n"), NULL);
    });
    EVAL1("al_attach_buffer", al, {
        rc = al_attach_buffer(al, S(baf), NULL, reclaim, context);
    });
    EVAL1("al_append_bytes", al, {
        rc = al_append_bytes(al, S("Goodbye world\n"), NULL);
    });
    EVAL0("al_destroy", {
        rc = al_destroy(al);
    });

    mess = reclaim_result(S(baf), context);
    if (mess != NULL)
        ts_test_fail(TS_CTX,"buffer reclaim failed: %s\n",mess);
}

/* test: exception handling */
#ifdef WITH_EX
#include "ex.h"
TS_TEST(test_al_ex)
{
    volatile al_t *al;
    ex_t ex;
    int caught;

    ts_test_check(TS_CTX, "exception handling");
    caught = 0;
    al = NULL;
    ex_try {
        al_create(&al);
        al_append_bytes(al, S("DUMMYDUMMY"), NULL);
        al_splice(al, 50, 1, NULL, NULL);
    }
    ex_cleanup {
        if (al != NULL) al_destroy(al);
    }
    ex_catch (ex) {
        if ((al_rc_t)ex.ex_value != AL_ERR_EOF)
            ts_test_fail(TS_CTX, "unexpected exception: %d\n", (al_rc_t)ex.ex_value);
        caught = 1;
    }
    if (!caught)
        ts_test_fail(TS_CTX, "expected exception not caught\n");
}
#endif

int main(int argc, char *argv[])
{
    ts_suite_t *ts;
    int n;

    ts = ts_suite_new("OSSP al (Assembly Line)");
    ts_suite_test(ts, test_al_data,   "assembly line data copying");
    ts_suite_test(ts, test_al_splice, "assembly line splicing");
    ts_suite_test(ts, test_al_label,  "assembly line labelling");
    ts_suite_test(ts, test_al_attach, "assembly line buffer attach");
#ifdef WITH_EX
    ts_suite_test(ts, test_al_ex,     "exception handling");
#endif
    n = ts_suite_run(ts);
    ts_suite_free(ts);
    return n;
}

