/* vector.c ..... store a vector of PPTP_CALL information and search it
 *                efficiently.
 *                C. Scott Ananian <cananian@alumni.princeton.edu>
 *
 * $Id: vector.c,v 1.4 2011/12/19 07:15:03 quozl Exp $
 */

#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "pptp_ctrl.h"
#include "vector.h"
/* #define VECTOR_DEBUG */
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif

struct vector_item {
    int key;
    PPTP_CALL *call;
};

struct vector_struct {
    struct vector_item *item;
    int size;
    int alloc;
#ifdef VECTOR_DEBUG
    int key_max;
#endif
};

static struct vector_item *binary_search(VECTOR *v, int key);

/*** vector_create ************************************************************/
VECTOR *vector_create(void)
{
    const int INITIAL_SIZE = 4;

    VECTOR *v = malloc(sizeof(*v));
    if (v == NULL) return v;

    v->size = 0;
    v->alloc = INITIAL_SIZE;
    v->item = malloc(sizeof(*(v->item)) * (v->alloc));
#ifdef VECTOR_DEBUG
    v->key_max = -1;
#endif
    if (v->item == NULL) { free(v); return NULL; }
    else return v;
}

/*** vector_destroy ***********************************************************/
void vector_destroy(VECTOR *v)
{
    free(v->item);
#ifdef VECTOR_DEBUG
    v->item = NULL;
#endif
    free(v);
}

/*** vector_size **************************************************************/
int vector_size(VECTOR *v)
{
    assert(v != NULL);
    return v->size;
}

/*** vector_insert*************************************************************
 * nice thing about file descriptors is that we are assured by POSIX 
 * that they are monotonically increasing.
 */
int vector_insert(VECTOR *v, int key, PPTP_CALL * call)
{
    int i;
    assert(v != NULL && call != NULL);
    assert(!vector_contains(v, key));
#ifdef VECTOR_DEBUG
    assert(v->key_max < key);
#endif
    if (!(v->size < v->alloc)) {
        void *tmp = realloc(v->item, sizeof(*(v->item)) * 2 * v->alloc);
        if (tmp != NULL) {
            v->alloc *= 2;
            v->item = tmp;
        } else return FALSE; /* failed to alloc memory. */
    }
    assert(v->size < v->alloc);
    /* for safety, we make this work in the general case;
     * but this is optimized for adding call to the end of the vector.
     */
    for(i = v->size - 1; i >= 0; i--)
        if (v->item[i].key < key)
            break;
    /* insert after item i */
    memmove(&v->item[i + 2], &v->item[i + 1],
            (v->size - i - 1) * sizeof(*(v->item)));
    v->item[i + 1].key  = key;
    v->item[i + 1].call = call;
    v->size++;
#ifdef VECTOR_DEBUG
    if (v->key_max < key) /* ie, always. */
        v->key_max = key;
#endif
    return TRUE;
}

/*** vector_remove ************************************************************/
int  vector_remove(VECTOR *v, int key)
{
    struct vector_item *tmp;
    assert(v != NULL);
    if ((tmp =binary_search(v,key)) == NULL) return FALSE;
    assert(tmp >= v->item && tmp < v->item + v->size);
    memmove(tmp, tmp + 1, (v->size - (tmp - v->item) - 1) * sizeof(*(v->item)));
    v->size--;
    return TRUE;
}

/*** vector_search ************************************************************/
int  vector_search(VECTOR *v, int key, PPTP_CALL **call)
{
    struct vector_item *tmp;
    assert(v != NULL);
    tmp = binary_search(v, key);
    if (tmp ==NULL) return FALSE;
    *call = tmp->call;
    return TRUE;
}

/*** vector_contains **********************************************************/
int  vector_contains(VECTOR *v, int key)
{
    assert(v != NULL);
    return (binary_search(v, key) != NULL);
}

/*** vector_item **************************************************************/
static struct vector_item *binary_search(VECTOR *v, int key)
{
    int l,r,x;
    l = 0;
    r = v->size - 1;
    while (r >= l) {
        x = (l + r)/2;
        if (key <  v->item[x].key) r = x - 1; else l = x + 1;
        if (key == v->item[x].key) return &(v->item[x]);
    }
    return NULL;
}

/*** vector_scan ***************************************************************
 * Hmm.  Let's be fancy and use a binary search for the first
 * unused key, taking advantage of the list is stored sorted; ie
 * we can look at pointers and keys at two different locations, 
 * and if (ptr1 - ptr2) = (key1 - key2) then all the slots
 * between ptr1 and ptr2 are filled.  Note that ptr1-ptr2 should
 * never be greater than key1-key2 (no duplicate keys!)... we
 * check for this.
 */
int vector_scan(VECTOR *v, int lo, int hi, int *key)
{
    int l,r,x;
    assert(v != NULL);
    assert(key != NULL);
    if ((v->size<1) || (lo < v->item[0].key)) { *key = lo; return TRUE; }
    /* our array bounds */
    l = 0;  r = v->size - 1;
    while (r > l) {
        /* check for a free spot right after l */
        if (v->item[l].key + 1 < v->item[l + 1].key) { /* found it! */
            *key = v->item[l].key + 1;
            return TRUE;
        }
        /* no dice. Let's see if the free spot is before or after the midpoint */
        x = (l + r)/2;
        /* Okay, we have right (r), left (l) and the probe (x). */
        assert(x - l <= v->item[x].key - v->item[l].key);
        assert(r - x <= v->item[r].key - v->item[x].key);
        if (x - l < v->item[x].key - v->item[l].key)
            /* room between l and x */
            r = x;
        else /* no room between l and x */
            if (r - x < v->item[r].key - v->item[x].key)
                /* room between x and r */
                l = x;
            else /* no room between x and r, either */
                break; /* game over, man. */
    }
    /* no room found in already allocated space.  Check to see if
     * there's free space above allocated entries. */
    if (v->item[v->size - 1].key < hi) {
        *key = v->item[v->size - 1].key + 1;
        return TRUE;
    }
    /* outta luck */
    return FALSE;
}

/*** vector_get_Nth ***********************************************************/
PPTP_CALL * vector_get_Nth(VECTOR *v, int n)
{
    assert(v != NULL);
    assert(0 <= n && n < vector_size(v));
    return v->item[n].call;
}
