/*
 * Copyright 2019-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.
 */

#include <stdint.h>
#include <stdbool.h>
#include "audio_mixer.h"
#if !((defined(MIXER_INPUT_INTERFACE_CNT)) && (MIXER_INPUT_INTERFACE_CNT > 0))
    #error "MIXER_INPUT_INTERFACE_CNT is not defined, or isn't greater than zero"
#endif

#include "error.h"
#include "audio_internal.h"
#include "audio.h"
#include "audio_unified.h"
#include "audio_ringbuffer.h"
//#include "framework.h"

/* ---------------------------------------------------------------------------- */
/* Defines                                                                      */
/* ---------------------------------------------------------------------------- */

/* Data structure used for internal book-keeping for an input interface */
typedef struct _audio_mixer_interface {
    uint8_t buffer[MIXER_INPUT_RING_BUFFER_SIZE]; /* byte buffer used by ring buffer */
    phRingBuffer_t ringBuffer;                    /* ring buffer data structure */
    uint32_t fillLevel;                           /* number of samples written so far */
    uint8_t ampFactor;                            /* amplification factor */
    uint8_t index;                                /* index of the interface (can be used as its id) */
    bool isActive;                                /* is this interface being mixed (enough samples were buffered) */
} audio_mixer_interface_t __attribute__((aligned(4)));

static audio_mixer_interface_t gs_Interfaces[MIXER_INPUT_INTERFACE_CNT];

static uint8_t gs_ActiveCount;         /* number of input interfaces being mixed at the moment */
static mixer_state_changed_cb gs_ReadyStateChangedCb;

/* -------------------------------------------------------------------------
 * Local functions
 * ------------------------------------------------------------------------- */

static void SpaceWarningDpc(void *ctxt)
{
    uint8_t *index = (uint8_t *)ctxt;
    (void)index;  /* Make release build happy*/
    PERR("Space warning on interface %d", *index);
}

static void SpaceWarningCb(uint32_t Filling, void *pCtxt)
{
    (void)Filling;
    uint8_t *index = (uint8_t *)pCtxt;
    /* restart this audio stream */
    AUDIO_MIXER_StopInterface(*index);
    /* starts again automatically when enough samples have been written */

}

static void GetMixedSamples_32(int32_t *outBuffer, uint16_t nbSamples)
{
    uint8_t audioTxSampleSizeBytes = audio_GetTxSampleSizeBytes();

    for (uint16_t s = 0; s < nbSamples; s++) {
        int64_t mixedSample = 0;

        /* read the next sample of each active input interface, and mix those by adding their individual contributions */
        for (uint8_t i = 0; i < MIXER_INPUT_INTERFACE_CNT; i++) {
            if (gs_Interfaces[i].isActive) {
                int32_t sample = *((int32_t *)gs_Interfaces[i].ringBuffer.pReadPointer);
                //mixedSample += (((int64_t)sample * gs_Interfaces[i].ampFactor) >> MIXER_AMPLIFIER_RESOLUTION);
                mixedSample += sample;

                gs_Interfaces[i].ringBuffer.pReadPointer += audioTxSampleSizeBytes;
                if (gs_Interfaces[i].ringBuffer.pReadPointer > (uint8_t *)gs_Interfaces[i].ringBuffer.EndAddress) {
                    gs_Interfaces[i].ringBuffer.pReadPointer = (uint8_t *)gs_Interfaces[i].ringBuffer.StartAddress;
                }
            }
        }
        /* As DMA do not support 3 bytes transfer, 24 bit audio uses 32 bit transfer */
        /* write the mixed value into 'pRingBuffer' */
        outBuffer[s] = (int32_t)mixedSample;
    }
}

static void GetMixedSamples_16(int16_t *outBuffer, uint16_t nbSamples)
{
    uint8_t audioTxSampleSizeBytes = audio_GetTxSampleSizeBytes();

    for (uint16_t s = 0; s < nbSamples; s++) {
        int32_t mixedSample = 0;

        /* read the next sample of each active input interface, and mix those by adding their individual contributions */
        for (uint8_t i = 0; i < MIXER_INPUT_INTERFACE_CNT; i++) {
            if (gs_Interfaces[i].isActive) {
                int16_t sample = *((int16_t *)gs_Interfaces[i].ringBuffer.pReadPointer);
                mixedSample += ((int32_t)sample * gs_Interfaces[i].ampFactor) >> MIXER_AMPLIFIER_RESOLUTION;

                gs_Interfaces[i].ringBuffer.pReadPointer += audioTxSampleSizeBytes;
                if (gs_Interfaces[i].ringBuffer.pReadPointer > (uint8_t *)gs_Interfaces[i].ringBuffer.EndAddress) {
                    gs_Interfaces[i].ringBuffer.pReadPointer = (uint8_t *)gs_Interfaces[i].ringBuffer.StartAddress;
                }
            }
        }
        /* write the mixed value into 'pRingBuffer' */
        outBuffer[s] = (int16_t)mixedSample;
    }
}

/* -------------------------------------------------------------------------
 * Public functions
 * ------------------------------------------------------------------------- */

void AUDIO_MIXER_Init(void)
{
    /* initialize all internal book-keeping variables */
    uint8_t audioTxSampleSizeBytes = audio_GetTxSampleSizeBytes();
    gs_ActiveCount = 0;
    for (uint8_t i = 0; i < MIXER_INPUT_INTERFACE_CNT; i++) {
        gs_Interfaces[i].index = i;
        gs_Interfaces[i].fillLevel = 0;
        gs_Interfaces[i].isActive = false;
        gs_Interfaces[i].ampFactor = (uint8_t)(MIXER_MAX_AMP / MIXER_INPUT_INTERFACE_CNT);
        audio_ringbuffer_Init(
            &gs_Interfaces[i].ringBuffer,
            &gs_Interfaces[i].buffer[0],
            sizeof(gs_Interfaces[i].buffer));
        audio_ringbuffer_ConfigureFillingWarning(
            &gs_Interfaces[i].ringBuffer,
            TX_AUDIO_DMA_TRANSFER_SIZE(audioTxSampleSizeBytes),
            SpaceWarningCb,
            &gs_Interfaces[i].index);
    }
    gs_ReadyStateChangedCb = NULL;
}

void AUDIO_MIXER_Deinit(void)
{
}

void AUDIO_MIXER_GetMixingFactors(uint8_t *amps)
{
    for (uint8_t i = 0; i < MIXER_INPUT_INTERFACE_CNT; i++) {
        amps[i] = gs_Interfaces[i].ampFactor;
    }
}

error_t AUDIO_MIXER_SetMixingFactors(uint8_t *amps)
{
    uint32_t ampsSum = 0;
    for (uint8_t i = 0; i < MIXER_INPUT_INTERFACE_CNT; i++) {
        if (amps[i] > MIXER_MAX_AMP) {  /* range check for amplification factor */
            return kERROR_Fail;
        }
        gs_Interfaces[i].ampFactor = amps[i];
        ampsSum += amps[i];
    }

    /* sum of all amplification factors should equal MIXER_MAX_AMP */
    if (ampsSum != MIXER_MAX_AMP) {
        return kERROR_Fail;
    }
    return kERROR_Ok;
}

void AUDIO_MIXER_RegisterStateCb(mixer_state_changed_cb cb)
{
    gs_ReadyStateChangedCb = cb;
}

void AUDIO_MIXER_WriteSamples(uint8_t interfaceIdx, uint8_t *pBufferInput, uint16_t nbSamples, uint8_t bufferInputFormat)
{
    audio_mixer_interface_t *inInterface = &gs_Interfaces[interfaceIdx];
    uint8_t audioTxSampleSizeBytes = audio_GetTxSampleSizeBytes();

    /* store the audio sample in the ring buffer for this interface */
    audio_ringbuffer_Write(&inInterface->ringBuffer, pBufferInput, (nbSamples * bufferInputFormat),
                           bufferInputFormat, audioTxSampleSizeBytes);

    /* if this interface has buffered enough samples, mark it to be used during mixing */
    inInterface->fillLevel += (nbSamples * audioTxSampleSizeBytes);
    //if (!inInterface->isActive && (inInterface->fillLevel >= MIXER_TARGET_BUFFER_FILLING(audioTxSampleSizeBytes))) {
    if (!inInterface->isActive && (inInterface->fillLevel >= MIXER_INPUT_RING_BUFFER_SIZE/2)) {
        inInterface->isActive = true;
        gs_ActiveCount++;

        /* if this was the first interface to become active, then the mixer is ready to start outputting samples */
        if ((gs_ActiveCount == 1) && (gs_ReadyStateChangedCb != NULL)) {
            gs_ReadyStateChangedCb(true);
        }
    }
}

void AUDIO_MIXER_GetMixedSamples(void *outBuffer, uint16_t nbSamples)
{
    if (g_AudioServiceCtxt.userConfig.audioIfWordLength == AUDIO_SERVICE_AUDIO_IF_WORD_LENGTH_32_BITS) {
        GetMixedSamples_32((int32_t *)outBuffer, nbSamples);
    } else {
        GetMixedSamples_16((int16_t *)outBuffer, nbSamples);
    }
}

void AUDIO_MIXER_GetEmptySamples(void *outBuffer, uint16_t nbSamples)
{
    uint8_t audioTxSampleSizeBytes = audio_GetTxSampleSizeBytes();
    uint16_t nbBytesTotal = nbSamples * audioTxSampleSizeBytes;

    /* write the empty samples to the output buffer */
    memset(outBuffer, 0, nbBytesTotal);

    /* update the readpointers of the active input interfaces */
    for (uint8_t i = 0; i < MIXER_INPUT_INTERFACE_CNT; i++) {
        if (gs_Interfaces[i].isActive) {
            uint32_t readPointer = (uint32_t)gs_Interfaces[i].ringBuffer.pReadPointer - gs_Interfaces[i].ringBuffer.StartAddress;
            readPointer = (readPointer + nbBytesTotal) % gs_Interfaces[i].ringBuffer.BufferSize;
            gs_Interfaces[i].ringBuffer.pReadPointer = (uint8_t *)(readPointer + gs_Interfaces[i].ringBuffer.StartAddress);
        }
    }
}

void AUDIO_MIXER_StopInterface(uint8_t interfaceIdx)
{
    audio_ringbuffer_Reset(&gs_Interfaces[interfaceIdx].ringBuffer);
    gs_Interfaces[interfaceIdx].fillLevel = 0;
    /* the 'ampFactor' is not being reset */

    if (gs_Interfaces[interfaceIdx].isActive) {
        gs_Interfaces[interfaceIdx].isActive = false;
        gs_ActiveCount--;
        /* if all input interfaces are stopped, the mixer is no longer ready to output samples */
        if ((gs_ActiveCount == 0) && (gs_ReadyStateChangedCb != NULL)) {
            gs_ReadyStateChangedCb(false);
        }
    }
}

void AUDIO_MIXER_StopAllInterfaces(void)
{
    for (uint8_t i = 0; i < MIXER_INPUT_INTERFACE_CNT; i++) {
        AUDIO_MIXER_StopInterface(i);
    }
}

uint32_t AUDIO_MIXER_GetBufferSize(uint8_t interfaceIdx)
{
    return gs_Interfaces[interfaceIdx].ringBuffer.BufferSize;
}

uint32_t AUDIO_MIXER_GetBufferFilling(uint8_t interfaceIdx)
{
    return audio_ringbuffer_GetFilling(&gs_Interfaces[interfaceIdx].ringBuffer);
}
