/* mp3_decode.c */

#include <string.h>

/* helix mp3 decode lib. */
#include "mp3dec.h"

#include "mp3_decode.h"

/* init mp3_decode. */
void mp3_decode_init(mp3_decode_obj_t * obj, mp3_decode_if_t * interface)
{
    obj->buf_offset  = 0;
    obj->buf_remain  = 0;
    obj->sample_rate = 0;
    obj->dec         = NULL;
    obj->interface   = (mp3_decode_if_t*)interface;
}

/* open file. */
bool mp3_decode_open(mp3_decode_obj_t * obj, char * file_name)
{
    if (!obj->interface->file_open(file_name))
    {
        return false; /* cannot open file. */
    }

    obj->buf_offset = 0;
    obj->buf_remain = obj->interface->file_read(obj->interface->mp3_buf, obj->interface->mp3_buf_size);
    if (obj->buf_remain == 0)
    {
        obj->interface->file_close();
        return false; /* cannot read file. */
    }

    obj->dec = MP3InitDecoder();
    if (obj->dec == NULL)
    {
        obj->interface->file_close();
        return false; /* cannot init mp3 decoder. */
    }

    return true;
}

/* decode one frame. */
static int32_t mp3_decode_one_frame(mp3_decode_obj_t * obj, int16_t * pcm_buf)
{
    uint8_t * read_ptr = obj->interface->mp3_buf + obj->buf_offset;

    /* find sync, a frame head. */
    int offset = MP3FindSyncWord(read_ptr, obj->buf_remain);
    if (offset < 0)
    {
        return -1; /* cannot find sync word. */
    }

    read_ptr  += offset;
    obj->buf_offset += offset;
    obj->buf_remain -= offset;

    /* if buf_remain < MAINBUF_SIZE * 2, read new raw from file. */
    /* ensure the integrity of one frame of data. */
    if (obj->buf_remain < MAINBUF_SIZE * 2)
    {
        memmove(obj->interface->mp3_buf, read_ptr, obj->buf_remain);/* move reamin raw to head. */
        obj->buf_remain += obj->interface->file_read(obj->interface->mp3_buf + obj->buf_remain, obj->interface->mp3_buf_size - obj->buf_remain);/* read new raw. */

        read_ptr = obj->interface->mp3_buf;
        obj->buf_offset = 0;
    }

    /* decode. */
    int remain = (int)obj->buf_remain;
    MP3Decode(obj->dec, &read_ptr, &remain, pcm_buf, 0);
    obj->buf_offset += obj->buf_remain - remain;
    obj->buf_remain = remain;

    /* update sample rate. */
    MP3FrameInfo info;
    MP3GetLastFrameInfo(obj->dec, &info);
    obj->sample_rate = info.samprate;

    return info.outputSamps;
}

/* decode mp3 file. */
uint32_t mp3_decode(mp3_decode_obj_t * obj, int16_t * pcm_buf, uint32_t pcm_samples)
{
    if (0 != (pcm_samples % MP3_DECODE_FRAME_SAMPLES))
    {
        return 0; /* pcm_samples must be an integral multiple of MP3_DECODE_FRAME_SAMPLES. */
    }

    uint32_t samples = 0;

    while (samples < pcm_samples)
    {
        int32_t out_samples = mp3_decode_one_frame(obj, pcm_buf);
        if (out_samples < 0)
        {
            break;
        }
        pcm_buf += out_samples;
        samples += out_samples;
    }

    return samples;
}

/* close mp3 file. */
void mp3_decode_close(mp3_decode_obj_t * obj)
{
    obj->interface->file_close();
    MP3FreeDecoder(obj->dec);
    obj->buf_offset = 0;
    obj->buf_remain   = 0;
}

/* get sample rate. */
uint32_t mp3_decode_get_sample_rate(mp3_decode_obj_t * obj)
{
    return obj->sample_rate;
}

/* EOF. */
