#ifndef SEQ66_PMUTIL_H
#define SEQ66_PMUTIL_H

/*
 *  This file is part of seq66, adapted from the PortMIDI project.
 *
 *  seq66 is free software; you can redistribute it and/or modify it under the
 *  terms of the GNU General Public License as published by the Free Software
 *  Foundation; either version 2 of the License, or (at your option) any later
 *  version.
 *
 *  seq66 is distributed in the hope that it will be useful, but WITHOUT ANY
 *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 *  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with seq66; if not, write to the Free Software Foundation, Inc., 59 Temple
 *  Place, Suite 330, Boston, MA  02111-1307  USA
 */

/**
 * \file        pmutil.h
 *
 *  Some helpful utilities for building MIDI applications that use PortMidi.
 *
 * \library     seq66 application
 * \author      PortMIDI team; modifications by Chris Ahlstrom
 * \date        2017-08-21
 * \updates     2018-04-10
 * \license     GNU GPLv2 or above
 */

#include "pminternal.h"                 /* they all include this one anyway */

#if defined __cplusplus
extern "C"
{
#endif

/**
 *  A single-reader, single-writer queue is created by Pm_QueueCreate(), which
 *  takes the number of messages and the message size as parameters. The queue
 *  only accepts fixed sized messages. Returns NULL if memory cannot be
 *  allocated.
 *
 *  This queue implementation uses the "light pipe" algorithm which operates
 *  correctly even with multi-processors and out-of-order memory writes. (See
 *  Alexander Dokumentov, "Lock-free Interprocess Communication," Dr. Dobbs
 *  Portal, http://www.ddj.com/, articleID=189401457, June 15, 2006. This
 *  algorithm requires that messages be translated to a form where no words
 *  contain zeros. Each word becomes its own "data valid" tag. Because of this
 *  translation, we cannot return a pointer to data still in the queue when
 *  the "peek" method is called. Instead, a buffer is preallocated so that
 *  data can be copied there. Pm_QueuePeek() dequeues a message into this
 *  buffer and returns a pointer to it. A subsequent Pm_Dequeue() will copy
 *  from this buffer.
 *
 *  This implementation does not try to keep reader/writer data in separate
 *  cache lines or prevent thrashing on cache lines.  However, this algorithm
 *  differs by doing inserts/removals in units of messages rather than units
 *  of machine words. Some performance improvement might be obtained by not
 *  clearing data immediately after a read, but instead by waiting for the end
 *  of the cache line, especially if messages are smaller than cache lines.
 *  See the Dokumentov article for explanation.
 *
 *  The algorithm is extended to handle "overflow" reporting. To report an
 *  overflow, the sender writes the current tail position to a field.  The
 *  receiver must acknowlege receipt by zeroing the field. The sender will not
 *  send more until the field is zeroed.
 *
 *  Pm_QueueDestroy() destroys the queue and frees its storage.
 */

PMEXPORT PmQueue * Pm_QueueCreate (long num_msgs, int32_t bytes_per_msg);
PMEXPORT PmError Pm_QueueDestroy (PmQueue * queue);

/**
 *  Pm_Dequeue() removes one item from the queue, copying it into msg.
 *  Returns 1 if successful, and 0 if the queue is empty.  Returns
 *  pmBufferOverflow if what would have been the next thing in the queue was
 *  dropped due to overflow. (So when overflow occurs, the receiver can
 *  receive a queue full of messages before getting the overflow report. This
 *  protocol ensures that the reader will be notified when data is lost due to
 *  overflow.
 */

PMEXPORT PmError Pm_Dequeue (PmQueue * queue, void * msg);

/**
 *  Pm_Enqueue() inserts one item into the queue, copying it from msg.
 *  Returns pmNoError if successful and pmBufferOverflow if the queue was
 *  already full. If pmBufferOverflow is returned, the overflow flag is set.
 */

PMEXPORT PmError Pm_Enqueue (PmQueue * queue, void * msg);

/**
 *  Pm_QueueFull() returns non-zero if the queue is full.
 *  Pm_QueueEmpty() returns non-zero if the queue is empty.
 *
 *  Either condition may change immediately because a parallel enqueue or
 *  dequeue operation could be in progress. Furthermore, Pm_QueueEmpty() is
 *  optimistic: it may say false, when due to out-of-order writes, the full
 *  message has not arrived. Therefore, Pm_Dequeue() could still return 0
 *  after Pm_QueueEmpty() returns false. On the other hand, Pm_QueueFull() is
 *  pessimistic: if it returns false, then Pm_Enqueue() is guaranteed to
 *  succeed.
 *
 *  Error conditions: Pm_QueueFull() returns pmBadPtr if queue is NULL.
 *  Pm_QueueEmpty() returns FALSE if queue is NULL.
 */

PMEXPORT int Pm_QueueFull (PmQueue * queue);
PMEXPORT int Pm_QueueEmpty (PmQueue * queue);

/**
 *  Pm_QueuePeek() returns a pointer to the item at the head of the queue, or
 *  NULL if the queue is empty. The item is not removed from the queue.
 *  Pm_QueuePeek() will not indicate when an overflow occurs. If you want to
 *  get and check pmBufferOverflow messages, use the return value of
 *  Pm_QueuePeek() *only* as an indication that you should call Pm_Dequeue().
 *  At the point where a direct call to Pm_Dequeue() would return
 *  pmBufferOverflow, Pm_QueuePeek() will return NULL but internally clear the
 *  pmBufferOverflow flag, enabling Pm_Enqueue() to resume enqueuing messages.
 *  A subsequent call to Pm_QueuePeek() will return a pointer to the first
 *  message *after* the overflow.  Using this as an indication to call
 *  Pm_Dequeue(), the first call to Pm_Dequeue() will return pmBufferOverflow.
 *  The second call will return success, copying the same message pointed to
 *  by the previous Pm_QueuePeek().
 *
 *  When to use Pm_QueuePeek(): (1) when you need to look at the message data
 *  to decide who should be called to receive it. (2) when you need to know a
 *  message is ready but cannot accept the message.
 *
 *  Note that Pm_QueuePeek() is not a fast check, so if possible, you might as
 *  well just call Pm_Dequeue() and accept the data if it is there.
 */

PMEXPORT void * Pm_QueuePeek (PmQueue * queue);

/**
 *  Pm_SetOverflow() allows the writer (enqueuer) to signal an overflow
 *  condition to the reader (dequeuer). E.g. when transfering data from the OS
 *  to an application, if the OS indicates a buffer overrun, Pm_SetOverflow()
 *  can be used to insure that the reader receives a pmBufferOverflow result
 *  from Pm_Dequeue(). Returns pmBadPtr if queue is NULL, returns
 *  pmBufferOverflow if buffer is already in an overflow state, returns
 *  pmNoError if successfully set overflow state.
 */

PMEXPORT PmError Pm_SetOverflow (PmQueue * queue);

#if defined __cplusplus
}               // extern "C"
#endif

#endif          // SEQ66_PMUTIL_H

/*
 * pmutil.h
 *
 * vim: sw=4 ts=4 wm=4 et ft=c
 */

