/*
 * node.c: implementation of automata node
 * This file is part of multifast.
 *
 Copyright 2010-2012 Kamiar Kanani <kamiar.kanani@gmail.com>

 multifast is free software: you can redistribute it and/or modify
 it under the terms of the GNU Lesser General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 multifast is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public License
 along with multifast.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "ndpi_api.h"
#include "../include/node.h"
#include "sort.h"

/* reallocation step for AC_NODE_t.matched_patterns */
#define REALLOC_CHUNK_MATCHSTR 8

/* reallocation step for AC_NODE_t.outgoing array */
#define REALLOC_CHUNK_OUTGOING 8
/* TODO: For different depth of node, number of outgoing edges differs
   considerably, It is efficient to use different chunk size for 
   different depths */

/* Private function prototype */
void node_init         (AC_NODE_t * thiz);
int  node_edge_compare (const void * l, const void * r);
int  node_has_matchstr (AC_NODE_t * thiz, AC_PATTERN_t * newstr);


/******************************************************************************
 * FUNCTION: node_create
 * Create the node
 ******************************************************************************/
AC_NODE_t * node_create(void)
{
    AC_NODE_t * thiz =  (AC_NODE_t *) ndpi_malloc (sizeof(AC_NODE_t));
    node_init(thiz);
    node_assign_id(thiz);
    return thiz;
}

/******************************************************************************
 * FUNCTION: node_init
 * Initialize node
 ******************************************************************************/
void node_init(AC_NODE_t * thiz)
{
    memset(thiz, 0, sizeof(AC_NODE_t));

    thiz->outgoing_max = REALLOC_CHUNK_OUTGOING;
    thiz->outgoing = (struct edge *) ndpi_malloc
        (thiz->outgoing_max*sizeof(struct edge));

    thiz->matched_patterns_max = REALLOC_CHUNK_MATCHSTR;
    thiz->matched_patterns = (AC_PATTERN_t *) ndpi_malloc
        (thiz->matched_patterns_max*sizeof(AC_PATTERN_t));
}

/******************************************************************************
 * FUNCTION: node_release
 * Release node
 ******************************************************************************/
void node_release(AC_NODE_t * thiz)
{
    ndpi_free(thiz->matched_patterns);
    ndpi_free(thiz->outgoing);
    ndpi_free(thiz);
}

/******************************************************************************
 * FUNCTION: node_find_next
 * Find out the next node for a given Alpha to move. this function is used in
 * the pre-processing stage in which edge array is not sorted. so it uses
 * linear search.
 ******************************************************************************/
AC_NODE_t * node_find_next(AC_NODE_t * thiz, AC_ALPHABET_t alpha)
{
    int i;

    for (i=0; i < thiz->outgoing_degree; i++)
    {
        if(thiz->outgoing[i].alpha == alpha)
            return (thiz->outgoing[i].next);
    }
    return NULL;
}

/******************************************************************************
 * FUNCTION: node_findbs_next
 * Find out the next node for a given Alpha. this function is used after the
 * pre-processing stage in which we sort edges. so it uses Binary Search.
 ******************************************************************************/
AC_NODE_t * node_findbs_next (AC_NODE_t * thiz, AC_ALPHABET_t alpha)
{
    int min, max, mid;
    AC_ALPHABET_t amid;

    min = 0;
    max = thiz->outgoing_degree - 1;

    while (min <= max)
    {
        mid = (min+max) >> 1;
        amid = thiz->outgoing[mid].alpha;
        if (alpha > amid)
            min = mid + 1;
        else if (alpha < amid)
            max = mid - 1;
        else
            return (thiz->outgoing[mid].next);
    }
    return NULL;
}

/******************************************************************************
 * FUNCTION: node_has_matchstr
 * Determine if a final node contains a pattern in its accepted pattern list
 * or not. return values: 1 = it has, 0 = it hasn't
 ******************************************************************************/
int node_has_matchstr (AC_NODE_t * thiz, AC_PATTERN_t * newstr)
{
    int i, j;
    AC_PATTERN_t * str;

    for (i=0; i < thiz->matched_patterns_num; i++)
    {
        str = &thiz->matched_patterns[i];

        if (str->length != newstr->length)
            continue;

        for (j=0; j<(int)str->length; j++)
            if(str->astring[j] != newstr->astring[j])
                continue;

        if (j == str->length)
            return 1;
    }
    return 0;
}

/******************************************************************************
 * FUNCTION: node_create_next
 * Create the next node for the given alpha.
 ******************************************************************************/
AC_NODE_t * node_create_next (AC_NODE_t * thiz, AC_ALPHABET_t alpha)
{
    AC_NODE_t * next;
    next = node_find_next (thiz, alpha);
    if (next)
        /* The edge already exists */
        return NULL;
    /* Otherwise register new edge */
    next = node_create ();
    node_register_outgoing(thiz, next, alpha);

    return next;
}

/******************************************************************************
 * FUNCTION: node_register_matchstr
 * Adds the pattern to the list of accepted pattern.
 ******************************************************************************/
void node_register_matchstr (AC_NODE_t * thiz, AC_PATTERN_t * str)
{
    /* Check if the new pattern already exists in the node list */
    if (node_has_matchstr(thiz, str))
        return;

    /* Manage memory */
    if (thiz->matched_patterns_num >= thiz->matched_patterns_max)
    {
        thiz->matched_patterns = (AC_PATTERN_t *) ndpi_realloc 
            (thiz->matched_patterns, thiz->matched_patterns_max*sizeof(AC_PATTERN_t),
             (REALLOC_CHUNK_MATCHSTR+thiz->matched_patterns_max)*sizeof(AC_PATTERN_t));

        thiz->matched_patterns_max += REALLOC_CHUNK_MATCHSTR;
    }

    thiz->matched_patterns[thiz->matched_patterns_num].astring = str->astring;
    thiz->matched_patterns[thiz->matched_patterns_num].length = str->length;
    memcpy(&thiz->matched_patterns[thiz->matched_patterns_num].rep, &str->rep, sizeof(AC_REP_t));
    thiz->matched_patterns_num++;
}

/******************************************************************************
 * FUNCTION: node_register_outgoing
 * Establish an edge between two nodes
 ******************************************************************************/
void node_register_outgoing
(AC_NODE_t * thiz, AC_NODE_t * next, AC_ALPHABET_t alpha)
{
    if(thiz->outgoing_degree >= thiz->outgoing_max)
    {
        thiz->outgoing = (struct edge *) ndpi_realloc 
            (thiz->outgoing, thiz->outgoing_max*sizeof(struct edge),
             (REALLOC_CHUNK_OUTGOING+thiz->outgoing_max)*sizeof(struct edge));
        thiz->outgoing_max += REALLOC_CHUNK_OUTGOING;
    }

    thiz->outgoing[thiz->outgoing_degree].alpha = alpha;
    thiz->outgoing[thiz->outgoing_degree++].next = next;
}

/******************************************************************************
 * FUNCTION: node_assign_id
 * assign a unique ID to the node (used for debugging purpose).
 ******************************************************************************/
void node_assign_id (AC_NODE_t * thiz)
{
    static int unique_id = 1;
    thiz->id = unique_id ++;
}

/******************************************************************************
 * FUNCTION: node_edge_compare
 * Comparison function for qsort. see man qsort.
 ******************************************************************************/
int node_edge_compare (const void * l, const void * r)
{
    /* According to man page:
     * The comparison function must return an integer less than, equal to, or
     * greater than zero if the first argument is considered to be
     * respectively less than, equal to, or greater than the second. if  two
     * members compare as equal, their order in the sorted array is undefined.
     *
     * NOTE: Because edge alphabets are unique in every node we ignore
     * equivalence case.
     **/
    if ( ((struct edge *)l)->alpha >= ((struct edge *)r)->alpha )
        return 1;
    else
        return -1;
}

/******************************************************************************
 * FUNCTION: node_sort_edges
 * sorts edges alphabets.
 ******************************************************************************/
void node_sort_edges (AC_NODE_t * thiz)
{
    sort ((void *)thiz->outgoing, thiz->outgoing_degree, sizeof(struct edge), node_edge_compare, NULL);
}
