/*
 # This file is part of the Astrometry.net suite.
 # Licensed under a 3-clause BSD style license - see LICENSE
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>

#include "bl.h"

#include "keywords.h"

#include "bl.ph"
#include "log.h"

static bl_node* bl_new_node(bl* list);
static void bl_remove_from_node(bl* list, bl_node* node,
                                bl_node* prev, int index_in_node);

// Defined in bl.ph (private header):
// free_node
// NODE_DATA
// NODE_CHARDATA
// NODE_INTDATA
// NODE_DOUBLEDATA

// Defined in bl.inc (inlined functions):
// bl_size
// bl_access
// il_size
// il_get

// NOTE!, if you make changes here, also see bl-sort.c !

//#define DEFINE_SORT 1
#define DEFINE_SORT 0
#define nl il
#define number int
#define NL_PRINT(x) printf("%i", x)
#include "bl-nl.c"
#undef nl
#undef number
#undef NL_PRINT

#define nl ll
#define number int64_t
#define NL_PRINT(x) printf("%lli", (long long int)x)
#include "bl-nl.c"
#undef nl
#undef number
#undef NL_PRINT

#define nl fl
#define number float
#define NL_PRINT(x) printf("%f", (float)x)
#include "bl-nl.c"
#undef nl
#undef number
#undef NL_PRINT

#define nl dl
#define number double
#define NL_PRINT(x) printf("%g", x)
#include "bl-nl.c"
#undef nl
#undef number
#undef NL_PRINT

#undef DEFINE_SORT
#define DEFINE_SORT 0
#define nl pl
#define number void*
#define NL_PRINT(x) printf("%p", x)
#include "bl-nl.c"
#undef nl
#undef number
#undef NL_PRINT
#undef DEFINE_SORT



Pure int bl_datasize(const bl* list) {
    if (!list)
        return 0;
    return list->datasize;
}


void bl_split(bl* src, bl* dest, size_t split) {
    bl_node* node;
    size_t nskipped;
    size_t ind;
    size_t ntaken = src->N - split;
    node = find_node(src, split, &nskipped);
    ind = split - nskipped;
    if (ind == 0) {
        // this whole node belongs to "dest".
        if (split) {
            // we need to get the previous node...
            bl_node* last = find_node(src, split-1, NULL);
            last->next = NULL;
            src->tail = last;
        } else {
            // we've removed everything from "src".
            src->head = NULL;
            src->tail = NULL;
        }
    } else {
        // create a new node to hold the second half of the items in "node".
        bl_node* newnode = bl_new_node(dest);
        newnode->N = (node->N - ind);
        newnode->next = node->next;
        memcpy(NODE_CHARDATA(newnode),
               NODE_CHARDATA(node) + (ind * src->datasize),
               newnode->N * src->datasize);
        node->N -= (node->N - ind);
        node->next = NULL;
        src->tail = node;
        // to make the code outside this block work...
        node = newnode;
    }

    // append it to "dest".
    if (dest->tail) {
        dest->tail->next = node;
        dest->N += ntaken;
    } else {
        dest->head = node;
        dest->tail = node;
        dest->N += ntaken;
    }

    // adjust "src".
    src->N -= ntaken;
    src->last_access = NULL;
}

void bl_init(bl* list, int blocksize, int datasize) {
    list->head = NULL;
    list->tail = NULL;
    list->N = 0;
    list->blocksize = blocksize;
    list->datasize  = datasize;
    list->last_access = NULL;
    list->last_access_n = 0;
}

bl* bl_new(int blocksize, int datasize) {
    bl* rtn;
    rtn = malloc(sizeof(bl));
    if (!rtn) {
        printf("Couldn't allocate memory for a bl.\n");
        return NULL;
    }
    bl_init(rtn, blocksize, datasize);
    return rtn;
}

void bl_free(bl* list) {
    if (!list) return;
    bl_remove_all(list);
    free(list);
}

void bl_remove_all(bl* list) {
    bl_node *n, *lastnode;
    lastnode = NULL;
    for (n=list->head; n; n=n->next) {
        if (lastnode)
            bl_free_node(lastnode);
        lastnode = n;
    }
    if (lastnode)
        bl_free_node(lastnode);
    list->head = NULL;
    list->tail = NULL;
    list->N = 0;
    list->last_access = NULL;
    list->last_access_n = 0;
}

void bl_remove_all_but_first(bl* list) {
    bl_node *n, *lastnode;
    lastnode = NULL;

    if (list->head) {
        for (n=list->head->next; n; n=n->next) {
            if (lastnode)
                bl_free_node(lastnode);
            lastnode = n;
        }
        if (lastnode)
            bl_free_node(lastnode);
        list->head->next = NULL;
        list->head->N = 0;
        list->tail = list->head;
    } else {
        list->head = NULL;
        list->tail = NULL;
    }
    list->N = 0;
    list->last_access = NULL;
    list->last_access_n = 0;
}

static void bl_remove_from_node(bl* list, bl_node* node,
                                bl_node* prev, int index_in_node) {
    // if we're removing the last element at this node, then
    // remove this node from the linked list.
    if (node->N == 1) {
        // if we're removing the first node...
        if (prev == NULL) {
            list->head = node->next;
            // if it's the first and only node...
            if (list->head == NULL) {
                list->tail = NULL;
            }
        } else {
            // if we're removing the last element from
            // the tail node...
            if (node == list->tail) {
                list->tail = prev;
            }
            prev->next = node->next;
        }
        bl_free_node(node);
    } else {
        int ncopy;
        // just remove this element...
        ncopy = node->N - index_in_node - 1;
        if (ncopy > 0) {
            memmove(NODE_CHARDATA(node) + index_in_node * list->datasize,
                    NODE_CHARDATA(node) + (index_in_node+1) * list->datasize,
                    ncopy * list->datasize);
        }
        node->N--;
    }
    list->N--;
}

void bl_remove_index(bl* list, size_t index) {
    // find the node (and previous node) at which element 'index'
    // can be found.
    bl_node *node, *prev;
    size_t nskipped = 0;
    for (node=list->head, prev=NULL;
         node;
         prev=node, node=node->next) {

        if (index < (nskipped + node->N))
            break;
        nskipped += node->N;
    }
    assert(node);
    bl_remove_from_node(list, node, prev, index-nskipped);
    list->last_access = NULL;
    list->last_access_n = 0;
}

void bl_remove_index_range(bl* list, size_t start, size_t length) {
    // find the node (and previous node) at which element 'start'
    // can be found.
    bl_node *node, *prev;
    size_t nskipped = 0;
    list->last_access = NULL;
    list->last_access_n = 0;
    for (node=list->head, prev=NULL;
         node;
         prev=node, node=node->next) {

        if (start < (nskipped + node->N))
            break;

        nskipped += node->N;
    }

    // begin by removing any indices that are at the end of a block.
    if (start > nskipped) {
        // we're not removing everything at this node.
        size_t istart;
        size_t n;
        istart = start - nskipped;
        if ((istart + length) < node->N) {
            // we're removing a chunk of elements from the middle of this
            // block.  move elements from the end into the removed chunk.
            memmove(NODE_CHARDATA(node) + istart * list->datasize,
                    NODE_CHARDATA(node) + (istart + length) * list->datasize,
                    (node->N - (istart + length)) * list->datasize);
            // we're done!
            node->N -= length;
            list->N -= length;
            return;
        } else {
            // we're removing everything from 'istart' to the end of this
            // block.  just change the "N" values.
            n = (node->N - istart);
            node->N -= n;
            list->N -= n;
            length -= n;
            start += n;
            nskipped = start;
            prev = node;
            node = node->next;
        }
    }

    // remove complete blocks.
    for (;;) {
        size_t n;
        bl_node* todelete;
        if (length == 0 || length < node->N)
            break;
        // we're skipping this whole block.
        n = node->N;
        length -= n;
        start += n;
        list->N -= n;
        nskipped += n;
        todelete = node;
        node = node->next;
        bl_free_node(todelete);
    }
    if (prev)
        prev->next = node;
    else
        list->head = node;

    if (!node)
        list->tail = prev;

    // remove indices from the beginning of the last block.
    // note that we may have removed everything from the tail of the list,
    // no "node" may be null.
    if (node && length>0) {
        //printf("removing %i from end.\n", length);
        memmove(NODE_CHARDATA(node),
                NODE_CHARDATA(node) + length * list->datasize,
                (node->N - length) * list->datasize);
        node->N -= length;
        list->N -= length;
    }
}

static void clear_list(bl* list) {
    list->head = NULL;
    list->tail = NULL;
    list->N = 0;
    list->last_access = NULL;
    list->last_access_n = 0;
}

void bl_append_list(bl* list1, bl* list2) {
    list1->last_access = NULL;
    list1->last_access_n = 0;
    if (list1->datasize != list2->datasize) {
        printf("Error: cannot append bls with different data sizes!\n");
        assert(0);
        exit(0);
    }
    if (list1->blocksize != list2->blocksize) {
        printf("Error: cannot append bls with different block sizes!\n");
        assert(0);
        exit(0);
    }

    // if list1 is empty, then just copy over list2's head and tail.
    if (list1->head == NULL) {
        list1->head = list2->head;
        list1->tail = list2->tail;
        list1->N = list2->N;
        // remove everything from list2 (to avoid sharing nodes)
        clear_list(list2);
        return;
    }

    // if list2 is empty, then do nothing.
    if (list2->head == NULL)
        return;

    // otherwise, append list2's head to list1's tail.
    list1->tail->next = list2->head;
    list1->tail = list2->tail;
    list1->N += list2->N;
    // remove everything from list2 (to avoid sharing nodes)
    clear_list(list2);
}

static bl_node* bl_new_node(bl* list) {
    bl_node* rtn;
    // merge the mallocs for the node and its data into one malloc.
    rtn = malloc(sizeof(bl_node) + list->datasize * list->blocksize);
    if (!rtn) {
        printf("Couldn't allocate memory for a bl node!\n");
        return NULL;
    }
    //rtn->data = (char*)rtn + sizeof(bl_node);
    rtn->N = 0;
    rtn->next = NULL;
    return rtn;
}

static void bl_append_node(bl* list, bl_node* node) {
    node->next = NULL;
    if (!list->head) {
        // first node to be added.
        list->head = node;
        list->tail = node;
    } else {
        list->tail->next = node;
        list->tail = node;
    }
    list->N += node->N;
}

/*
 * Append an item to this bl node.  If this node is full, then create a new
 * node and insert it into the list.
 *
 * Returns the location where the new item was copied.
 */
void* bl_node_append(bl* list, bl_node* node, const void* data) {
    void* dest;
    if (node->N == list->blocksize) {
        // create a new node and insert it after the current node.
        bl_node* newnode;
        newnode = bl_new_node(list);
        newnode->next = node->next;
        node->next = newnode;
        if (list->tail == node)
            list->tail = newnode;
        node = newnode;
    }
    // space remains at this node.  add item.
    dest = NODE_CHARDATA(node) + node->N * list->datasize;
    if (data)
        memcpy(dest, data, list->datasize);
    node->N++;
    list->N++;
    return dest;
}

void* bl_append(bl* list, const void* data) {
    if (!list->tail)
        // empty list; create a new node.
        bl_append_node(list, bl_new_node(list));
    // append the item to the tail.  if the tail node is full, a new tail node may be created.
    return bl_node_append(list, list->tail, data);
}

void* bl_push(bl* list, const void* data) {
    return bl_append(list, data);
}

void bl_pop(bl* list, void* into) {
    assert(list->N > 0);
    bl_get(list, list->N-1, into);
    bl_remove_index(list, list->N-1);
}

void bl_print_structure(bl* list) {
    bl_node* n;
    printf("bl: head %p, tail %p, N %zu\n", list->head, list->tail, list->N);
    for (n=list->head; n; n=n->next) {
        printf("[N=%i] ", n->N);
    }
    printf("\n");
}

void bl_get(bl* list, size_t n, void* dest) {
    assert(list->N > 0);
    char* src = bl_access(list, n);
    memcpy(dest, src, list->datasize);
}

static void bl_find_ind_and_element(bl* list, const void* data,
                                    int (*compare)(const void* v1, const void* v2),
                                    void** presult, ptrdiff_t* pindex) {
    ptrdiff_t lower, upper;
    int cmp = -2;
    void* result;
    lower = -1;
    upper = list->N;
    while (lower < (upper-1)) {
        ptrdiff_t mid;
        mid = (upper + lower) / 2;
        cmp = compare(data, bl_access(list, mid));
        if (cmp >= 0) {
            lower = mid;
        } else {
            upper = mid;
        }
    }
    if (lower == -1 || compare(data, (result = bl_access(list, lower)))) {
        *presult = NULL;
        if (pindex)
            *pindex = -1;
        return;
    }
    *presult = result;
    if (pindex)
        *pindex = lower;
}

/**
 * Finds a node for which the given compare() function
 * returns zero when passed the given 'data' pointer
 * and elements from the list.
 */
void* bl_find(bl* list, const void* data,
              int (*compare)(const void* v1, const void* v2)) {
    void* rtn;
    bl_find_ind_and_element(list, data, compare, &rtn, NULL);
    return rtn;
}

ptrdiff_t bl_find_index(bl* list, const void* data,
                        int (*compare)(const void* v1, const void* v2)) {
    void* val;
    ptrdiff_t ind;
    bl_find_ind_and_element(list, data, compare, &val, &ind);
    return ind;
}

size_t bl_insert_sorted(bl* list, const void* data,
                        int (*compare)(const void* v1, const void* v2)) {
    ptrdiff_t lower, upper;
    lower = -1;
    upper = list->N;
    while (lower < (upper-1)) {
        ptrdiff_t mid;
        int cmp;
        mid = (upper + lower) / 2;
        cmp = compare(data, bl_access(list, mid));
        if (cmp >= 0) {
            lower = mid;
        } else {
            upper = mid;
        }
    }
    bl_insert(list, lower+1, data);
    return lower+1;
}

ptrdiff_t bl_insert_unique_sorted(bl* list, const void* data,
                                  int (*compare)(const void* v1, const void* v2)) {
    // This is just straightforward binary search - really should
    // use the block structure...
    ptrdiff_t lower, upper;
    lower = -1;
    upper = list->N;
    while (lower < (upper-1)) {
        ptrdiff_t mid;
        int cmp;
        mid = (upper + lower) / 2;
        cmp = compare(data, bl_access(list, mid));
        if (cmp >= 0) {
            lower = mid;
        } else {
            upper = mid;
        }
    }

    if (lower >= 0) {
        if (compare(data, bl_access(list, lower)) == 0) {
            return BL_NOT_FOUND;
        }
    }
    bl_insert(list, lower+1, data);
    return lower+1;
}

void bl_set(bl* list, size_t index, const void* data) {
    bl_node* node;
    size_t nskipped;
    void* dataloc;

    node = find_node(list, index, &nskipped);
    dataloc = NODE_CHARDATA(node) + (index - nskipped) * list->datasize;
    memcpy(dataloc, data, list->datasize);
    // update the last_access member...
    list->last_access = node;
    list->last_access_n = nskipped;
}

/**
 * Insert the element "data" into the list, such that its index is "index".
 * All elements that previously had indices "index" and above are moved
 * one position to the right.
 */
void bl_insert(bl* list, size_t index, const void* data) {
    bl_node* node;
    size_t nskipped;

    if (list->N == index) {
        bl_append(list, data);
        return;
    }

    node = find_node(list, index, &nskipped);

    list->last_access = node;
    list->last_access_n = nskipped;

    // if the node is full:
    //   if we're inserting at the end of this node, then create a new node.
    //   else, shift all but the last element, add in this element, and 
    //     add the last element to a new node.
    if (node->N == list->blocksize) {
        int localindex, nshift;
        bl_node* next = node->next;
        bl_node* destnode;
        localindex = index - nskipped;

        // if the next node exists and is not full, then insert the overflowing
        // element at the front.  otherwise, create a new node.
        if (next && (next->N < list->blocksize)) {
            // shift the existing elements up by one position...
            memmove(NODE_CHARDATA(next) + list->datasize,
                    NODE_CHARDATA(next),
                    next->N * list->datasize);
            destnode = next;
        } else {
            // create and insert a new node.
            bl_node* newnode = bl_new_node(list);
            newnode->next = next;
            node->next = newnode;
            if (!newnode->next)
                list->tail = newnode;
            destnode = newnode;
        }

        if (localindex == node->N) {
            // the new element becomes the first element in the destination node.
            memcpy(NODE_CHARDATA(destnode), data, list->datasize);
        } else {
            // the last element in this node is added to the destination node.
            memcpy(NODE_CHARDATA(destnode), NODE_CHARDATA(node) + (node->N-1)*list->datasize, list->datasize);
            // shift the end portion of this node up by one...
            nshift = node->N - localindex - 1;
            memmove(NODE_CHARDATA(node) + (localindex+1) * list->datasize,
                    NODE_CHARDATA(node) + localindex * list->datasize,
                    nshift * list->datasize);
            // insert the new element...
            memcpy(NODE_CHARDATA(node) + localindex * list->datasize, data, list->datasize);
        }

        destnode->N++;
        list->N++;

    } else {
        // shift...
        int localindex, nshift;
        localindex = index - nskipped;
        nshift = node->N - localindex;
        memmove(NODE_CHARDATA(node) + (localindex+1) * list->datasize,
                NODE_CHARDATA(node) + localindex * list->datasize,
                nshift * list->datasize);
        // insert...
        memcpy(NODE_CHARDATA(node) + localindex * list->datasize,
               data, list->datasize);
        node->N++;
        list->N++;
    }
}

void* bl_access_const(const bl* list, size_t n) {
    bl_node* node;
    size_t nskipped;
    node = find_node(list, n, &nskipped);
    // grab the element.
    return NODE_CHARDATA(node) + (n - nskipped) * list->datasize;
}

void bl_copy(bl* list, size_t start, size_t length, void* vdest) {
    bl_node* node;
    size_t nskipped;
    char* dest;
    if (length <= 0)
        return;
    node = find_node(list, start, &nskipped);

    // we've found the node containing "start".  keep copying elements and
    // moving down the list until we've copied all "length" elements.
    dest = vdest;
    while (length > 0) {
        size_t take, avail;
        char* src;
        // number of elements we want to take.
        take = length;
        // number of elements available at this node.
        avail = node->N - (start - nskipped);
        if (take > avail)
            take = avail;
        src = NODE_CHARDATA(node) + (start - nskipped) * list->datasize;
        memcpy(dest, src, take * list->datasize);

        dest += take * list->datasize;
        start += take;
        length -= take;
        nskipped += node->N;
        node = node->next;
    }
    // update the last_access member...
    list->last_access = node;
    list->last_access_n = nskipped;
}

int bl_check_consistency(bl* list) {
    bl_node* node;
    size_t N;
    int tailok = 1;
    int nempty = 0;
    int nnull = 0;
	
    // if one of head or tail is NULL, they had both better be NULL!
    if (!list->head)
        nnull++;
    if (!list->tail)
        nnull++;
    if (nnull == 1) {
        fprintf(stderr, "bl_check_consistency: head is %p, and tail is %p.\n",
                list->head, list->tail);
        return 1;
    }

    N = 0;
    for (node=list->head; node; node=node->next) {
        N += node->N;
        if (!node->N) {
            // this block is empty.
            nempty++;
        }
        // are we at the last node?
        if (!node->next) {
            tailok = (list->tail == node) ? 1 : 0;
        }
    }
    if (!tailok) {
        fprintf(stderr, "bl_check_consistency: tail pointer is wrong.\n");
        return 1;
    }
    if (nempty) {
        fprintf(stderr, "bl_check_consistency: %i empty blocks.\n", nempty);
        return 1;
    }
    if (N != list->N) {
        fprintf(stderr, "bl_check_consistency: list->N is %zu, but sum of blocks is %zu.\n",
                list->N, N);
        return 1;
    }
    return 0;
}

int bl_check_sorted(bl* list,
                    int (*compare)(const void* v1, const void* v2),
                    int isunique) {
    size_t i, N;
    size_t nbad = 0;
    void* v2 = NULL;
    N = bl_size(list);
    if (N)
        v2 = bl_access(list, 0);
    for (i=1; i<N; i++) {
        void* v1;
        int cmp;
        v1 = v2;
        v2 = bl_access(list, i);
        cmp = compare(v1, v2);
        if (isunique) {
            if (cmp >= 0) {
                nbad++;
            }
        } else {
            if (cmp > 0) {
                nbad++;
            }
        }
    }
    if (nbad) {
        fprintf(stderr, "bl_check_sorted: %zu are out of order.\n", nbad);
        return 1;
    }
    return 0;
}

static void memswap(void* v1, void* v2, int len) {
    unsigned char tmp;
    unsigned char* c1 = v1;
    unsigned char* c2 = v2;
    int i;
    for (i=0; i<len; i++) {
        tmp = *c1;
        *c1 = *c2;
        *c2 = tmp;
        c1++;
        c2++;
    }
}

void bl_reverse(bl* list) {
    // reverse each block, and reverse the order of the blocks.
    pl* blocks;
    bl_node* node;
    bl_node* lastnode;
    int i;

    // reverse each block
    blocks = pl_new(256);
    for (node=list->head; node; node=node->next) {
        for (i=0; i<(node->N/2); i++) {
            memswap(NODE_CHARDATA(node) + i * list->datasize,
                    NODE_CHARDATA(node) + (node->N - 1 - i) * list->datasize,
                    list->datasize);
        }
        pl_append(blocks, node);
    }

    // reverse the blocks
    lastnode = NULL;
    for (i=pl_size(blocks)-1; i>=0; i--) {
        node = pl_get(blocks, i);
        if (lastnode)
            lastnode->next = node;
        lastnode = node;
    }
    if (lastnode)
        lastnode->next = NULL;
    pl_free(blocks);

    // swap head and tail
    node = list->head;
    list->head = list->tail;
    list->tail = node;

    list->last_access = NULL;
    list->last_access_n = 0;
}

void* bl_extend(bl* list) {
    return bl_append(list, NULL);
}



// special-case pointer list accessors...
int bl_compare_pointers_ascending(const void* v1, const void* v2) {
    void* p1 = *(void**)v1;
    void* p2 = *(void**)v2;
    if (p1 > p2) return 1;
    else if (p1 < p2) return -1;
    else return 0;
}

void  pl_free_elements(pl* list) {
    size_t i;
    for (i=0; i<pl_size(list); i++) {
        free(pl_get(list, i));
    }
}

size_t pl_insert_sorted(pl* list, const void* data, int (*compare)(const void* v1, const void* v2)) {
    // we don't just call bl_insert_sorted because then we end up passing
    // "void**" rather than "void*" args to the compare function, which 
    // violates the principle of least surprise.
    ptrdiff_t lower, upper;
    lower = -1;
    upper = list->N;
    while (lower < (upper-1)) {
        ptrdiff_t mid;
        int cmp;
        mid = (upper + lower) / 2;
        cmp = compare(data, pl_get(list, mid));
        if (cmp >= 0) {
            lower = mid;
        } else {
            upper = mid;
        }
    }
    bl_insert(list, lower+1, &data);
    return lower+1;
}

/*
 void pl_set(pl* list, int index, void* data) {
 int i;
 int nadd = (index+1) - list->N;
 if (nadd > 0) {
 // enlarge the list to hold 'nadd' more entries.
 for (i=0; i<nadd; i++) {
 pl_append(list, NULL);
 }
 }
 bl_set(list, index, &data);
 }
 */

void sl_remove_duplicates(sl* lst) {
    size_t i, j;
    for (i=0; i<sl_size(lst); i++) {
        const char* s1 = sl_get(lst, i);
        for (j=i+1; j<sl_size(lst); j++) {
            const char* s2 = sl_get(lst, j);
            if (strcmp(s1, s2) == 0) {
                sl_remove(lst, j);
                j--;
            }
        }
    }
}

sl* sl_new(int blocksize) {
    pl* lst = pl_new(blocksize);
    assert(lst);
    return lst;
}

void sl_init2(sl* list, int blocksize) {
    pl_init(list, blocksize);
}

void sl_free2(sl* list) {
    size_t i;
    if (!list) return;
    for (i=0; i<sl_size(list); i++)
        free(sl_get(list, i));
    bl_free(list);
}

sl* sl_split(sl* lst, const char* str, const char* sepstring) {
    int seplen;
    const char* s;
    char* next_sep;
    if (!lst)
        lst = sl_new(4);
    seplen = strlen(sepstring);
    s = str;
    while (s && *s) {
        next_sep = strstr(s, sepstring);
        if (!next_sep) {
            sl_append(lst, s);
            break;
        }
        //logverb("Appending: '%.*s'\n", (int)(next_sep - s), s);
        sl_appendf(lst, "%.*s", (int)(next_sep - s), s);
        s = next_sep + seplen;
    }
    return lst;
}

void sl_free_nonrecursive(sl* list) {
    bl_free(list);
}

void sl_append_contents(sl* dest, sl* src) {
    size_t i;
    if (!src)
        return;
    for (i=0; i<sl_size(src); i++) {
        char* str = sl_get(src, i);
        sl_append(dest, str);
    }
}

ptrdiff_t sl_index_of(sl* lst, const char* str) {
    size_t i;
    for (i=0; i<sl_size(lst); i++) {
        char* s = sl_get(lst, i);
        if (strcmp(s, str) == 0)
            return i;
    }
    return BL_NOT_FOUND;
}

ptrdiff_t sl_last_index_of(sl* lst, const char* str) {
    ptrdiff_t i;
    for (i=sl_size(lst)-1; i>=0; i--) {
        char* s = sl_get(lst, i);
        if (strcmp(s, str) == 0)
            return i;
    }
    return BL_NOT_FOUND;
}

// Returns 0 if the string is not in the sl, 1 otherwise.
// (same as sl_index_of(lst, str) > -1)
int sl_contains(sl* lst, const char* str) {
    return (sl_index_of(lst, str) > -1);
}

void sl_reverse(sl* list) {
    bl_reverse(list);
}

char* sl_append(sl* list, const char* data) {
    char* copy;
    if (data) {
        copy = strdup(data);
        assert(copy);
    } else
        copy = NULL;
    pl_append(list, copy);
    return copy;
}

void sl_append_array(sl* list, const char**strings, size_t n) {
    size_t i;
    for (i=0; i<n; i++)
        sl_append(list, strings[i]);
}

void sl_append_nocopy(sl* list, const char* data) {
    pl_append(list, data);
}

char* sl_push(sl* list, const char* data) {
    char* copy = strdup(data);
    pl_push(list, copy);
    return copy;
}

char* sl_pop(sl* list) {
    return pl_pop(list);
}

char* sl_get(sl* list, size_t n) {
    return pl_get(list, n);
}

char* sl_get_const(const sl* list, size_t n) {
    return pl_get_const(list, n);
}

char* sl_set(sl* list, size_t index, const char* value) {
    char* copy;
    assert(index >= 0);
    copy = strdup(value);
    if (index < list->N) {
        // we're replacing an existing value - free it!
        free(sl_get(list, index));
        bl_set(list, index, &copy);
    } else {
        // pad
        size_t i;
        for (i=list->N; i<index; i++)
            sl_append_nocopy(list, NULL);
        sl_append(list, copy);
    }
    return copy;
}

int sl_check_consistency(sl* list) {
    return bl_check_consistency(list);
}

char* sl_insert(sl* list, size_t indx, const char* data) {
    char* copy = strdup(data);
    bl_insert(list, indx, &copy);
    return copy;
}

void sl_insert_nocopy(sl* list, size_t indx, const char* str) {
    bl_insert(list, indx, &str);
}

void sl_remove_from(sl* list, size_t start) {
    sl_remove_index_range(list, start, sl_size(list) - start);
}

ptrdiff_t sl_remove_string(sl* list, const char* string) {
    return pl_remove_value(list, string);
}

char* sl_remove_string_bycaseval(sl* list, const char* string) {
    size_t N = sl_size(list);
    size_t i;
    for (i=0; i<N; i++) {
        char* str = sl_get(list, i);
        if (strcasecmp(str, string) == 0) {
            char* s = sl_get(list, i);
            sl_remove(list, i);
            return s;
        }
    }
    return NULL;
}

ptrdiff_t sl_remove_string_byval(sl* list, const char* string) {
    size_t N = sl_size(list);
    size_t i;
    for (i=0; i<N; i++) {
        char* str = sl_get(list, i);
        if (strcmp(str, string) == 0) {
            sl_remove(list, i);
            return i;
        }
    }
    return BL_NOT_FOUND;
}

void sl_remove_index_range(sl* list, size_t start, size_t length) {
    size_t i;
    assert(list);
    assert(start + length <= list->N);
    assert(start >= 0);
    assert(length >= 0);
    for (i=0; i<length; i++) {
        char* str = sl_get(list, start + i);
        free(str);
    }
    bl_remove_index_range(list, start, length);
}

void sl_remove(sl* list, size_t index) {
    bl_remove_index(list, index);
}

void  sl_remove_all(sl* list) {
    size_t i;
    if (!list) return;
    for (i=0; i<sl_size(list); i++)
        free(pl_get(list, i));
    bl_remove_all(list);
}

void   sl_merge_lists(sl* list1, sl* list2) {
    bl_append_list(list1, list2);
}

void sl_print(sl* list) {
    bl_node* n;
    int i;
    for (n=list->head; n; n=n->next) {
        printf("[\n");
        for (i=0; i<n->N; i++)
            printf("  \"%s\"\n", ((char**)NODE_DATA(n))[i]);
        printf("]\n");
    }
}

static char* sljoin(sl* list, const char* join, int forward) {
    size_t start, end, inc;

    size_t len = 0;
    size_t i, N;
    char* rtn;
    size_t offset;
    size_t JL;

    if (sl_size(list) == 0)
        return strdup("");

    // step through the list forward or backward?
    if (forward) {
        start = 0;
        end = sl_size(list);
        inc = 1;
    } else {
        start = sl_size(list) - 1;
        end = -1;
        inc = -1;
    }

    JL = strlen(join);
    N = sl_size(list);
    for (i=0; i<N; i++)
        len += strlen(sl_get(list, i));
    len += ((N-1) * JL);
    rtn = malloc(len + 1);
    if (!rtn)
        return rtn;
    offset = 0;
    for (i=start; i!=end; i+= inc) {
        char* str = sl_get(list, i);
        size_t L = strlen(str);
        if (i != start) {
            memcpy(rtn + offset, join, JL);
            offset += JL;
        }
        memcpy(rtn + offset, str, L);
        offset += L;
    }
    assert(offset == len);
    rtn[offset] = '\0';
    return rtn;
}

char*  sl_join(sl* list, const char* join) {
    return sljoin(list, join, 1);
}

char*  sl_join_reverse(sl* list, const char* join) {
    return sljoin(list, join, 0);
}

char*  sl_implode(sl* list, const char* join) {
    return sl_join(list, join);
}

char* sl_appendf(sl* list, const char* format, ...) {
    char* str;
    va_list lst;
    va_start(lst, format);
    str = sl_appendvf(list, format, lst);
    va_end(lst);
    return str;
}

char* sl_appendvf(sl* list, const char* format, va_list va) {
    char* str;
    if (vasprintf(&str, format, va) == -1)
        return NULL;
    sl_append_nocopy(list, str);
    return str;
}

char* sl_insert_sortedf(sl* list, const char* format, ...) {
    va_list lst;
    char* str;
    va_start(lst, format);
    if (vasprintf(&str, format, lst) == -1)
        return NULL;
    sl_insert_sorted_nocopy(list, str);
    va_end(lst);
    return str;
}

char* sl_insertf(sl* list, size_t index, const char* format, ...) {
    va_list lst;
    char* str;
    va_start(lst, format);
    if (vasprintf(&str, format, lst) == -1)
        return NULL;
    sl_insert_nocopy(list, index, str);
    va_end(lst);
    return str;
}

int bl_compare_strings_ascending(const void* v1, const void* v2) {
    const char* str1 = v1;
    const char* str2 = v2;
    return strcoll(str1, str2);
}

void sl_insert_sorted_nocopy(sl* list, const char* string) {
    pl_insert_sorted(list, string, bl_compare_strings_ascending);
}

char* sl_insert_sorted(sl* list, const char* string) {
    char* copy = strdup(string);
    pl_insert_sorted(list, copy, bl_compare_strings_ascending);
    return copy;
}

#define InlineDefine InlineDefineC
#include "bl.inc"
#undef InlineDefine

