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

/**
 Defined:

 --nl
 --number
 --NL_PRINT(x)  prints number 'x'

 Note:
 --You can't declare multiple "number" variables like this:
     number n1, n2;
   Instead, do:
     number n1;
     number n2;
   This is because "number" may be a pointer type.
 */

#include "bl-nl.ph"

#define NODE_NUMDATA(node) ((number*)NODE_DATA(node))

number* NLF(to_array)(nl* list) {
	number* arr;
	size_t N;
	if (!list)
		return NULL;
	N = NLF(size)(list);
	arr = malloc(N * sizeof(number));
	bl_copy(list, 0, N, arr);
	return arr;
}

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

static int NLF(compare_ascending)(const void* v1, const void* v2) {
    number i1 = *(number*)v1;
    number i2 = *(number*)v2;
    if (i1 > i2) return 1;
    else if (i1 < i2) return -1;
    else return 0;
}

static int NLF(compare_descending)(const void* v1, const void* v2) {
    number i1 = *(number*)v1;
    number i2 = *(number*)v2;
    if (i1 > i2) return -1;
    else if (i1 < i2) return 1;
    else return 0;
}

void NLF(reverse)(nl* list) {
	bl_reverse(list);
}

void NLF(append_array)(nl* list, const number* data, size_t ndata) {
	size_t i;
	for (i=0; i<ndata; i++)
		NLF(append)(list, data[i]);
}

nl* NLF(merge_ascending)(nl* list1, nl* list2) {
	nl* res;
	size_t i1, i2, N1, N2;
	number v1 = 0;
	number v2 = 0;
	unsigned char getv1, getv2;
	if (!list1)
		return NLF(dupe)(list2);
	if (!list2)
		return NLF(dupe)(list1);
	if (!NLF(size)(list1))
		return NLF(dupe)(list2);
	if (!NLF(size)(list2))
		return NLF(dupe)(list1);

	res = NLF(new)(list1->blocksize);
	N1 = NLF(size)(list1);
	N2 = NLF(size)(list2);
	i1 = i2 = 0;
	getv1 = getv2 = 1;
	while (i1 < N1 && i2 < N2) {
		if (getv1) {
			v1 = NLF(get)(list1, i1);
			getv1 = 0;
		}
		if (getv2) {
			getv2 = 0;
			v2 = NLF(get)(list2, i2);
		}
		if (v1 <= v2) {
			NLF(append)(res, v1);
			i1++;
			getv1 = 1;
		} else {
			NLF(append)(res, v2);
			i2++;
			getv2 = 1;
		}
	}
	for (; i1<N1; i1++)
		NLF(append)(res, NLF(get)(list1, i1));
	for (; i2<N2; i2++)
		NLF(append)(res, NLF(get)(list2, i2));
	return res;
}

void NLF(remove_all_reuse)(nl* list) {
	bl_remove_all_but_first(list);
}

ptrdiff_t  NLF(find_index_ascending)(nl* list, const number value) {
	return bl_find_index(list, &value, NLF(compare_ascending));
}

int NLF(check_consistency)(nl* list) {
	return bl_check_consistency(list);
}

int NLF(check_sorted_ascending)(nl* list,
							  int isunique) {
	return bl_check_sorted(list, NLF(compare_ascending), isunique);
}

int NLF(check_sorted_descending)(nl* list,
							   int isunique) {
	return bl_check_sorted(list, NLF(compare_descending), isunique);
}

void NLF(remove)(nl* nlist, size_t index) {
    bl_remove_index(nlist, index);
}

number NLF(pop)(nl* nlist) {
    number ret = NLF(get)(nlist, nlist->N-1);
    bl_remove_index(nlist, nlist->N-1);
    return ret;
}

nl* NLF(dupe)(nl* nlist) {
    nl* ret = NLF(new)(nlist->blocksize);
    size_t i;
    for (i=0; i<nlist->N; i++)
        NLF(push)(ret, NLF(get)(nlist, i));
    return ret;
}

ptrdiff_t NLF(remove_value)(nl* nlist, const number value) {
    bl* list = nlist;
	bl_node *node, *prev;
	size_t istart = 0;
	for (node=list->head, prev=NULL;
		 node;
		 prev=node, node=node->next) {
		int i;
		number* idat;
		idat = NODE_DATA(node);
		for (i=0; i<node->N; i++)
			if (idat[i] == value) {
				bl_remove_from_node(list, node, prev, i);
				list->last_access = prev;
				list->last_access_n = istart;
				return istart + i;
			}
		istart += node->N;
	}
	return BL_NOT_FOUND;
}

void NLF(remove_all)(nl* list) {
	bl_remove_all(list);
}

void NLF(remove_index_range)(nl* list, size_t start, size_t length) {
	bl_remove_index_range(list, start, length);
}

void NLF(set)(nl* list, size_t index, const number value) {
	bl_set(list, index, &value);
}

/*
 void dl_set(dl* list, int index, double value) {
 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++) {
 dl_append(list, 0.0);
 }
 }
 bl_set(list, index, &value);
 }
 */

nl* NLF(new)(int blocksize) {
	return bl_new(blocksize, sizeof(number));
}

void NLF(new_existing)(nl* list, int blocksize) {
	bl_init(list, blocksize, sizeof(number));
}

void NLF(init)(nl* list, int blocksize) {
	bl_init(list, blocksize, sizeof(number));
}

void NLF(free)(nl* list) {
	bl_free(list);
}

void NLF(push)(nl* list, const number data) {
	bl_append(list, &data);
}

number* NLF(append)(nl* list, const number data) {
	return bl_append(list, &data);
}

void NLF(append_list)(nl* list, nl* list2) {
    size_t i, N;
    N = NLF(size)(list2);
    for (i=0; i<N; i++)
        NLF(append)(list, NLF(get)(list2, i));
}

void NLF(merge_lists)(nl* list1, nl* list2) {
	bl_append_list(list1, list2);
}

static ptrdiff_t NLF(binarysearch)(bl_node* node, const number n) {
	number* iarray = NODE_NUMDATA(node);
	ptrdiff_t lower = -1;
	ptrdiff_t upper = node->N;
	ptrdiff_t mid;
	while (lower < (upper-1)) {
		mid = (upper + lower) / 2;
		if (n >= iarray[mid])
			lower = mid;
		else
			upper = mid;
	}
	return lower;
}

// find the first node for which n <= the last element.
static bl_node* NLF(findnodecontainingsorted)(const nl* list, const number n,
											  size_t* p_nskipped) {
	bl_node *node;
	size_t nskipped;
	//bl_node *prev;
	//int prevnskipped;

	// check if we can use the jump accessor or if we have to start at
	// the beginning...
	if (list->last_access && list->last_access->N &&
		// is the value we're looking for >= the first element?
		(n >= *NODE_NUMDATA(list->last_access))) {
		node = list->last_access;
		nskipped = list->last_access_n;
	} else {
		node = list->head;
		nskipped = 0;
	}

	/*
	 // find the first node for which n < the first element.  The
	 // previous node will contain the value (if it exists).
	 for (prev=node, prevnskipped=nskipped;
	 node && (n < *NODE_NUMDATA(node));) {
	 prev=node;
	 prevnskipped=nskipped;
	 nskipped+=node->N;
	 node=node->next;
	 }
	 if (prev && n <= NODE_NUMDATA(prev)[prev->N-1]) {
	 if (p_nskipped)
	 *p_nskipped = prevnskipped;
	 return prev;
	 }
	 if (node && n <= NODE_NUMDATA(node)[node->N-1]) {
	 if (p_nskipped)
	 *p_nskipped = nskipped;
	 return node;
	 }
	 return NULL;
	 */
	/*
	 if (!node && prev && n > NODE_NUMDATA(prev)[prev->N-1])
	 return NULL;
	 if (p_nskipped)
	 *p_nskipped = prevnskipped;
	 return prev;
	 */

	for (; node && (n > NODE_NUMDATA(node)[node->N-1]); node=node->next)
		nskipped += node->N;
	if (p_nskipped)
		*p_nskipped = nskipped;
	return node;
}

static ptrdiff_t NLF(insertascending)(nl* list, const number n, int unique) {
	bl_node *node;
	size_t ind;
	size_t nskipped;

	node = NLF(findnodecontainingsorted)(list, n, &nskipped);
	if (!node) {
		NLF(append)(list, n);
		return list->N-1;
	}

	/*
	for (; node && (n > NODE_NUMDATA(node)[node->N-1]); node=node->next)
		nskipped += node->N;
	if (!node) {
		// either we're adding the first element, or we're appending since
		// n is bigger than the largest element in the list.
		NLF(append)(list, n);
		return list->N-1;
	}
	 */

	// find where in the node it should be inserted...
	ind = 1 + NLF(binarysearch)(node, n);

    // check if it's a duplicate...
	if (unique && ind > 0 && (n == NODE_NUMDATA(node)[ind-1]))
		return BL_NOT_FOUND;

	// set the jump accessors...
	list->last_access = node;
	list->last_access_n = nskipped;
	// ... so that this runs in O(1).
	bl_insert(list, nskipped + ind, &n);
	return nskipped + ind;
}

size_t NLF(insert_ascending)(nl* list, const number n) {
	return NLF(insertascending)(list, n, 0);
}

ptrdiff_t NLF(insert_unique_ascending)(nl* list, const number n) {
	return NLF(insertascending)(list, n, 1);
}

size_t NLF(insert_descending)(nl* list, const number n) {
    return bl_insert_sorted(list, &n, NLF(compare_descending));
}

void NLF(insert)(nl* list, size_t indx, const number data) {
	bl_insert(list, indx, &data);
}

void NLF(copy)(nl* list, size_t start, size_t length, number* vdest) {
	bl_copy(list, start, length, vdest);
}

void NLF(print)(nl* list) {
	bl_node* n;
	for (n=list->head; n; n=n->next) {
        int i;
		printf("[ ");
		for (i=0; i<n->N; i++) {
			if (i > 0)
				printf(", ");
			NL_PRINT(NODE_NUMDATA(n)[i]);
		}
		printf("] ");
	}
}

ptrdiff_t  NLF(index_of)(nl* list, const number data) {
	bl_node* n;
	number* idata;
	size_t npast = 0;
	for (n=list->head; n; n=n->next) {
        int i;
		idata = NODE_NUMDATA(n);
		for (i=0; i<n->N; i++)
			if (idata[i] == data)
				return npast + i;
		npast += n->N;
	}
	return BL_NOT_FOUND;
}

int NLF(contains)(nl* list, const number data) {
	return (NLF(index_of)(list, data) != BL_NOT_FOUND);
}

int NLF(sorted_contains)(nl* list, const number n) {
	return NLF(sorted_index_of)(list, n) != BL_NOT_FOUND;
}

ptrdiff_t NLF(sorted_index_of)(nl* list, const number n) {
	bl_node *node;
	ptrdiff_t lower;
	size_t nskipped;

	node = NLF(findnodecontainingsorted)(list, n, &nskipped);
	if (!node)
		return BL_NOT_FOUND;
	//if (!node && (n > NODE_NUMDATA(prev)[prev->N-1]))
	//return -1;
	//node = prev;

	 /*
	 // find the first node for which n <= the last element.  That node
	 // will contain the value (if it exists)
	 for (; node && (n > NODE_NUMDATA(node)[node->N-1]); node=node->next)
	 nskipped += node->N;
	 if (!node)
	 return -1;
	 */

	// update jump accessors...
	list->last_access = node;
	list->last_access_n = nskipped;

	// find within the node...
	lower = NLF(binarysearch)(node, n);
    if (lower == BL_NOT_FOUND)
        return BL_NOT_FOUND;
	if (n == NODE_NUMDATA(node)[lower])
		return nskipped + lower;
	return BL_NOT_FOUND;
}

#undef NLF
#undef NODE_NUMDATA
