/*
    ChibiOS - Copyright (C) 2006..2015 Giovanni Di Sirio.

    This file is part of ChibiOS.

    ChibiOS 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 3 of the License, or
    (at your option) any later version.

    ChibiOS 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/**
 * @file    chqueues.c
 * @brief   I/O Queues code.
 *
 * @addtogroup io_queues
 * @details ChibiOS/RT queues are mostly used in serial-like device drivers.
 *          The device drivers are usually designed to have a lower side
 *          (lower driver, it is usually an interrupt service routine) and an
 *          upper side (upper driver, accessed by the application threads).<br>
 *          There are several kind of queues:<br>
 *          - <b>Input queue</b>, unidirectional queue where the writer is the
 *            lower side and the reader is the upper side.
 *          - <b>Output queue</b>, unidirectional queue where the writer is the
 *            upper side and the reader is the lower side.
 *          - <b>Full duplex queue</b>, bidirectional queue. Full duplex queues
 *            are implemented by pairing an input queue and an output queue
 *            together.
 *          .
 * @pre     In order to use the I/O queues the @p CH_CFG_USE_QUEUES option must
 *          be enabled in @p chconf.h.
 * @{
 */

#include "ch.h"

#if (CH_CFG_USE_QUEUES == TRUE) || defined(__DOXYGEN__)

/*===========================================================================*/
/* Module local definitions.                                                 */
/*===========================================================================*/

/*===========================================================================*/
/* Module exported variables.                                                */
/*===========================================================================*/

/*===========================================================================*/
/* Module local types.                                                       */
/*===========================================================================*/

/*===========================================================================*/
/* Module local variables.                                                   */
/*===========================================================================*/

/*===========================================================================*/
/* Module local functions.                                                   */
/*===========================================================================*/

/*===========================================================================*/
/* Module exported functions.                                                */
/*===========================================================================*/

/**
 * @brief   Initializes an input queue.
 * @details A Semaphore is internally initialized and works as a counter of
 *          the bytes contained in the queue.
 * @note    The callback is invoked from within the S-Locked system state,
 *          see @ref system_states.
 *
 * @param[out] iqp      pointer to an @p input_queue_t structure
 * @param[in] bp        pointer to a memory area allocated as queue buffer
 * @param[in] size      size of the queue buffer
 * @param[in] infy      pointer to a callback function that is invoked when
 *                      data is read from the queue. The value can be @p NULL.
 * @param[in] link      application defined pointer
 *
 * @init
 */
void chIQObjectInit(input_queue_t *iqp, uint8_t *bp, size_t size,
                    qnotify_t infy, void *link) {

  chThdQueueObjectInit(&iqp->waiting);
  iqp->counter = 0;
  iqp->buffer  = bp;
  iqp->rdptr   = bp;
  iqp->wrptr   = bp;
  iqp->top     = bp + size;
  iqp->notify  = infy;
  iqp->link    = link;
}

/**
 * @brief   Resets an input queue.
 * @details All the data in the input queue is erased and lost, any waiting
 *          thread is resumed with status @p Q_RESET.
 * @note    A reset operation can be used by a low level driver in order to
 *          obtain immediate attention from the high level layers.
 *
 * @param[in] iqp       pointer to an @p input_queue_t structure
 *
 * @iclass
 */
void chIQResetI(input_queue_t *iqp) {

  chDbgCheckClassI();

  iqp->rdptr = iqp->buffer;
  iqp->wrptr = iqp->buffer;
  iqp->counter = 0;
  chThdDequeueAllI(&iqp->waiting, Q_RESET);
}

/**
 * @brief   Input queue write.
 * @details A byte value is written into the low end of an input queue.
 *
 * @param[in] iqp       pointer to an @p input_queue_t structure
 * @param[in] b         the byte value to be written in the queue
 * @return              The operation status.
 * @retval Q_OK         if the operation has been completed with success.
 * @retval Q_FULL       if the queue is full and the operation cannot be
 *                      completed.
 *
 * @iclass
 */
msg_t chIQPutI(input_queue_t *iqp, uint8_t b) {

  chDbgCheckClassI();

  if (chIQIsFullI(iqp)) {
    return Q_FULL;
  }

  iqp->counter++;
  *iqp->wrptr++ = b;
  if (iqp->wrptr >= iqp->top) {
    iqp->wrptr = iqp->buffer;
  }

  chThdDequeueNextI(&iqp->waiting, Q_OK);

  return Q_OK;
}

/**
 * @brief   Input queue read with timeout.
 * @details This function reads a byte value from an input queue. If the queue
 *          is empty then the calling thread is suspended until a byte arrives
 *          in the queue or a timeout occurs.
 * @note    The callback is invoked before reading the character from the
 *          buffer or before entering the state @p CH_STATE_WTQUEUE.
 *
 * @param[in] iqp       pointer to an @p input_queue_t structure
 * @param[in] timeout   the number of ticks before the operation timeouts,
 *                      the following special values are allowed:
 *                      - @a TIME_IMMEDIATE immediate timeout.
 *                      - @a TIME_INFINITE no timeout.
 *                      .
 * @return              A byte value from the queue.
 * @retval Q_TIMEOUT    if the specified time expired.
 * @retval Q_RESET      if the queue has been reset.
 *
 * @api
 */
msg_t chIQGetTimeout(input_queue_t *iqp, systime_t timeout) {
  uint8_t b;

  chSysLock();
  if (iqp->notify != NULL) {
    iqp->notify(iqp);
  }

  while (chIQIsEmptyI(iqp)) {
    msg_t msg = chThdEnqueueTimeoutS(&iqp->waiting, timeout);
    if (msg < Q_OK) {
      chSysUnlock();
      return msg;
    }
  }

  iqp->counter--;
  b = *iqp->rdptr++;
  if (iqp->rdptr >= iqp->top) {
    iqp->rdptr = iqp->buffer;
  }
  chSysUnlock();

  return (msg_t)b;
}

/**
 * @brief   Input queue read with timeout.
 * @details The function reads data from an input queue into a buffer. The
 *          operation completes when the specified amount of data has been
 *          transferred or after the specified timeout or if the queue has
 *          been reset.
 * @note    The function is not atomic, if you need atomicity it is suggested
 *          to use a semaphore or a mutex for mutual exclusion.
 * @note    The callback is invoked before reading each character from the
 *          buffer or before entering the state @p CH_STATE_WTQUEUE.
 *
 * @param[in] iqp       pointer to an @p input_queue_t structure
 * @param[out] bp       pointer to the data buffer
 * @param[in] n         the maximum amount of data to be transferred, the
 *                      value 0 is reserved
 * @param[in] timeout   the number of ticks before the operation timeouts,
 *                      the following special values are allowed:
 *                      - @a TIME_IMMEDIATE immediate timeout.
 *                      - @a TIME_INFINITE no timeout.
 *                      .
 * @return              The number of bytes effectively transferred.
 *
 * @api
 */
size_t chIQReadTimeout(input_queue_t *iqp, uint8_t *bp,
                       size_t n, systime_t timeout) {
  qnotify_t nfy = iqp->notify;
  size_t r = 0;

  chDbgCheck(n > 0U);

  chSysLock();
  while (true) {
    if (nfy != NULL) {
      nfy(iqp);
    }

    while (chIQIsEmptyI(iqp)) {
      if (chThdEnqueueTimeoutS(&iqp->waiting, timeout) != Q_OK) {
        chSysUnlock();
        return r;
      }
    }

    iqp->counter--;
    *bp++ = *iqp->rdptr++;
    if (iqp->rdptr >= iqp->top) {
      iqp->rdptr = iqp->buffer;
    }
    chSysUnlock(); /* Gives a preemption chance in a controlled point.*/

    r++;
    if (--n == 0U) {
      return r;
    }

    chSysLock();
  }
}

/**
 * @brief   Initializes an output queue.
 * @details A Semaphore is internally initialized and works as a counter of
 *          the free bytes in the queue.
 * @note    The callback is invoked from within the S-Locked system state,
 *          see @ref system_states.
 *
 * @param[out] oqp      pointer to an @p output_queue_t structure
 * @param[in] bp        pointer to a memory area allocated as queue buffer
 * @param[in] size      size of the queue buffer
 * @param[in] onfy      pointer to a callback function that is invoked when
 *                      data is written to the queue. The value can be @p NULL.
 * @param[in] link      application defined pointer
 *
 * @init
 */
void chOQObjectInit(output_queue_t *oqp, uint8_t *bp, size_t size,
                    qnotify_t onfy, void *link) {

  chThdQueueObjectInit(&oqp->waiting);
  oqp->counter = size;
  oqp->buffer  = bp;
  oqp->rdptr   = bp;
  oqp->wrptr   = bp;
  oqp->top     = bp + size;
  oqp->notify  = onfy;
  oqp->link    = link;
}

/**
 * @brief   Resets an output queue.
 * @details All the data in the output queue is erased and lost, any waiting
 *          thread is resumed with status @p Q_RESET.
 * @note    A reset operation can be used by a low level driver in order to
 *          obtain immediate attention from the high level layers.
 *
 * @param[in] oqp       pointer to an @p output_queue_t structure
 *
 * @iclass
 */
void chOQResetI(output_queue_t *oqp) {

  chDbgCheckClassI();

  oqp->rdptr = oqp->buffer;
  oqp->wrptr = oqp->buffer;
  oqp->counter = chQSizeX(oqp);
  chThdDequeueAllI(&oqp->waiting, Q_RESET);
}

/**
 * @brief   Output queue write with timeout.
 * @details This function writes a byte value to an output queue. If the queue
 *          is full then the calling thread is suspended until there is space
 *          in the queue or a timeout occurs.
 * @note    The callback is invoked after writing the character into the
 *          buffer.
 *
 * @param[in] oqp       pointer to an @p output_queue_t structure
 * @param[in] b         the byte value to be written in the queue
 * @param[in] timeout   the number of ticks before the operation timeouts,
 *                      the following special values are allowed:
 *                      - @a TIME_IMMEDIATE immediate timeout.
 *                      - @a TIME_INFINITE no timeout.
 *                      .
 * @return              The operation status.
 * @retval Q_OK         if the operation succeeded.
 * @retval Q_TIMEOUT    if the specified time expired.
 * @retval Q_RESET      if the queue has been reset.
 *
 * @api
 */
msg_t chOQPutTimeout(output_queue_t *oqp, uint8_t b, systime_t timeout) {

  chSysLock();
  while (chOQIsFullI(oqp)) {
    msg_t msg = chThdEnqueueTimeoutS(&oqp->waiting, timeout);
    if (msg < Q_OK) {
      chSysUnlock();
      return msg;
    }
  }

  oqp->counter--;
  *oqp->wrptr++ = b;
  if (oqp->wrptr >= oqp->top) {
    oqp->wrptr = oqp->buffer;
  }

  if (oqp->notify != NULL) {
    oqp->notify(oqp);
  }
  chSysUnlock();

  return Q_OK;
}

/**
 * @brief   Output queue read.
 * @details A byte value is read from the low end of an output queue.
 *
 * @param[in] oqp       pointer to an @p output_queue_t structure
 * @return              The byte value from the queue.
 * @retval Q_EMPTY      if the queue is empty.
 *
 * @iclass
 */
msg_t chOQGetI(output_queue_t *oqp) {
  uint8_t b;

  chDbgCheckClassI();

  if (chOQIsEmptyI(oqp)) {
    return Q_EMPTY;
  }

  oqp->counter++;
  b = *oqp->rdptr++;
  if (oqp->rdptr >= oqp->top) {
    oqp->rdptr = oqp->buffer;
  }

  chThdDequeueNextI(&oqp->waiting, Q_OK);

  return (msg_t)b;
}

/**
 * @brief   Output queue write with timeout.
 * @details The function writes data from a buffer to an output queue. The
 *          operation completes when the specified amount of data has been
 *          transferred or after the specified timeout or if the queue has
 *          been reset.
 * @note    The function is not atomic, if you need atomicity it is suggested
 *          to use a semaphore or a mutex for mutual exclusion.
 * @note    The callback is invoked after writing each character into the
 *          buffer.
 *
 * @param[in] oqp       pointer to an @p output_queue_t structure
 * @param[in] bp        pointer to the data buffer
 * @param[in] n         the maximum amount of data to be transferred, the
 *                      value 0 is reserved
 * @param[in] timeout   the number of ticks before the operation timeouts,
 *                      the following special values are allowed:
 *                      - @a TIME_IMMEDIATE immediate timeout.
 *                      - @a TIME_INFINITE no timeout.
 *                      .
 * @return              The number of bytes effectively transferred.
 *
 * @api
 */
size_t chOQWriteTimeout(output_queue_t *oqp, const uint8_t *bp,
                        size_t n, systime_t timeout) {
  qnotify_t nfy = oqp->notify;
  size_t w = 0;

  chDbgCheck(n > 0U);

  chSysLock();
  while (true) {
    while (chOQIsFullI(oqp)) {
      if (chThdEnqueueTimeoutS(&oqp->waiting, timeout) != Q_OK) {
        chSysUnlock();
        return w;
      }
    }
    
    oqp->counter--;
    *oqp->wrptr++ = *bp++;
    if (oqp->wrptr >= oqp->top) {
      oqp->wrptr = oqp->buffer;
    }

    if (nfy != NULL) {
      nfy(oqp);
    }
    chSysUnlock(); /* Gives a preemption chance in a controlled point.*/

    w++;
    if (--n == 0U) {
      return w;
    }
    chSysLock();
  }
}
#endif  /* CH_CFG_USE_QUEUES == TRUE */

/** @} */