/**************************************************************************
 * Copyright (C) 2012-2021  Unisound
 *
 * This program 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
 * any later version.
 *
 * This program 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 **************************************************************************
 *
 * Description : lvp_voice_player.c
 * Author      : yzs.unisound.com
 * Date        : 2021.03.01
 *
 **************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <driver/uni_delay.h>
#include <driver/uni_gpio.h>
#include <driver/uni_audio_out.h>
#include <driver/uni_audio_out/uni_audio_out_v2.h>
#include <driver/uni_flash.h>
#include <lvp_buffer.h>
#include <lvp_queue.h>
#include <driver/uni_timer.h>
#include "lvp_voice_player.h"
#include "decoder/base_decoder.h"
#include "types.h"
#include <board_misc_config.h>

#define MAX_DECODE_LEN 100
#define MAX_PCM_LEN PCM_16K_FRAME_LEN
#define COUNT 6

__attribute__((section(".audio_out")))static unsigned char lvp_play_buffer[MAX_PCM_LEN * COUNT] __attribute__((aligned(16)));
static int handle = -1;
static unsigned char *user_pcm_buffer;
static int user_pcm_buffer_len;

typedef struct {
    const unsigned char *resource_position;
    int sample_rate;
    int channels;
    int interlace;
    int len;
} LVP_PLAYER_VOICE;


#define LVP_VOICE_PLAYER_QUEUE_LEN 8
static unsigned char s_voice_player_event_queue_buffer[LVP_VOICE_PLAYER_QUEUE_LEN * sizeof(LVP_PLAYER_VOICE)];
LVP_QUEUE s_voice_player_event_queue;

#define LVP_VOICE_PLAYER_FRAME_COUNT (MAX_PCM_LEN * COUNT / MAX_PCM_LEN)
static unsigned char s_voice_player_frame_queue_buffer[LVP_VOICE_PLAYER_FRAME_COUNT * sizeof(int)];
LVP_QUEUE s_voice_player_frame_queue;

typedef enum {
    TYPE_PCM = 0,
    TYPE_WAV = 1,
    TYPE_OPUS = 8,
    TYPE_AMR = 9,
    TYPE_FILL = 1000,
} VOICE_TYPE;


typedef struct {
    PLAYER_STATUS status;
    int mute;
    int volume;
} LVP_PLAYER_INFO;

typedef struct {
    int sample_rate;
    int channels;
    int bits;
    int bytes_rate;
    int interlace;
    int endian;
    VOICE_TYPE decoder;
    const unsigned char *resource_position;
    unsigned int enc_frame_size;
    unsigned int dec_frame_size;
    unsigned int play_offset;
    unsigned int play_pos;
    unsigned int voice_size;
} LVP_VOICE_INFO;

LVP_PLAYER_INFO player_info = {
    .status = PLAYER_STATUS_STOP,
    .mute = 0,
    .volume = 30,
};
LVP_VOICE_INFO voice_info;
LvpVoicePlayerEventCallback event_cb;

int LvpVoicePlayerPlay(const unsigned char *resource_position)
{
    LVP_PLAYER_VOICE voice_event;
    if (resource_position == NULL)
        return -1;
    voice_event.resource_position = resource_position;
    voice_event.len = 0;
    voice_event.sample_rate = 0;
    voice_event.channels = 0;
    if (LvpQueuePut(&s_voice_player_event_queue, (const unsigned char *)&voice_event))
        return 0;
    return -1;
}

int LvpVoicePlayerSetUserPcmBuffer(unsigned char *resource_position, int len)
{
    if (resource_position == NULL || len <= 0)
        return -1;
    user_pcm_buffer = resource_position;
    user_pcm_buffer_len = len;
    return 0;
}

void LvpVoicePlayerClearUserPcmBuffer(void)
{
    user_pcm_buffer = NULL;
    user_pcm_buffer_len = 0;
}

int LvpVoicePlayerPlayPcm(const unsigned char *resource_position, int len, int sample_rate, int channels, int interlace)
{
    LVP_PLAYER_VOICE voice_event;
    voice_event.resource_position = resource_position;
    voice_event.len = len;
    voice_event.sample_rate = sample_rate;
    voice_event.channels = channels;
    voice_event.interlace = interlace;
    if (resource_position == NULL)
        return -1;
    if (LvpQueuePut(&s_voice_player_event_queue, (const unsigned char *)&voice_event)) {
        return 0;
    }
    return -1;
}

static void lvp_player_clear_frame_buffer(void)
{
    int play_saddr;
    int frame_num = LvpQueueGetDataNum(&s_voice_player_frame_queue);
    for (int i = 0; i < frame_num; i++)
        LvpQueueGet(&s_voice_player_frame_queue, (unsigned char *)&play_saddr);
}

static void lvp_audio_out_exit(void)
{
    LvpVoicePlayerMute();
    player_info.status = PLAYER_STATUS_STOP;
    lvp_player_clear_frame_buffer();
}

static void lvp_player_callback(unsigned int saddr, unsigned int eaddr)
{
    int play_saddr;
    UNI_AUDIO_OUT_FRAME frame;
    
    if (LvpQueueIsEmpty(&s_voice_player_frame_queue)) {
        return;
    }

    if (player_info.status == PLAYER_STATUS_STOP) {
        lvp_player_clear_frame_buffer();
        return;
    }
    if (LvpQueueGet(&s_voice_player_frame_queue, (unsigned char *)&play_saddr)) {
        frame.saddr = play_saddr;
        frame.eaddr = frame.saddr + (eaddr - saddr);
        if (LvpQueueGetDataNum(&s_voice_player_frame_queue) == 0 && event_cb) {
            event_cb(EVENT_PLAYER_LAST_FRAME, NULL);
            lvp_player_clear_frame_buffer();
            return;
        }
        uni_audio_out_push_frame(handle, &frame);
    }
}

static void lvp_player_finish_callback(void)
{
    if (LvpQueueGetDataNum(&s_voice_player_event_queue) > 0) {
        player_info.status = PLAYER_STATUS_NEXT;
        return;
    }
    lvp_audio_out_exit();
    if (event_cb)
        event_cb(EVENT_PLAYER_FINISH, NULL);
    return;
}

static int lvp_vol_to_db(int volume)
{
    const int vol_to_db[] = {-25, -6, 0, 3, 6, 8, 10, 12, 14, 15, 16};
    volume = (volume > 100 ? 100 : volume) < 0 ? 0 : volume;
    volume = (volume + 5) / 10;
    return vol_to_db[volume];
}

typedef struct {
/*    RIFF Chunk */
    char riff[4];              // "RIFF"
    unsigned int riff_size;    // size - 8
    char riff_format[4];       // "WAVE" "OPUS" "AMR "
/*    FMT Chunk */
    char format[4];            // "fmt "
    unsigned int format_size;  // 0x10 (FMT Chunk size - 8)
/*    1(0x0001)   PCM/非压缩格式      support
 *    2(0x0002)   Microsoft ADPCM
 *    3(0x0003)   IEEE float
 *    6(0x0006)   ITU G.711 a-law
 *    7(0x0007)   ITU G.711 μ-law
 *    49(0x0031)  GSM 6.10
 *    64(0x0040)  ITU G.721 ADPCM
 *    8(0x0008)   opus                support
 *    9(0x0009)   amr                 support
 */
    unsigned short format_tag;
    unsigned short channels;
    unsigned int sample_rate;  // 8k or 16k for opus and amr
    unsigned int bytes_rate;
    unsigned short block_align;
    unsigned short bits;
/*    DATA Chunk*/
    char data_tag[4];
    unsigned int data_size;
} VOICE_HEADER;

static int LvpVoicePlayerCheckInPcmBuffer(const unsigned char *resource_position, int len)
{
    if (user_pcm_buffer == NULL || user_pcm_buffer_len <= 0)
        return 0;
    if ((resource_position < user_pcm_buffer)
            || (resource_position + len > user_pcm_buffer + user_pcm_buffer_len))
        return 0;
    return 1;
}


static void lvp_audio_out_init(void)
{
    UNI_AUDIO_OUT_PCM pcm;
    UNI_AUDIO_OUT_BUFFER buf;
    UNI_AUDIO_OUT_CALLBACK cb;

    pcm.sample_rate = voice_info.sample_rate;
    pcm.channels    = voice_info.channels;
    pcm.bits        = voice_info.bits;
    pcm.interlace   = voice_info.interlace;
    pcm.endian      = voice_info.endian;

    if (LvpVoicePlayerCheckInPcmBuffer(voice_info.resource_position, voice_info.voice_size) != 0 && voice_info.decoder == TYPE_PCM) {
        buf.buffer0 = user_pcm_buffer;
        buf.buffer1 = NULL;
        buf.size = user_pcm_buffer_len;
    } else {
        buf.buffer0 = (unsigned char *)lvp_play_buffer;
        buf.buffer1 = NULL;
        buf.size = sizeof(lvp_play_buffer);
    }

    lvp_audio_out_exit();
    uni_mdelay(1);

    cb.new_frame_callback = lvp_player_callback;
    cb.frame_over_callback = NULL;
    uni_audio_out_config_buffer(handle, &buf);
    uni_audio_out_config_pcm(handle, &pcm);
    uni_audio_out_config_cb(handle, &cb);
    uni_audio_out_set_channel(handle, UNI_AUDIO_OUT_STEREO_C);
    uni_audio_out_set_db(handle, lvp_vol_to_db(player_info.volume));
    LvpVoicePlayerUnmute();
}

static int lvp_audio_play(void)
{
    int play_saddr;
    UNI_AUDIO_OUT_FRAME frame;
    UNI_AUDIO_OUT_CALLBACK cb;
    if (LvpQueueIsEmpty(&s_voice_player_frame_queue))
        return -1;
    if (LvpQueueGet(&s_voice_player_frame_queue, (unsigned char *)&play_saddr)) {
        frame.saddr = play_saddr;
        frame.eaddr = frame.saddr + voice_info.dec_frame_size - 1;

        uni_audio_out_push_frame(handle, &frame);
        cb.new_frame_callback = NULL;
        cb.frame_over_callback = lvp_player_finish_callback;
        uni_audio_out_config_cb(handle, &cb);
        return 0;
    }
    return -1;
}

int LvpVoicePlayerMute(void)
{
    int ret = 0;
    ret = uni_audio_out_set_mute(handle, 1);
    return ret;
}

int LvpVoicePlayerUnmute(void)
{
    int ret = 0;
    ret = uni_audio_out_set_mute(handle, 0);
    uni_mdelay(180); //wait PA enable
    return ret;
}

static int lvp_update_voice_header_config(LVP_VOICE_INFO *voice_info, const unsigned char *resource_position)
{
    VOICE_HEADER *voice_head = NULL;
    if (voice_info == NULL)
        return -1;
    if (resource_position == NULL)
        return -1;
    voice_head = (VOICE_HEADER *)resource_position;

    if (strncmp(voice_head->riff, "RIFF", 4) != 0 || voice_head->riff_size <= sizeof(VOICE_HEADER)
            || voice_head->format_size != 0x10 || voice_head->data_size <= 0)
    {
        return -1;
    }

    if (strncmp(voice_head->riff_format, "WAVE", 4) == 0 && voice_head->format_tag == TYPE_WAV)
    {
        voice_info->decoder = TYPE_WAV;
        if (voice_head->sample_rate == 8000) {
            voice_info->enc_frame_size = PCM_8K_FRAME_LEN;
            voice_info->dec_frame_size = PCM_8K_FRAME_LEN;
        } else if (voice_head->sample_rate == 16000) {
            voice_info->enc_frame_size = PCM_16K_FRAME_LEN;
            voice_info->dec_frame_size = PCM_16K_FRAME_LEN;
        }
    }
    else
    {
        printf("unsupport decoder\n");
        return -1;
    }
    voice_info->sample_rate = voice_head->sample_rate;
    voice_info->channels = voice_head->channels;
    voice_info->bits = voice_head->bits;
    voice_info->bytes_rate = voice_head->bytes_rate;
    voice_info->play_offset = sizeof(VOICE_HEADER);
    voice_info->play_pos = voice_info->play_offset;
    voice_info->voice_size = voice_head->data_size;
    voice_info->resource_position = resource_position;
    return 0;
}

static int LvpVoicePlayerUpdate(LVP_PLAYER_VOICE *voice_event)
{
    int ret = -1;
    if (voice_event->resource_position == NULL)
        return ret;

    if (voice_event->sample_rate != 0 && voice_event->channels > 0 && voice_event->len > 0)
    {
        voice_info.sample_rate = voice_event->sample_rate;
        voice_info.channels = voice_event->channels;
        voice_info.resource_position = voice_event->resource_position;
        voice_info.bits = 16;
        voice_info.bytes_rate = voice_event->sample_rate * 16 / 8;
        voice_info.voice_size = voice_event->len;
        voice_info.decoder = TYPE_PCM;
        voice_info.interlace = voice_event->interlace;
        if (LvpVoicePlayerCheckInPcmBuffer(voice_event->resource_position, voice_event->len)) {
            voice_info.play_offset = voice_event->resource_position - user_pcm_buffer;
        } else {
            voice_info.play_offset = 0;
        }
        voice_info.play_pos = voice_info.play_offset;

        if (voice_info.sample_rate == 16000) {
            voice_info.enc_frame_size = PCM_16K_FRAME_LEN;
            voice_info.dec_frame_size = PCM_16K_FRAME_LEN;
        } else if (voice_info.sample_rate == 8000) {
            voice_info.enc_frame_size = PCM_8K_FRAME_LEN;
            voice_info.dec_frame_size = PCM_8K_FRAME_LEN;
        } else {
            return ret;
        }
    }
    else
    {
        if (lvp_update_voice_header_config(&voice_info, voice_event->resource_position))
            return ret;
    }

    switch (voice_info.decoder)
    {
        case TYPE_PCM:
        case TYPE_WAV:
            ret = 0;
            break;
        default:
            printf("no this type voice resource\n");
            break;
    }
    if (ret != 0) {
        printf("decoder init fail\n");
        memset((void *)&voice_info, 0, sizeof(voice_info));
        player_info.status = PLAYER_STATUS_STOP;
        return ret;
    }

    if (player_info.status != PLAYER_STATUS_PLAY && player_info.status != PLAYER_STATUS_PREPARE) {
        lvp_audio_out_init();
    }
    player_info.status         = PLAYER_STATUS_PREPARE;
    return ret;
}

int LvpVoicePlayerSetVolume(int volume)
{
    if (handle != -1) {
        if (volume > 100)
            volume = 100;
        else if (volume < 0)
            volume = 0;
        player_info.volume = volume;
        return 0;
    }
    return -1;
}

int LvpVoicePlayerGetVolume(void)
{
    if (handle != -1) {
        return player_info.volume;
    }
    return -1;
}

int LvpVoicePlayerStop(void)
{
    LvpVoicePlayerMute();
    player_info.status = PLAYER_STATUS_STOP;
    return 0;
}

//int LvpVoicePlayerPause(void)
//{
//    player_info.status = PLAYER_STATUS_PAUSE;
//    return 0;
//}

//int LvpVoicePlayerResume(void)
//{
//    player_info.status = PLAYER_STATUS_PLAY;
//    return 0;
//}

static void LvpVoicePlayerPutFrame(const int base_offset, const int decode_offset_count, const int dec_len)
{
    int play_saddr = base_offset + decode_offset_count * dec_len;
    LvpQueuePut(&s_voice_player_frame_queue, (unsigned char *)&play_saddr);
}

int LvpVoicePlayerTask(void *arg)
{
    LVP_PLAYER_VOICE voice_event;
    const unsigned char *resource_position;
    int enc_len = 0;
    int dec_len = 0;
    int decode_offset_count;
    if (player_info.status == PLAYER_STATUS_STOP || player_info.status == PLAYER_STATUS_NEXT)
    {
        memset(&voice_info, 0, sizeof(voice_info));
        voice_info.resource_position = NULL;
    }
    else if (player_info.status == PLAYER_STATUS_PAUSE)
    {
    }
    else if (player_info.status <= PLAYER_STATUS_PLAY && ((voice_info.play_pos - voice_info.play_offset) <= voice_info.voice_size)
            && LvpQueueGetDataNum(&s_voice_player_frame_queue) < (LVP_VOICE_PLAYER_FRAME_COUNT - 2))
    {
        resource_position = voice_info.resource_position;
        enc_len = voice_info.enc_frame_size;
        dec_len = voice_info.dec_frame_size;
        decode_offset_count = ((voice_info.play_pos - voice_info.play_offset) / enc_len ) % (MAX_PCM_LEN * COUNT / dec_len);

        switch (voice_info.decoder)
        {
            case TYPE_PCM:
                if (!LvpVoicePlayerCheckInPcmBuffer(resource_position, voice_info.voice_size)) {
                  #ifndef CONFIG_HW_EXT_FLASH
                  memcpy(lvp_play_buffer + decode_offset_count * dec_len,
                         resource_position + voice_info.play_pos, dec_len);
                  #else
                  extern UNI_FLASH_DEV nor_spi_dev;
                  uni_spi_flash_readdata(&nor_spi_dev, (unsigned int)resource_position + voice_info.play_pos, 
                                         lvp_play_buffer + decode_offset_count * dec_len, dec_len);
                  #endif
                }
                LvpVoicePlayerPutFrame(voice_info.play_offset, decode_offset_count, dec_len);
                voice_info.play_pos += enc_len;
                break;
            case TYPE_WAV:
                memcpy(lvp_play_buffer + decode_offset_count * dec_len,
                        resource_position + voice_info.play_pos, dec_len);
                LvpVoicePlayerPutFrame(0, decode_offset_count, dec_len);
                voice_info.play_pos += enc_len;
                break;
            case TYPE_FILL:
                break;
            default:
                player_info.status = PLAYER_STATUS_STOP;
                break;
        }
        if (player_info.status == PLAYER_STATUS_PREPARE
                && LvpQueueGetDataNum(&s_voice_player_frame_queue) >= 3) {
            player_info.status = PLAYER_STATUS_PLAY;
            lvp_audio_play();
        }
    }

    if (player_info.status > PLAYER_STATUS_PLAY) {
      if (LvpQueueGet(&s_voice_player_event_queue, (unsigned char *)&voice_event))
      {
          LvpVoicePlayerUpdate(&voice_event);
      }
    }
    return 0;
}

int LvpVoicePlayerGetStatus(void)
{
    return player_info.status;
}

int LvpVoicePlayerInit(LvpVoicePlayerEventCallback cb)
{
    if (handle == -1)
    {
        uni_audio_out_init(0);
        handle = (int)uni_audio_out_alloc_playback(0);
    }
    if (cb != NULL) {
        event_cb = cb;
    }
    LvpVoicePlayerSetVolume(player_info.volume);
    LvpQueueInit(&s_voice_player_event_queue, s_voice_player_event_queue_buffer, LVP_VOICE_PLAYER_QUEUE_LEN * sizeof(LVP_PLAYER_VOICE), sizeof(LVP_PLAYER_VOICE));
    LvpQueueInit(&s_voice_player_frame_queue, s_voice_player_frame_queue_buffer, LVP_VOICE_PLAYER_FRAME_COUNT * sizeof(int), sizeof(int));
    return 0;
}
int LvpVoicePlayerSuspend(void)
{
    if (handle != -1) {
        uni_audio_out_free(handle);
        uni_audio_out_suspend(handle);
        handle = -1;
        //uni_audio_out_exit();
    }
    return 0;
}

int LvpVoicePlayerResume(void)
{
    if (handle == -1) {
        uni_audio_out_init(0);
        handle = (int)uni_audio_out_alloc_playback(0);
    }
    return 0;
}

