/*
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * Copyright 2016-2017 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <stdio.h>
#include <string.h>
#include "app.h"
#include "fsl_dmamux.h"
#include "fsl_sai_edma.h"
#include "fsl_cs42888.h"
#include "fsl_codec_common.h"
#include "fsl_sd.h"
#include "fsl_debug_console.h"
#include "ff.h"
#include "ffconf.h"
#include "diskio.h"
#include "fsl_sd_disk.h"
#include "pin_mux.h"
#include "clock_config.h"
#include "board.h"
#include "sdmmc_config.h"
#include "fsl_common.h"

#include "FLAC/assert.h"
#include "FLAC/stream_decoder.h"
#include "FLAC/ordinals.h"
#include "share/compat.h"
/*******************************************************************************
 * Definitions
 ******************************************************************************/
#define BUFFER_NUMBER 3
/* demo flac block size */
#define FLAC_BLOCK_SIZE 4608
/* demo audio sample rate */
#define DEMO_AUDIO_SAMPLE_RATE (kSAI_SampleRate48KHz)
/* demo audio master clock */
#define DEMO_AUDIO_MASTER_CLOCK DEMO_SAI_CLK_FREQ
/* demo audio data channel */
#define DEMO_AUDIO_DATA_CHANNEL (8U)
/* demo audio bitwidth */
#define DEMO_AUDIO_BIT_WIDTH kSAI_WordWidth32bits
#define DEMO_FRMAE_SYNC_LEN  kSAI_FrameSyncLenOneBitClk
#define DEMO_SAI_CHANNEL     kSAI_Channel0Mask
/* demo audio buffer size */   
#define BUFFER_SIZE FLAC_BLOCK_SIZE * 4 * DEMO_AUDIO_DATA_CHANNEL
/*******************************************************************************
 * Prototypes
 ******************************************************************************/
static void callback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
static void DEMO_FlacDecode(void);
static status_t DEMO_MountFileSystem(void);
extern void BORAD_CodecReset(bool state);
static void DEMO_InitCS42888(void);
/*******************************************************************************
 * Variables
 ******************************************************************************/
AT_NONCACHEABLE_SECTION_ALIGN(static uint8_t s_buffer[4608 * 4 * 8 * BUFFER_NUMBER], 4);
AT_NONCACHEABLE_SECTION_INIT(sai_transfer_t xfer) = {0};
AT_NONCACHEABLE_SECTION_INIT(sai_edma_handle_t txHandle) = {0};
edma_handle_t dmaHandle                                  = {0};
extern codec_config_t boardCodecConfig;

#if (defined(FSL_FEATURE_SAI_HAS_MCR) && (FSL_FEATURE_SAI_HAS_MCR)) || \
    (defined(FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER) && (FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER))
sai_master_clock_t mclkConfig = {
#if defined(FSL_FEATURE_SAI_HAS_MCR) && (FSL_FEATURE_SAI_HAS_MCR)
    mclkOutputEnable = true,
#if !(defined(FSL_FEATURE_SAI_HAS_NO_MCR_MICS) && (FSL_FEATURE_SAI_HAS_NO_MCR_MICS))
    mclkSource = kSAI_MclkSourceSysclk,
#endif
#endif
};
#endif

/*! @brief Card descriptor. */
extern sd_card_t g_sd;
static FATFS s_fileSystem;
static FIL s_fileObject;
static volatile bool s_saiTransferFinish = false;
codec_handle_t codecHandle;

static FLAC__StreamDecoder *g_decoder = 0;
static const uint8_t *fileName = "TEST.FLA";
volatile static uint32_t sample_count = 0;

static uint32_t volatile decode_index = 0;
volatile bool isFinished = false;

volatile static uint32_t blocksize;
static uint32_t metadata_total_samples = 0;
static unsigned metadata_sample_rate = 0;
static unsigned metadata_channels = 0;
static unsigned metadata_bps = 0;

/*******************************************************************************
 * Code
 ******************************************************************************/
static void callback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
{
    if (kStatus_SAI_RxError == status)
    {
    }
    else
    {        
        s_saiTransferFinish = true;
    }
}

FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data)
{
    uint32_t i,j,k;
    blocksize = frame->header.blocksize;
    
    /* Decode odd block */
    if(0 == decode_index % BUFFER_NUMBER)
    {
        for(i = k = 0; i < blocksize; i++)
        {
            for(j = 0; j < metadata_channels; j++, k++)
            {        
                  s_buffer[4*k+0] = 0;
                  s_buffer[4*k+1] = buffer[j][i] & 0x000000ff;
                  s_buffer[4*k+2] = (buffer[j][i] & 0x0000ff00)>>8;
                  s_buffer[4*k+3] = (buffer[j][i] & 0x00ff0000)>>16;
            }
        }
    }
    
    /* Decode even block */
    else if(1 == decode_index % BUFFER_NUMBER)
    {
        for(i = k = 0; i < blocksize; i++)
        {
            for(j = 0; j < metadata_channels; j++, k++)
            {             
                  s_buffer[4*k+0+BUFFER_SIZE] = 0;
                  s_buffer[4*k+1+BUFFER_SIZE] = buffer[j][i] & 0x000000ff;
                  s_buffer[4*k+2+BUFFER_SIZE] = (buffer[j][i] & 0x0000ff00)>>8;
                  s_buffer[4*k+3+BUFFER_SIZE] = (buffer[j][i] & 0x00ff0000)>>16;
            }
        }
    }
    else if(2 == decode_index % BUFFER_NUMBER)
    {
        for(i = k = 0; i < blocksize; i++)
        {            
            for(j = 0; j < metadata_channels; j++, k++)
            {             
                  s_buffer[4*k+0+2*BUFFER_SIZE] = 0;
                  s_buffer[4*k+1+2*BUFFER_SIZE] = buffer[j][i] & 0x000000ff;
                  s_buffer[4*k+2+2*BUFFER_SIZE] = (buffer[j][i] & 0x0000ff00)>>8;
                  s_buffer[4*k+3+2*BUFFER_SIZE] = (buffer[j][i] & 0x00ff0000)>>16;
            }
        }
    }

    if(decode_index == 0)
    {
        s_saiTransferFinish = true;
    }
    
    if(decode_index == 1)
    {
        xfer.data = (uint8_t *)&s_buffer[BUFFER_SIZE * 0];
        xfer.dataSize = BUFFER_SIZE;
        if (kStatus_Success != SAI_TransferSendEDMA(DEMO_SAI, &txHandle, &xfer))
        {
        
        }
        s_saiTransferFinish = true;
    }
    
    if(decode_index >= 2)
    {
        xfer.data = (uint8_t *)&s_buffer[BUFFER_SIZE * ( (decode_index+2) % BUFFER_NUMBER)];
        xfer.dataSize = BUFFER_SIZE;
        if (kStatus_Success != SAI_TransferSendEDMA(DEMO_SAI, &txHandle, &xfer))
        {
        
        }
    }
    
    while(s_saiTransferFinish != true);
    s_saiTransferFinish = false;
    
    decode_index++;

    return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}

void metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data)
{
    (void)decoder, (void)client_data;
    
    /* print some stats */
    if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
        /* save for later */
        metadata_total_samples = metadata->data.stream_info.total_samples;
        metadata_sample_rate = metadata->data.stream_info.sample_rate;
        metadata_channels = metadata->data.stream_info.channels;
        metadata_bps = metadata->data.stream_info.bits_per_sample;

        flac_printf("sample rate    : %u Hz\r\n", metadata_sample_rate);
        flac_printf("channels       : %u\r\n", metadata_channels);
        flac_printf("bits per sample: %u\r\n", metadata_bps);
        flac_printf("total samples  : %d\r\n", metadata_total_samples);
    }
    
    if(8 != metadata_channels)
    {
        PRINTF("The demo only support for 8 channel flac decoder!\r\n");
        assert(0);
    }
    
    if(24 != metadata_bps)
    {
        PRINTF("The demo only support for 24 bits per sample!\r\n");
        assert(0);
    }

}

void error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data)
{
    (void)decoder, (void)client_data;

    flac_printf("Got error callback: %s\n", FLAC__StreamDecoderErrorStatusString[status]);
}
/*******************************************************************************
 * Code
 ******************************************************************************/
/*!
 * @brief Main function
 */
int main(void)
{
    edma_config_t dmaConfig = {0};
    sai_transceiver_t saiConfig;
   
    BOARD_InitHardware();
    
    PRINTF("\r\nFlac7.1 Decode and SAI edma TDM example started.\n\r");

    /* Create EDMA handle */
    /*
     * dmaConfig.enableRoundRobinArbitration = false;
     * dmaConfig.enableHaltOnError = true;
     * dmaConfig.enableContinuousLinkMode = false;
     * dmaConfig.enableDebugMode = false;
     */
    EDMA_GetDefaultConfig(&dmaConfig);
    EDMA_Init(EXAMPLE_DMA, &dmaConfig);
    EDMA_CreateHandle(&dmaHandle, EXAMPLE_DMA, EXAMPLE_CHANNEL);
    
#if defined(FSL_FEATURE_SOC_DMAMUX_COUNT) && FSL_FEATURE_SOC_DMAMUX_COUNT
    DMAMUX_Init(DMAMUX0);
    DMAMUX_SetSource(DMAMUX0, EXAMPLE_CHANNEL, EXAMPLE_SAI_TX_SOURCE);
    DMAMUX_EnableChannel(DMAMUX0, EXAMPLE_CHANNEL);
#endif    

    if (DEMO_MountFileSystem() != kStatus_Success)
    {
        PRINTF("Mount file system failed, make sure card is formatted.\r\n");
        return -1;
    }    

    /* SAI init */
    SAI_Init(DEMO_SAI);
    SAI_TransferTxCreateHandleEDMA(DEMO_SAI, &txHandle, callback, NULL, &dmaHandle);

    /* TDM mode configurations */
    SAI_GetTDMConfig(&saiConfig, DEMO_FRMAE_SYNC_LEN, DEMO_AUDIO_BIT_WIDTH, DEMO_AUDIO_DATA_CHANNEL, DEMO_SAI_CHANNEL);
    
    saiConfig.frameSync.frameSyncEarly = true;
    
    SAI_TransferTxSetConfigEDMA(DEMO_SAI, &txHandle, &saiConfig);

    /* set bit clock divider */
    SAI_TxSetBitClockRate(DEMO_SAI, DEMO_AUDIO_MASTER_CLOCK, DEMO_AUDIO_SAMPLE_RATE, DEMO_AUDIO_BIT_WIDTH,
                          DEMO_AUDIO_DATA_CHANNEL);

    CLOCK_GetFreq(kCLOCK_AudioPllClk);
    
    /* CS42888 initialization */
    DEMO_InitCS42888();
    
    PRINTF("\r\nStart Flac7.1 Decode and Playback.\n\r");

    DEMO_FlacDecode();
    
    f_close(&s_fileObject);
    /* Once transfer finish, disable SAI instance. */
    SAI_TransferAbortSendEDMA(DEMO_SAI, &txHandle);
    SAI_Deinit(DEMO_SAI);
    PRINTF("\r\nFlac7.1 Audio file playback example finished.\n\r ");
    while (1)
    {
    }
}
 
static void DEMO_FlacDecode(void)
{
    static FLAC__bool ok = true;
    static FLAC__StreamDecoderInitStatus init_status;
    
    /* Creat a new decoder. */
    if((g_decoder = FLAC__stream_decoder_new()) == NULL)
    {
        flac_printf("ERROR: allocating decoder.\r\n");
        assert(false);
    }

    (void)FLAC__stream_decoder_set_md5_checking(g_decoder, true);

    init_status = FLAC__stream_decoder_init_file(g_decoder, fileName, write_callback, metadata_callback, error_callback, &s_fileObject);
    if(init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK)
    {
        flac_printf("ERROR: initializing decoder: %s\n", FLAC__StreamDecoderInitStatusString[init_status]);
        ok = false;
    }

    if(ok) {
        ok = FLAC__stream_decoder_process_until_end_of_stream(g_decoder);
        flac_printf("decoding: %s\r\n", ok? "succeeded" : "FAILED");
        flac_printf("   state: %s\r\n", FLAC__StreamDecoderStateString[FLAC__stream_decoder_get_state(g_decoder)]);
    }

    FLAC__stream_decoder_delete(g_decoder);
}

static status_t sdcardWaitCardInsert(void)
{
    BOARD_SD_Config(&g_sd, NULL, BOARD_SDMMC_SD_HOST_IRQ_PRIORITY, NULL);

    /* SD host init function */
    if (SD_HostInit(&g_sd) != kStatus_Success)
    {
        PRINTF("\r\nSD host init fail\r\n");
        return kStatus_Fail;
    }
    /* power off card */
    SD_SetCardPower(&g_sd, false);

    PRINTF("\r\nPlease insert a SDCARD into board, make sure the sdcard is format to FAT32 format and put the 7.1flac audio file into the sdcard.\r\n");

    /* wait card insert */
    if (SD_PollingCardInsert(&g_sd, kSD_Inserted) == kStatus_Success)
    {
        PRINTF("\r\nCard inserted.\r\n");
        /* power on the card */
        SD_SetCardPower(&g_sd, true);
    }
    else
    {
        PRINTF("\r\nCard detect fail.\r\n");
        return kStatus_Fail;
    }

    return kStatus_Success;
}

static status_t DEMO_MountFileSystem(void)
{
    const TCHAR driverNumberBuffer[3U] = {SDDISK + '0', ':', '/'};
    FRESULT error;

    if (sdcardWaitCardInsert() != kStatus_Success)
    {
        return kStatus_Fail;
    }

    if (f_mount(&s_fileSystem, driverNumberBuffer, 0U))
    {
        PRINTF("Mount volume failed.\r\n");
        return kStatus_Fail;
    }

#if (FF_FS_RPATH >= 2U)
    error = f_chdrive((char const *)&driverNumberBuffer[0U]);
    if (error)
    {
        PRINTF("Change drive failed.\r\n");
        return kStatus_Fail;
    }
#endif

    return kStatus_Success;
}

static void DEMO_InitCS42888(void)
{
    if (CODEC_Init(&codecHandle, &boardCodecConfig) != kStatus_Success)
    {
        PRINTF("CODEC_Init failed!\r\n");
        assert(false);
    }

    PRINTF("\r\nCS42888 codec Init Done.\r\n");
}