/**
 * \file
 *
 * \brief Workqueue implemented as a linked list
 *
 * This is a workqueue designed to simplify and formalize sequential execution
 * of work items/fuctions. It provides a low overhead structure that can
 * replace or extend the use of threads in simple applications.
 *
 * - Compiler:           IAR EWAVR32 and GNU GCC for AVR32
 * - Supported devices:  All devices
 * - AppNote:
 *
 * \author               Atmel Corporation: http://www.atmel.com \n
 *                       Support and FAQ: http://support.atmel.no/
 *
 * \page License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 * Atmel AVR product.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
#ifndef WORKQUEUE_H_INCLUDED
#define WORKQUEUE_H_INCLUDED

#include <types.h>
#include <slist.h>
#include <util.h>
#include <interrupt.h>

/*! Work queue callback function pointer type. */
typedef void (*workqueue_callback_t)(void *data);

/*! Work item structure. This is the element type in the queue. */
struct workqueue_item
{
	workqueue_callback_t callback;  /*!< Work callback function. */
	void *data;                    /*!< Data to be passed to callback. */
	struct slist_node node;        /*!< Linked list node of the item */
};

/*! Work queue structure. Do not access the memebers directly. */
struct workqueue
{
	struct slist list;             /*!< List used by the queue */
};


#ifdef __cplusplus
extern "C" {
#endif


/*!
 * \brief Initialize a new work queue
 *
 * \param  queue  Pointer to work queue control structure.
 */
static inline void workqueue_init(struct workqueue *queue)
{
	/* Sanity check on parameters. */
	assert(queue);

	/* Initialize to an empty state, ready for data. */
	slist_init(&queue->list);
}

/*!
 * \brief Initialize a work item
 *
 * This function initialize a work item. It should be used before adding the
 * item to the workqueue. The caller must make sure memory is allocated for
 * the item struct for initializing. DO NOT initialize a work item already
 * in use (present in a work queue).
 *
 * \param  item    	Pointer to work item to add.
 * \param  callback	Pointer to function doing the work
 * \param  data    	Pointer to data that will be passed to the function
 */
static inline void workqueue_init_item(
		struct workqueue_item *item,
		workqueue_callback_t callback,
		void *data)
{
	/* Flag the item as ready for use by clearing next pointer */
	item->node.next = NULL;

	item->callback = callback;
	item->data = data;
}

/*!
 * \brief Checks if the queue is empty or not.
 *
 * \param  queue  Pointer to work queue control structure.
 *
 * \return true   Queue has no items.
 * \return false  Queue has at least one item.
 */
static inline bool workqueue_is_empty(struct workqueue *queue)
{
	/* Sanity check on parameters. */
	assert(queue);

	return slist_is_empty(&queue->list);
}

/*!
 * \brief Checks if \a item has already been queued
 * \retval true \a item has already been added to a workqueue
 * \retval false \a item can safely be added to a workqueue
 */
static inline bool workqueue_item_is_queued(struct workqueue_item *item)
{
	return item->node.next != NULL;
}

/*!
 * \brief Add work item to work queue
 *
 * This function adds a work item to a work queue. The item structure must be
 * set up by the caller and only a pointer to it is stored in the queue. The
 * caller must make sure the item struct is kept intact while in the queue
 * and if necessary freed after running the item (This can be done safely in
 * the work item itself). The same item CAN NOT exist twice in the same queue,
 * it can only be added again when the work item has started execution or later.
 *
 * \param  queue  Pointer to work queue control structure.
 * \param  item   Pointer to work item to add.
 *
 * \return  Pointer to work item just added.
 */
static inline void workqueue_add_item(
		struct workqueue *queue,
		struct workqueue_item *item)
{
	unsigned long iflags;

	/* Sanity check on parameters. */
	assert(queue);
	assert(item);
	/* We will run this later, so check that it is valid */
	assert(item->callback);
	/* If the next pointer is set, the item is in use */
	assert(item->node.next == NULL);

	iflags = cpu_irq_save();
	slist_insert_tail(&queue->list, &item->node);
	cpu_irq_restore(iflags);
}

/*!
 * \brief Add work item to work queue, unless it has been added before
 *
 * This function adds a work item to a work queue. The item structure
 * must be set up by the caller and only a pointer to it is stored in
 * the queue. The caller must make sure the item struct is kept intact
 * while in the queue and if necessary freed after running the item
 * (This can be done safely in the work item itself).
 *
 * If \a item has been queued on any work queue before, this function
 * does nothing.
 *
 * \param  queue  Pointer to work queue control structure.
 * \param  item   Pointer to work item to add.
 *
 * \return  Pointer to work item just added.
 */
static inline void workqueue_add_item_safe(
		struct workqueue *queue,
		struct workqueue_item *item)
{
	unsigned long iflags;

	/* Sanity check on parameters. */
	assert(queue);
	assert(item);
	/* We will run this later, so check that it is valid */
	assert(item->callback);

	iflags = cpu_irq_save();
	if (!workqueue_item_is_queued(item))
		slist_insert_tail(&queue->list, &item->node);
	cpu_irq_restore(iflags);
}

/*!
 * \brief Remove work item from front of queue
 *
 * This function removes one work item from the front of a work queue and
 * returns a pointer to that item. The memory allocated to the item struct
 * will not be freed.If the queue is empty, a NULL pointer will be returned.
 *
 * \param  queue  Pointer to work queue control structure.
 *
 * \return Pointer to work item removed
 */
static inline struct workqueue_item *workqueue_remove_item(
		struct workqueue *queue)
{
	struct workqueue_item *item;
	unsigned long iflags;

	/* Sanity check on parameters. */
	assert(queue);

	iflags = cpu_irq_save();
	if (!workqueue_is_empty(queue)) {
		item = slist_pop_head(&queue->list, struct workqueue_item, node);
		/* Flag the item as ready for use by clearing next pointer */
		item->node.next = NULL;
	} else {
		item = NULL;
	}
	cpu_irq_restore(iflags);

	return item;
}

/*!
 * \brief Move first work item from one queue to the back of another queue
 *
 * This function removes the first work item from the source queue and adds it
 * to the end of the destination queue.
 *
 * \param  source  Pointer to source work queue.
 * \param  dest    Pointer to destination work queue.
 */
inline static void workqueue_move_item(
		struct workqueue *source,
		struct workqueue *dest)
{
	/* Sanity check on parameters. */
	assert(source);
	assert(dest);

	/* Move work between queues. */
	workqueue_add_item(dest, workqueue_remove_item(source));
}

/*!
 * \brief Get data associated with a workqueue item
 *
 * \param item  Pointer to a workqueue item
 * \return      Pointer to the items data field
 */
inline static void *workqueue_get_data(struct workqueue_item *item)
{
	assert(item);

	return item->data;
}


/*!
 * \brief Remove first work item and run its callback
 *
 * This function removes one work item from the front of the queue and then
 * runs the work callback function. The work item can be considered unused
 * when the callback function is executed and can freely be be reused or freed.
 * If the queue is empty, running this function will not have any effect.
 *
 * \param  queue  Pointer to work queue control structure.
 */
inline static void workqueue_do_one_item(struct workqueue *queue)
{
	struct workqueue_item *item;

	/* Sanity check on parameters. */
	assert(queue);

	/* Get work and dispatch */
	item = workqueue_remove_item(queue);
	if (item)
		item->callback(workqueue_get_data(item));
}


#ifdef __cplusplus
} /* extern "C" */
#endif


#endif /* WORKQUEUE_H_INCLUDED */
