/* 
 * Copyright 2008-2018 Aerospike, Inc.
 *
 * Portions may be licensed to Aerospike, Inc. under one or more contributor
 * license agreements.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

/**
 *  double linked list functionality
 */
#include <citrusleaf/cf_ll.h>
#include <citrusleaf/alloc.h>

/**
 * SYNOPSIS
 * LinkedList
 * Sometimes the answer is a doubly linked list. It's not that frequent, but
 * all the corner cases in a double linked list can be annoying.
 */
 
/******************************************************************************
 * MACROS
 ******************************************************************************/

#define LL_UNLOCK(_ll) 	if ( _ll->uselock ) { pthread_mutex_unlock(&(_ll->LOCK)); }
#define LL_LOCK(_ll)	if ( _ll->uselock ) { pthread_mutex_lock(&(_ll->LOCK)); }

/******************************************************************************
 * FUNCTIONS
 ******************************************************************************/

void cf_ll_prepend_lockfree(cf_ll * ll, cf_ll_element * e) {
	// empty list
	if (ll->head == 0) { 
		ll->head = e;
		ll->tail = e;
		e->next = 0;
		e->prev = 0;
	}

	// at least one element - add to head
	else {
		e->next = ll->head;
		e->prev = 0;
		
		ll->head->prev = e;
		ll->head = e;
	}
	
	ll->sz++;
}

void cf_ll_prepend(cf_ll *ll, cf_ll_element *e  ) {
	LL_LOCK(ll);
	cf_ll_prepend_lockfree(ll, e);
	LL_UNLOCK(ll)
}

void cf_ll_append_lockfree(cf_ll *ll, cf_ll_element *e  ) {
	if (ll->head == 0) { 
		ll->head = e;
		ll->tail = e;
		e->next = 0;
		e->prev = 0;
	}
	// at least one element - add to tail
	else {
		e->next = 0;
		e->prev = ll->tail;
		
		ll->tail->next = e;
		ll->tail = e;
	}
	
	ll->sz++;
}


void cf_ll_append(cf_ll *ll, cf_ll_element *e  ) {
	LL_LOCK(ll);

	cf_ll_append_lockfree(ll, e);

	LL_UNLOCK(ll);
}

void cf_ll_insert_after_lockfree(cf_ll *ll, cf_ll_element *cur, cf_ll_element *ins) {
	ins->next = cur->next;
	ins->prev = cur;
	
	if (cur->next == 0)
		ll->tail = ins;
	else
		cur->next->prev = ins;

	cur->next = ins;
	
	ll->sz++;
}


void cf_ll_insert_after(cf_ll *ll, cf_ll_element *cur, cf_ll_element *ins) {
	LL_LOCK(ll);
	cf_ll_insert_after_lockfree(ll, cur, ins);
	LL_UNLOCK(ll);
}

void cf_ll_insert_before_lockfree(cf_ll *ll, cf_ll_element *cur, cf_ll_element *ins) {

	ins->next = cur;
	ins->prev = cur->prev;

	if (cur->prev == 0)
		ll->head = ins;
	else
		cur->prev->next = ins;
	cur->prev = ins;
	
	ll->sz++;
}

void cf_ll_insert_before(cf_ll *ll, cf_ll_element *cur, cf_ll_element *ins) {
	LL_LOCK(ll);
	cf_ll_insert_before_lockfree( ll, cur, ins);
	LL_UNLOCK(ll);
}


void cf_ll_delete_lockfree(cf_ll *ll, cf_ll_element *e ) {
	// make empty
	if (ll->sz == 1) {
		ll->head = 0;
		ll->tail = 0;
	}
	
	// head delete
	else if (e == ll->head) {
		ll->head = e->next;
		e->next->prev = 0;
	}
	// tail delete
	else if (e == ll->tail) {
		ll->tail = e->prev;
		e->prev->next = 0;
	}
	// we're in the middle somewhere
	else {
		e->prev->next = e->next;
		e->next->prev = e->prev;
	}
	
	ll->sz --;
	
	if (ll->destroy_fn)
		(ll->destroy_fn) (e);
}

void cf_ll_delete(cf_ll *ll, cf_ll_element *e ) {
	// extra check for fun
	if (ll->sz == 0)	return;

	LL_LOCK(ll);

	cf_ll_delete_lockfree(ll, e);
	
	LL_UNLOCK(ll);
	
}


int cf_ll_reduce( cf_ll *ll, bool forward, cf_ll_reduce_fn fn, void *udata) {
	int rv = 0;
	
	LL_LOCK(ll);
	
	cf_ll_element *cur = forward ? ll->head : ll->tail;
	
	while (cur) {
		
		if ( (rv = (*fn) (cur, udata) ) ) {
			
			if (rv == CF_LL_REDUCE_DELETE) {

				cf_ll_element *next = forward ? cur->next : cur->prev;
				// Calls the destructor on cur, can't touch it after that
				cf_ll_delete_lockfree(ll, cur);
				cur = next;
				
			}
			else
				goto Exit;
		}
		else {
			cur = forward ? cur->next : cur->prev;
		}
	}

Exit:		
	LL_UNLOCK(ll);
	return(rv);
}

int cf_ll_insert_reduce(cf_ll *ll, cf_ll_element *e, bool forward, cf_ll_reduce_fn fn, void *udata) {
	int rv = 0;
	LL_LOCK(ll);

	cf_ll_element *cur = forward ? ll->head : ll->tail;
	
	while (cur) {
		
		if ( (rv = (*fn) (cur, udata) ) ) {
			
			if (rv == CF_LL_REDUCE_INSERT) {

				if (forward)
					cf_ll_insert_before_lockfree(ll, cur, e);
				else
					cf_ll_insert_after_lockfree(ll, cur, e);
				rv = 0;
				
			}
			
			goto Exit;
			
		}
		else {
			cur = forward ? cur->next : cur->prev;
		}
	}
	
	// give chance to insert at "end"
	if ( (rv = (*fn) (cur, udata) ) ) {
		if (rv == CF_LL_REDUCE_INSERT) {
			if (forward)
				cf_ll_append_lockfree(ll, e);
			else
				cf_ll_prepend_lockfree(ll, e);
			rv = 0;
		}
	}

Exit:
	LL_UNLOCK(ll);
	return(rv);
}

uint32_t cf_ll_size(cf_ll *ll) {
	LL_LOCK(ll);
	uint32_t sz = ll->sz;
	LL_UNLOCK(ll);
	return(sz);
}	

cf_ll_element * cf_ll_search_lockfree(cf_ll *ll, cf_ll_element *e, bool forward, cf_ll_reduce_fn fn) {
	if (ll->head == NULL) {
		return NULL;
	}
	int rv = 0;
	cf_ll_element *cur = forward ? ll->head : ll->tail;
	
	while (cur) {
		rv = (*fn) (cur, (void*)e);

		if (rv == CF_LL_REDUCE_MATCHED) {
			return cur;
		}
		else if (rv == CF_LL_REDUCE_NOT_MATCHED) {
			cur = forward ? cur->next : cur->prev;
			rv = 0;
		}
		else {
			return NULL;
		}
	}
	return NULL;
}

cf_ll_element * cf_ll_search(cf_ll *ll, cf_ll_element *e, bool forward, cf_ll_reduce_fn fn) 
{
	LL_LOCK(ll);
	
	cf_ll_element * ele = NULL;
	ele = cf_ll_search_lockfree(ll, e, forward, fn);

	LL_UNLOCK(ll);

	return ele;
}

cf_ll_iterator * cf_ll_getIterator(cf_ll * ll, bool forward)
{
	cf_ll_iterator *iter = NULL;
	iter = (cf_ll_iterator *)cf_malloc(sizeof(cf_ll_iterator));
	if (iter == NULL) {
		return NULL;
	}
	iter->forward = forward;
	iter->next = iter->forward ? ll->head : ll->tail;
	return iter;
}

void cf_ll_releaseIterator(cf_ll_iterator *iter)
{
	if (iter) cf_free(iter);
}

cf_ll_element * cf_ll_getNext(cf_ll_iterator *iter)
{
	if (!iter) return NULL;
	cf_ll_element * ele = iter->next;
	if (ele != NULL) {
		iter->next = iter->forward ? ele->next : ele->prev;
	}
	return ele;
}

/* Return the element at the specified zero-based index
 * where 0 is the head, 1 is the element next to head
 * and so on. Negative integers are used in order to count
 * from the tail, -1 is the last element, -2 the penultimante
 * and so on. If the index is out of range NULL is returned. */
cf_ll_element *cf_ll_index(cf_ll *ll, int index)
{
	cf_ll_element *ele = NULL;
	if (index < 0) {
		index = (-index) - 1;
		ele = ll->tail;
		while (index-- && ele) {
			ele = ele->prev;
		}
	}
	else {
		ele = ll->head;
		while (index-- && ele) {
			ele = ele->next;
		}
	}
	return ele;
}

int  cf_ll_init(cf_ll *ll, cf_ll_destructor destroy_fn, bool uselock) {
	ll->head = 0;
	ll->tail = 0;
	ll->destroy_fn = destroy_fn;
	ll->sz = 0;
	ll->uselock = uselock;
	if (uselock) {
		pthread_mutex_init(&ll->LOCK, 0);
	}
	return(0);	
}
