/*
 * Copyright 2017-2020 NXP
 * This software is owned or controlled by NXP and may only be used strictly
 * in accordance with the applicable license terms.  By expressly accepting
 * such terms or by downloading, installing, activating and/or otherwise using
 * the software, you are agreeing that you have read, and that you agree to
 * comply with and are bound by, such license terms.  If you do not agree to
 * be bound by the applicable license terms, then you may not retain, install,
 * activate or otherwise use the software.
 */

/**
 * @defgroup KL_MODULES_AUDIOSERVICE_RINGBUF audio_ringbuffer
 * @ingroup KL_MODULES_AUDIOSERVICE
 * audio_ringbuffer initializes and uses Ring buffer for transferring audio from USB to NxH via DMA.
 * This is a sub module of @ref KL_MODULES_AUDIOSERVICE audio
 * @{
 */

/* ---------------------------------------------------------------------------- */
/* Include files                                                                */
/* ---------------------------------------------------------------------------- */
#include <stdint.h>
#include "audio_internal.h"
#include "audio_ringbuffer.h"

/* -------------------------------------------------------------------------
 * Local functions
 * ------------------------------------------------------------------------- */
/**
 * Checks if the filling margins of the given ring buffer are exceeded.
 * If so, this function calls the filling warning callback function.
 */
static void audio_ringbuffer_CheckFillingWarning(phRingBuffer_t *spRingBuffer)
{
    if (spRingBuffer->pFillingWarningCb != NULL) {
        uint32_t Filling = audio_ringbuffer_GetFilling(spRingBuffer);
        if ((Filling > (spRingBuffer->BufferSize - spRingBuffer->FillingMargin))
            || (Filling < spRingBuffer->FillingMargin)) {
            spRingBuffer->pFillingWarningCb(Filling, spRingBuffer->pFillingWarningCbCtxt);
        }
    }
}

inline static void audio_ringbuffer_CheckAndResetWritePointer(phRingBuffer_t *spRingBuffer)
{
    /* Checks for the end of Ring buffer*/
    if (((uint32_t)spRingBuffer->pWritePointer) > spRingBuffer->EndAddress) {
        /* send to the beginning the pointer - Ring Buffer*/
        spRingBuffer->pWritePointer = ((uint8_t *)spRingBuffer->StartAddress);
    }
}

/* -------------------------------------------------------------------------
 * Public functions
 * ------------------------------------------------------------------------- */
/**
 * Initialize the ring buffer.
 */
void audio_ringbuffer_Init(phRingBuffer_t *spRingBuffer, uint8_t *pStartAddress, uint32_t BufferSize)
{
    spRingBuffer->StartAddress = (uint32_t)pStartAddress;
    if (BufferSize != 0) {
        spRingBuffer->EndAddress = ((uint32_t)(pStartAddress) + (uint32_t)(BufferSize) - (uint32_t)(1));
    }
    spRingBuffer->BufferSize = BufferSize;
    spRingBuffer->pReadPointer = pStartAddress;
    spRingBuffer->pWritePointer = pStartAddress;
    spRingBuffer->FillingMargin = 0;
    spRingBuffer->pFillingWarningCb = NULL;
    spRingBuffer->pFillingWarningCbCtxt = NULL;
}

/**
 * Configure the filling warning callback function to be called when the margin is
 * exceeded (overflow or underflow). The margin is symmetrical.
 */
void audio_ringbuffer_ConfigureFillingWarning(phRingBuffer_t *spRingBuffer, uint32_t Margin, pphRingBuffer_FillingWarningCb_t pCb,
                                              void *pCtxt)
{
    spRingBuffer->FillingMargin = Margin;
    spRingBuffer->pFillingWarningCb = pCb;
    spRingBuffer->pFillingWarningCbCtxt = pCtxt;
}

/**
 * Write several bytes into the ring buffer.
 */
void audio_ringbuffer_Write(phRingBuffer_t *spRingBuffer, uint8_t *pDataIn, uint32_t SizeOfDataToWrite,
                            const uint8_t DataInFormat, const uint8_t RingBufferFormat)
{
    /* Check if pointers are NULL */
    if ((pDataIn != 0) && (spRingBuffer->pWritePointer != 0)) {
        if (DataInFormat == AUDIO_IF_WORD_LENGTH_3_BYTES && RingBufferFormat == AUDIO_IF_WORD_LENGTH_4_BYTES) {
            while (SizeOfDataToWrite) {
                *(spRingBuffer->pWritePointer++) = 0;
                *(spRingBuffer->pWritePointer++) = *pDataIn++;
                *(spRingBuffer->pWritePointer++) = *pDataIn++;
                *(spRingBuffer->pWritePointer++) = *pDataIn++;

                audio_ringbuffer_CheckAndResetWritePointer(spRingBuffer);
                SizeOfDataToWrite -= AUDIO_IF_WORD_LENGTH_3_BYTES;
            }
        } else if (DataInFormat == AUDIO_IF_WORD_LENGTH_2_BYTES && RingBufferFormat == AUDIO_IF_WORD_LENGTH_4_BYTES) {
            while (SizeOfDataToWrite) {
                *(uint16_t *)(spRingBuffer->pWritePointer) = (uint16_t)0;
                spRingBuffer->pWritePointer += AUDIO_IF_WORD_LENGTH_2_BYTES;

                *(uint16_t *)(spRingBuffer->pWritePointer) = *(uint16_t *)pDataIn;
                spRingBuffer->pWritePointer += AUDIO_IF_WORD_LENGTH_2_BYTES;
                pDataIn += AUDIO_IF_WORD_LENGTH_2_BYTES;

                audio_ringbuffer_CheckAndResetWritePointer(spRingBuffer);
                SizeOfDataToWrite -= AUDIO_IF_WORD_LENGTH_2_BYTES;
            }
        } else if (DataInFormat == AUDIO_IF_WORD_LENGTH_2_BYTES && RingBufferFormat == AUDIO_IF_WORD_LENGTH_2_BYTES) {
            while (SizeOfDataToWrite) {
                *(uint16_t *)(spRingBuffer->pWritePointer) = *(uint16_t *)pDataIn;
                spRingBuffer->pWritePointer += AUDIO_IF_WORD_LENGTH_2_BYTES;
                pDataIn += AUDIO_IF_WORD_LENGTH_2_BYTES;

                audio_ringbuffer_CheckAndResetWritePointer(spRingBuffer);
                SizeOfDataToWrite -= AUDIO_IF_WORD_LENGTH_2_BYTES;
            }
        } else {
            PERR("Format not supported DataInFormat:%d, RingBufferFormat:%d", DataInFormat, RingBufferFormat);
        }
    }

    /* call filling warning callback if needed */
    audio_ringbuffer_CheckFillingWarning(spRingBuffer);
}

/**
 * Resets the ring buffer to start address
 */
void audio_ringbuffer_Reset(phRingBuffer_t *spRingBuffer)
{
    spRingBuffer->pReadPointer = (uint8_t *)spRingBuffer->StartAddress;
    spRingBuffer->pWritePointer = (uint8_t *)spRingBuffer->StartAddress;
}

/**
 * Returns the current space in use.
 * The returned value is not valid in case of overflow or underflow.
 */
uint32_t audio_ringbuffer_GetFilling(phRingBuffer_t *spRingBuffer)
{
    uint32_t SpaceInUse, TmpReadPointer, TmpWritePointer;

    /*
     * Get read- and write pointer only once to get fixed values for both.
     * If we would not do this, so we retrieve their values multiple times, the calculation can go wrong when the pointers are
     * being modified from within another context, such as 2 different ISRs. See artf704661 for more info.
     * An interrupt could still occur in between retrieving the values for both pointers, such that they're not perfectly in-sync.
     * In our implementation, this error is limited to the size of one audio sample, so we prefer that approach over using a
     * critical section.
     */
    TmpReadPointer = (uint32_t)spRingBuffer->pReadPointer;
    TmpWritePointer = (uint32_t)spRingBuffer->pWritePointer;

    /* calculate current space in use */
    if (TmpWritePointer >= TmpReadPointer) {
        SpaceInUse = (TmpWritePointer - TmpReadPointer);
    } else {
        SpaceInUse = (spRingBuffer->BufferSize - (TmpReadPointer - TmpWritePointer));
    }

    return SpaceInUse;
}

/**
 * Read several bytes from the ring buffer
 */
void audio_ringbuffer_Read(phRingBuffer_t *spRingBuffer, uint8_t *pDataOut, uint32_t DataToRead)
{
    /* Check if pointers are NULL */
    if ((pDataOut != 0) && (spRingBuffer->pReadPointer != 0)) {
        while (DataToRead) {
            /* Copy data */
            *pDataOut = *(spRingBuffer->pReadPointer);
            /* Increments the pointers*/
            spRingBuffer->pReadPointer++;
            pDataOut++;
            /* Decrements the count*/
            DataToRead--;
            /* Checks for the end of Ring buffer*/
            if (((uint32_t)spRingBuffer->pReadPointer) > spRingBuffer->EndAddress) {
                /* send to the beginning the pointer  - Ring Buffer*/
                spRingBuffer->pReadPointer = ((uint8_t *)spRingBuffer->StartAddress);
            }
        }
    }

    /* call filling warning callback if needed */
    audio_ringbuffer_CheckFillingWarning(spRingBuffer);
}
/** @} */
