/*
 * Copyright (c) 2015-2016, Intel Corporation
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  * 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.
 *  * Neither the name of Intel Corporation nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 THE COPYRIGHT OWNER 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.
 */

#ifndef NFA_API_QUEUE_H
#define NFA_API_QUEUE_H

#ifdef __cplusplus
extern "C"
{
#endif

#include "ue2common.h"
#include "callback.h"

/** Size of mq::items, max elements on a queue. */
#define MAX_MQE_LEN 10

/** Queue events */

/** Queue event: begin scanning. Note: stateless engines will start from this
 * location. */
#define MQE_START 0U

/** Queue event: stop scanning. */
#define MQE_END 1U

/** Queue event: enable start and start-dot-star. */
#define MQE_TOP 2U

/** Queue event: first event corresponding to a numbered TOP. Additional tops
 * (in multi-top engines) use the event values from MQE_TOP_FIRST to
 * MQE_INVALID - 1. */
#define MQE_TOP_FIRST 4U

/** Invalid queue event */
#define MQE_INVALID (~0U)

/** Queue item */
struct mq_item {
    u32 type; /**< event type, from MQE_* */
    s64a location; /**< relative to the start of the current buffer */
    u64a som; /**< pattern start-of-match corresponding to a top, only used
               * by som engines. */
};

// Forward decl.
struct NFA;

/**
 * Queue of events to control engine execution.  mq::cur is index of first
 * valid event, mq::end is one past the index of last valid event.
 */
struct mq {
    const struct NFA *nfa; /**< nfa corresponding to the queue */
    u32 cur; /**< index of the first valid item in the queue */
    u32 end; /**< index one past the last valid item in the queue */
    char *state; /**< uncompressed stream state; lives in scratch */
    char *streamState; /**<
                        * real stream state; used to access structures which
                        * not duplicated the scratch state (bounded repeats,
                        * etc) */
    u64a offset; /**< base offset of the buffer */
    const u8 *buffer; /**< buffer to scan */
    size_t length; /**< length of buffer */
    const u8 *history; /**<
                        * history buffer; (logically) immediately before the
                        * main buffer */
    size_t hlength; /**< length of the history buffer */
    struct hs_scratch *scratch; /**< global scratch space */
    char report_current; /**<
                          * report_current matches at starting offset through
                          * callback. If true, the queue must be located at a
                          * point where MO_MATCHES_PENDING was returned */
    NfaCallback cb; /**< callback to trigger on matches */
    void *context; /**< context to pass along with a callback */
    struct mq_item items[MAX_MQE_LEN]; /**< queue items */
};


/**
 * Pushes an (event, location, som) item onto a queue. If it is identical to the
 * previous item on the queue, it is not added to the queue.
 * @param q queue
 * @param e event
 * @param som som marker
 * @param loc event location
 */
static really_inline
void pushQueueSom(struct mq * restrict q, u32 e, s64a loc, u64a som) {
    DEBUG_PRINTF("pushing %u@%lld -> %u [som = %llu]\n", e, loc, q->end, som);
    assert(q->end < MAX_MQE_LEN);
    assert(e < MQE_INVALID);
/* stop gcc getting too smart for its own good */
/*     assert(!q->end || q->items[q->end - 1].location <= loc); */
    assert(q->end || e == MQE_START);

    // Avoid duplicate items on the queue.
    if (q->end) {
        struct mq_item *item = &q->items[q->end - 1];
        if (item->type == e && item->location == loc) {
            DEBUG_PRINTF("dropping duplicate item\n");
            LIMIT_TO_AT_MOST(&item->som, som); /* take lower som */
            return;
        }
    }

    u32 end = q->end;
    struct mq_item *item = &q->items[end];
    item->type = e;
    item->location = loc;
    item->som = som;
    q->end = end + 1;
}

/**
 * Pushes an (event, location) item onto a queue. If it is identical to the
 * previous item on the queue, it is not added to the queue.
 * @param q queue
 * @param e event
 * @param loc event location
 */
static really_inline
void pushQueue(struct mq * restrict q, u32 e, s64a loc) {
    pushQueueSom(q, e, loc, 0);
}

/**
 * Pushes an (event, location) item onto a queue.
 * This version of @ref pushQueue does not check to ensure that the item being
 * added is not already on the queue. Used for events other than tops.
 */
static really_inline
void pushQueueNoMerge(struct mq * restrict q, u32 e, s64a loc) {
    DEBUG_PRINTF("pushing %u@%lld -> %u\n", e, loc, q->end);
    assert(q->end < MAX_MQE_LEN);
    assert(e < MQE_INVALID);
/* stop gcc getting too smart for its own good */
/*     assert(!q->end || q->items[q->end - 1].location <= loc); */
    assert(q->end || e == MQE_START);

#ifndef NDEBUG
    // We assert that the event is different from its predecessor. If it's a
    // dupe, you should have used the ordinary pushQueue call.
    if (q->end) {
        UNUSED struct mq_item *prev = &q->items[q->end - 1];
        assert(prev->type != e || prev->location != loc);
    }
#endif

    u32 end = q->end;
    struct mq_item *item = &q->items[end];
    item->type = e;
    item->location = loc;
    item->som = 0;
    q->end = end + 1;
}

/** \brief Returns the type of the current queue event. */
static really_inline u32 q_cur_type(const struct mq *q) {
    assert(q->cur < q->end);
    assert(q->cur < MAX_MQE_LEN);
    return q->items[q->cur].type;
}

/** \brief Returns the location (relative to the beginning of the current data
 * buffer) of the current queue event. */
static really_inline s64a q_cur_loc(const struct mq *q) {
    assert(q->cur < q->end);
    assert(q->cur < MAX_MQE_LEN);
    return q->items[q->cur].location;
}

/** \brief Returns the type of the last event in the queue. */
static really_inline u32 q_last_type(const struct mq *q) {
    assert(q->cur < q->end);
    assert(q->end > 0);
    assert(q->end <= MAX_MQE_LEN);
    return q->items[q->end - 1].type;
}

/** \brief Returns the location (relative to the beginning of the current data
 * buffer) of the last event in the queue. */
static really_inline s64a q_last_loc(const struct mq *q) {
    assert(q->cur < q->end);
    assert(q->end > 0);
    assert(q->end <= MAX_MQE_LEN);
    return q->items[q->end - 1].location;
}

/** \brief Returns the absolute stream offset of the current queue event. */
static really_inline u64a q_cur_offset(const struct mq *q) {
    assert(q->cur < q->end);
    assert(q->cur < MAX_MQE_LEN);
    return q->offset + (u64a)q->items[q->cur].location;
}

/**
 * \brief Removes all events in the queue before the given location.
 */
static really_inline
void q_skip_forward_to(struct mq *q, s64a min_loc) {
    assert(q->cur < q->end);
    assert(q->cur < MAX_MQE_LEN);
    assert(q->items[q->cur].type == MQE_START);

    if (q_cur_loc(q) >= min_loc) {
        DEBUG_PRINTF("all events >= loc %lld\n", min_loc);
        return;
    }

    const u32 start_loc = q->cur;

    do {
        DEBUG_PRINTF("remove item with loc=%lld\n", q_cur_loc(q));
        q->cur++;
    } while (q->cur < q->end && q_cur_loc(q) < min_loc);

    if (q->cur > start_loc) {
        // Move original MQE_START item forward.
        q->cur--;
        q->items[q->cur] = q->items[start_loc];
    }
}

#ifdef DEBUG
// Dump the contents of the given queue.
static never_inline UNUSED
void debugQueue(const struct mq *q) {
    DEBUG_PRINTF("q=%p, nfa=%p\n", q, q->nfa);
    DEBUG_PRINTF("q offset=%llu, buf={%p, len=%zu}, history={%p, len=%zu}\n",
                 q->offset, q->buffer, q->length, q->history, q->hlength);
    DEBUG_PRINTF("q cur=%u, end=%u\n", q->cur, q->end);
    for (u32 cur = q->cur; cur < q->end; cur++) {
        const char *type = "UNKNOWN";
        u32 e = q->items[cur].type;
        switch (e) {
        case MQE_START:
            type = "MQE_START";
            break;
        case MQE_END:
            type = "MQE_END";
            break;
        case MQE_TOP:
            type = "MQE_TOP";
            break;
        case MQE_INVALID:
            type = "MQE_INVALID";
            break;
        default:
            assert(e >= MQE_TOP_FIRST && e < MQE_INVALID);
            type = "MQE_TOP_N";
            break;
        }
        DEBUG_PRINTF("\tq[%u] %lld %u:%s\n", cur, q->items[cur].location,
                     q->items[cur].type, type);
    }
}
#endif // DEBUG

#ifdef __cplusplus
}
#endif

#endif