/**************************************************************************
 * 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_mp3_player.c
 * Author      : yzs.unisound.com
 * Date        : 2021.03.01
 *
 **************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <autoconf.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 <lvp_buffer.h>
#include <lvp_queue.h>
#include <lvp_pmu.h>
#include <driver/uni_timer.h>
#include <soc_config.h>
#include "lvp_mp3_player.h"
#include "decoder/base_decoder.h"
#include "types.h"
#include <mp3dec.h>
#include "driver/uni_flash.h"

#ifdef USE_MP3
#include "decoder/mp3_decoder.h"
#endif
#include <driver/uni_irq.h>
#include <board_misc_config.h>
#include "uni_vui_interface.h"

#define AUDIO_OUT_BUFFER_LEN    CONFIG_AUDIO_OUT_BUFFER_SIZE

#define COUNT (CONFIG_AUDIO_OUT_BUFFER_SIZE / 1152)

__attribute__((section(".audio_out"))) static unsigned char lvp_play_buffer[AUDIO_OUT_BUFFER_LEN]__attribute__((aligned(16)));
static int handle = -1;

typedef struct {
    const unsigned char *resource_position;
    int sample_rate;
    int channels;
    int interlace;
    int len;
    int is_repeat;
} 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 COUNT
static unsigned char s_voice_player_frame_queue_buffer[LVP_VOICE_PLAYER_FRAME_COUNT * sizeof(int)];
LVP_QUEUE s_voice_player_frame_queue;

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;
    const unsigned char *resource_position;
    unsigned int enc_frame_size;
    unsigned int dec_frame_size;
    unsigned int play_pos;
    unsigned int voice_size;
    unsigned int decoded_length;
    unsigned int is_repeat;
} LVP_VOICE_INFO;

LVP_PLAYER_INFO player_info = {
    .status = PLAYER_STATUS_STOP,
    .mute = 0,
    .volume = 30,
};
LVP_VOICE_INFO voice_info = {0};
LvpVoicePlayerEventCallback event_cb;
static void lvp_player_clear_frame_buffer(void);

#define DecodeSkipFrame 1
#define READBUF_SIZE (4096)
unsigned char readBuf[READBUF_SIZE] ={0};
static int bytesLeft;
static unsigned char *decode_ptr;
static int saved_length;
static int player_pmu_lock = 0;

int LvpMp3PlayerPlay(const unsigned char *resource_position, unsigned int length, unsigned int repeat)
{
    LVP_PLAYER_VOICE voice_event;
    if (resource_position == NULL)
        return -1;
    voice_event.resource_position = resource_position;
    voice_event.len = length;
    voice_event.sample_rate = 0;
    voice_event.channels = 0;
    voice_event.is_repeat = repeat;

    if (LvpQueuePut(&s_voice_player_event_queue, (const unsigned char *)&voice_event)) {
        LvpPmuSuspendLock(player_pmu_lock);
        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)
{
    LvpMp3PlayerMute();
    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)) {
        player_info.status = PLAYER_STATUS_PREPARE;
        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 ((voice_info.voice_size - voice_info.play_pos) <= 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;
    }
    if (voice_info.is_repeat) {
        player_info.status = PLAYER_STATUS_PREPARE;
    } else {
        player_info.status = PLAYER_STATUS_STOP;
        LvpPmuSuspendUnlock(player_pmu_lock);
        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 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;

    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));
    LvpMp3PlayerUnmute();
}

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 LvpMp3PlayerMute(void)
{
    int ret = 0;
    ret = uni_audio_out_set_mute(handle, 1);
    return ret;
}

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

static int LvpMp3PlayerUpdate(LVP_PLAYER_VOICE *voice_event)
{
    int ret = -1;
    if (voice_event->resource_position == NULL)
        return ret;
    bytesLeft = 0;
    decode_ptr = readBuf;
    saved_length = 0;
    voice_info.sample_rate = 0;
    voice_info.channels = 0;
    voice_info.bits = 0;
    voice_info.play_pos = 0;
    voice_info.voice_size = voice_event->len;
    voice_info.resource_position = voice_event->resource_position;
    voice_info.decoded_length = 0;
    voice_info.is_repeat = voice_event->is_repeat;
    player_info.status         = PLAYER_STATUS_PREPARE;
    return ret;
}

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

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

void LvpMp3PlayerStop(void)
{
    LvpMp3PlayerMute();
    player_info.status = PLAYER_STATUS_STOP;
}

static void LvpMp3PlayerPutFrame(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 LvpMp3PlayerTask(void *arg)
{
    LVP_PLAYER_VOICE voice_event;
    int out_len = 0;
    int decode_offset_count;
    int first_offset = 1;
    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.voice_size) \
            && !LvpQueueIsFull(&s_voice_player_frame_queue)
            )
    {
        out_len = voice_info.dec_frame_size;
        if(out_len)
            decode_offset_count = (voice_info.decoded_length/out_len)%(AUDIO_OUT_BUFFER_LEN/out_len);
        else
            decode_offset_count = 0;

        if(bytesLeft < 2*1940)
        {
            int left = voice_info.voice_size - saved_length;
            memmove(readBuf, decode_ptr, bytesLeft);
            //printf("memmove from %d move length %d\n",decode_ptr - readBuf, bytesLeft);
            int read_size;
            if(left >= READBUF_SIZE - bytesLeft)
            {
                read_size = READBUF_SIZE - bytesLeft;
                #ifndef CONFIG_HW_EXT_FLASH
                memcpy(readBuf + bytesLeft, (unsigned char *)voice_info.resource_position + saved_length, READBUF_SIZE - bytesLeft);
                #else
                extern UNI_FLASH_DEV nor_spi_dev;
                uni_spi_flash_readdata(&nor_spi_dev, (unsigned int)voice_info.resource_position + saved_length, 
                                       readBuf + bytesLeft, READBUF_SIZE - bytesLeft);
                #endif
            }
            else
            {
                read_size =  left;
                #ifndef CONFIG_HW_EXT_FLASH
                memcpy(readBuf + bytesLeft, (unsigned char *)voice_info.resource_position + saved_length, left);
                #else
                extern UNI_FLASH_DEV nor_spi_dev;
                uni_spi_flash_readdata(&nor_spi_dev, (unsigned int)voice_info.resource_position + saved_length, 
                                       readBuf + bytesLeft, left);
                #endif
                memset(readBuf + bytesLeft + left, 0, READBUF_SIZE - bytesLeft - left);
            }
            //printf("memcpy from %d copy length:%d\n", bytesLeft, read_size);
            decode_ptr = readBuf;
            bytesLeft += read_size;
            saved_length += read_size;
        }

        MP3FrameInfo info;
        lvp_mp3_getinfo(decode_ptr, bytesLeft, &first_offset, &info);
        if(voice_info.sample_rate == 0)
        {
            voice_info.sample_rate = info.samprate;
            voice_info.channels =info.nChans;
            voice_info.bits = info.bitsPerSample;
            voice_info.interlace = 0;
            voice_info.endian = 0;
            printf("voice_info.sample_rate =%d voice_info.channels =%d\n", voice_info.sample_rate, voice_info.channels);
            lvp_audio_out_init();
        }
        if(first_offset < 0)
        {
            printf("decode over!!! voice_info.play_pos =%d voice_info.voice_size=%d\n",
                voice_info.play_pos, voice_info.voice_size);
            if(voice_info.is_repeat)
            {
                bytesLeft = 0;
                decode_ptr = readBuf;
                saved_length = 0;
            }
            return 0;
        }
        else
        {
            decode_ptr += first_offset;
            bytesLeft -= first_offset;
            voice_info.play_pos += first_offset;
        }
        unsigned char * addr = decode_ptr;
        lvp_mp3_decode(&decode_ptr, &bytesLeft, lvp_play_buffer + decode_offset_count * out_len, &out_len);
        voice_info.play_pos += (decode_ptr - addr);
        voice_info.decoded_length += out_len;
        voice_info.dec_frame_size = out_len;
        if (voice_info.is_repeat)
        {
            static int i = 0;
            if(i >= DecodeSkipFrame)
                LvpMp3PlayerPutFrame(0, decode_offset_count, out_len);
            i++;
            if(voice_info.play_pos >= voice_info.voice_size)
            {
                voice_info.play_pos = 0;
                i = 0;
            }
        }
        else
        {
            LvpMp3PlayerPutFrame(0, decode_offset_count, out_len);
        }

        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))
      {
          VuiRecognStop();
          if (lvp_mp3_decoder_init() < 0) {
            printf("%s:mp3 decoder init fail\n",__func__);
            return -1;
          }
          printf("+++[%s]%d+++\n", __func__, __LINE__);
          LvpMp3PlayerUpdate(&voice_event);
      }
    }

    return 0;
}

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

int LvpMp3PlayerInit(LvpVoicePlayerEventCallback cb)
{
    if (handle == -1)
    {
        uni_audio_out_init(0);
        handle = (int)uni_audio_out_alloc_playback(0);
    }
    if (cb != NULL) {
        event_cb = cb;
    }
    LvpPmuSuspendLockCreate(&player_pmu_lock);
    LvpMp3PlayerSetVolume(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 LvpMp3PlayerSuspend()
{
    if (handle != -1) {
        uni_audio_out_free(handle);
        uni_audio_out_suspend(handle);
        handle = -1;
        //uni_audio_out_exit();
    }
    return 0;
}

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

