#include <rtthread.h>
#include <dfs_posix.h>
#include <mp3/pub/mp3dec.h>

#include "board.h"
#include "netbuffer.h"

#define MP3_AUDIO_BUF_SZ    4096

rt_uint8_t mp3_fd_buffer[MP3_AUDIO_BUF_SZ];

struct mp3_decoder
{
    /* mp3 information */
    HMP3Decoder decoder;
    MP3FrameInfo frame_info;
    rt_uint32_t frames;

    /* mp3 file descriptor */
	rt_size_t (*fetch_data)(void* parameter, rt_uint8_t *buffer, rt_size_t length);
	void* fetch_parameter;

    /* mp3 read session */
    rt_uint8_t *read_buffer;
    rt_uint8_t* read_ptr;
    rt_int32_t read_offset;
    rt_uint32_t bytes_left, bytes_left_before_decoding;

	/* audio device */
	rt_device_t snd_device;
};

static rt_err_t mp3_decoder_tx_done(rt_device_t dev, void *buffer)
{
	/* release memory block */
	sbuf_release(buffer);

	return RT_EOK;
}

void mp3_decoder_init(struct mp3_decoder* decoder)
{
    RT_ASSERT(decoder != RT_NULL);

	/* init read session */
	decoder->read_ptr = RT_NULL;
	decoder->bytes_left_before_decoding = decoder->bytes_left = 0;
	decoder->frames = 0;

    // decoder->read_buffer = rt_malloc(MP3_AUDIO_BUF_SZ);
    decoder->read_buffer = &mp3_fd_buffer[0];
	if (decoder->read_buffer == RT_NULL) return;

    decoder->decoder = MP3InitDecoder();

	/* open audio device */
	decoder->snd_device = rt_device_find("snd");
	if (decoder->snd_device != RT_NULL)
	{
		/* set tx complete call back function */
		rt_device_set_tx_complete(decoder->snd_device, mp3_decoder_tx_done);
		rt_device_open(decoder->snd_device, RT_DEVICE_OFLAG_WRONLY);
	}
}

void mp3_decoder_detach(struct mp3_decoder* decoder)
{
    RT_ASSERT(decoder != RT_NULL);

	/* close audio device */
	if (decoder->snd_device != RT_NULL)
		rt_device_close(decoder->snd_device);

	/* release mp3 decoder */
    MP3FreeDecoder(decoder->decoder);
}

struct mp3_decoder* mp3_decoder_create()
{
    struct mp3_decoder* decoder;

	/* allocate object */
    decoder = (struct mp3_decoder*) rt_malloc (sizeof(struct mp3_decoder));
    if (decoder != RT_NULL)
    {
        mp3_decoder_init(decoder);
    }

    return decoder;
}

void mp3_decoder_delete(struct mp3_decoder* decoder)
{
    RT_ASSERT(decoder != RT_NULL);

	/* de-init mp3 decoder object */
	mp3_decoder_detach(decoder);
	/* release this object */
    rt_free(decoder);
}

rt_uint16_t is_first = 1;
rt_uint32_t current_offset = 0;
static rt_int32_t mp3_decoder_fill_buffer(struct mp3_decoder* decoder)
{
	rt_size_t bytes_read;
	rt_size_t bytes_to_read;

	// rt_kprintf("left: %d. refilling inbuffer...\n", decoder->bytes_left);
	if (decoder->bytes_left > 0)
	{
		// better: move unused rest of buffer to the start
		rt_memmove(decoder->read_buffer, decoder->read_ptr, decoder->bytes_left);
	}

	bytes_to_read = (MP3_AUDIO_BUF_SZ - decoder->bytes_left) & ~(512 - 1);
	// rt_kprintf("read bytes: %d\n", bytes_to_read);

	if (is_first) is_first = 0;
	else current_offset += MP3_AUDIO_BUF_SZ - decoder->bytes_left;

	bytes_read = decoder->fetch_data(decoder->fetch_parameter,
		(rt_uint8_t *)(decoder->read_buffer + decoder->bytes_left),
        bytes_to_read);

	if (bytes_read == bytes_to_read)
	{
		decoder->read_ptr = decoder->read_buffer;
		decoder->read_offset = 0;
		decoder->bytes_left = decoder->bytes_left + bytes_to_read;
		return 0;
	}
	else
	{
		rt_kprintf("can't read more data");
		return -1;
	}
}

int mp3_decoder_run(struct mp3_decoder* decoder)
{
	int err;
	rt_uint16_t* buffer;

    RT_ASSERT(decoder != RT_NULL);

	if ((decoder->read_ptr == RT_NULL) || decoder->bytes_left < 2*MAINBUF_SIZE)
	{
		if(mp3_decoder_fill_buffer(decoder) != 0)
			return -1;
	}

	// rt_kprintf("read offset: 0x%08x\n", decoder->read_ptr - decoder->read_buffer);
	decoder->read_offset = MP3FindSyncWord(decoder->read_ptr, decoder->bytes_left);
	if (decoder->read_offset < 0)
	{
		rt_kprintf("Error: MP3FindSyncWord returned <0");

		if(mp3_decoder_fill_buffer(decoder) != 0)
			return -1;
	}
	// rt_kprintf("sync position: %x\n", decoder->read_offset);

	decoder->read_ptr += decoder->read_offset;
	decoder->bytes_left -= decoder->read_offset;
	decoder->bytes_left_before_decoding = decoder->bytes_left;

#if 0
	// check if this is really a valid frame
	// (the decoder does not seem to calculate CRC, so make some plausibility checks)
	if (!(MP3GetNextFrameInfo(decoder->decoder, &decoder->frame_info, decoder->read_ptr) == 0 &&
			decoder->frame_info.nChans == 2 &&
			decoder->frame_info.version == 0))
	{
		rt_kprintf("this is an invalid frame\n");
		// advance data pointer
		// TODO: handle bytes_left == 0
		RT_ASSERT(decoder->bytes_left > 0);

		decoder->bytes_left --;
		decoder->read_ptr ++;
		return 0;
	}

	if (decoder->bytes_left < 1024)
	{
		if(mp3_decoder_fill_buffer(decoder) != 0)
			return -1;
	}
#endif

    /* get a decoder buffer */
    buffer = (rt_uint16_t*)sbuf_alloc();

	// rt_kprintf("bytes left before decode: %d\n", decoder->bytes_left);

	err = MP3Decode(decoder->decoder, &decoder->read_ptr,
        (int*)&decoder->bytes_left, (short*)buffer, 0);

	// rt_kprintf("bytes left after decode: %d\n", decoder->bytes_left);

	decoder->frames++;

	if (err != ERR_MP3_NONE)
	{
		switch (err)
		{
		case ERR_MP3_INDATA_UNDERFLOW:
			rt_kprintf("ERR_MP3_INDATA_UNDERFLOW\n");
			decoder->bytes_left = 0;
			if(mp3_decoder_fill_buffer(decoder) != 0)
				return -1;
			break;

		case ERR_MP3_MAINDATA_UNDERFLOW:
			/* do nothing - next call to decode will provide more mainData */
			rt_kprintf("ERR_MP3_MAINDATA_UNDERFLOW\n");
			break;

		case ERR_MP3_INVALID_FRAMEHEADER:
			rt_kprintf("ERR_MP3_INVALID_FRAMEHEADER\n");
			rt_kprintf("current offset: 0x%08x, frames: %d\n", current_offset, decoder->frames);
			/* dump sector */
			{
				rt_uint8_t *ptr;
				rt_size_t   size = 0, col = 0;

				ptr = decoder->read_buffer;
				rt_kprintf("   00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n");
				rt_kprintf("00 ");
				while (size ++ < 512)
				{
					rt_kprintf("%02x ", *ptr ++);
					if (size % 16 == 0) rt_kprintf("\n%02x ", ++col);
				}
			}
			RT_ASSERT(0);
			// break;

		case ERR_MP3_INVALID_HUFFCODES:
			rt_kprintf("ERR_MP3_INVALID_HUFFCODES\n");
			break;

		default:
			rt_kprintf("unknown error: %i\n", err);
			// skip this frame
			if (decoder->bytes_left > 0)
			{
				decoder->bytes_left --;
				decoder->read_ptr ++;
			}
			else
			{
				// TODO
				RT_ASSERT(0);
			}
			break;
		}

		/* release this memory block */
		sbuf_release(buffer);
	}
	else
	{
		/* no error */
		MP3GetLastFrameInfo(decoder->decoder, &decoder->frame_info);

#ifdef MP3_DECODER_TRACE
		rt_kprintf("Bitrate: %i\n", decoder->frame_info.bitrate);
		rt_kprintf("%i samples\n", decoder->frame_info.outputSamps);

		rt_kprintf("%lu Hz, %i kbps\n", decoder->frame_info.samprate,
            decoder->frame_info.bitrate/1000);
#endif

        /* set sample rate */

		/* write to sound device */
		rt_device_write(decoder->snd_device, 0, buffer, decoder->frame_info.outputSamps * 2);
		// rt_mp_free(buffer);
	}

	return 0;
}

#include <finsh.h>
rt_size_t fd_fetch(void* parameter, rt_uint8_t *buffer, rt_size_t length)
{
	int fd = (int)parameter;
	return read(fd, (char*)buffer, length);
}
void mp3(char* filename)
{
	int fd;
	struct mp3_decoder* decoder;

	fd = open(filename, O_RDONLY, 0);
	if (fd >= 0)
	{
		decoder = mp3_decoder_create();
		if (decoder != RT_NULL)
		{
			decoder->fetch_data = fd_fetch;
			decoder->fetch_parameter = (void*)fd;

			while (mp3_decoder_run(decoder) != -1);
			close(fd);

			/* delete decoder object */
			mp3_decoder_delete(decoder);
		}
	}
}
FINSH_FUNCTION_EXPORT(mp3, mp3 decode test);

#if STM32_EXT_SRAM
/* http mp3 */
#include "http.h"
static rt_size_t http_fetch(rt_uint8_t* ptr, rt_size_t len, void* parameter)
{
	struct http_session* session = (struct http_session*)parameter;
	RT_ASSERT(session != RT_NULL);

	return http_session_read(session, ptr, len);
}

static void http_close(void* parameter)
{
	struct http_session* session = (struct http_session*)parameter;
	RT_ASSERT(session != RT_NULL);

	http_session_close(session);
}

rt_size_t http_data_fetch(void* parameter, rt_uint8_t *buffer, rt_size_t length)
{
	return net_buf_read(buffer, length);
}
void http_mp3(char* url)
{
    struct http_session* session;
	struct mp3_decoder* decoder;

	session = http_session_open(url);
	if (session != RT_NULL)
	{
		/* add a job to netbuf worker */
		net_buf_add_job(http_fetch, http_close, (void*)session);

		decoder = mp3_decoder_create();
		if (decoder != RT_NULL)
		{
			decoder->fetch_data = http_data_fetch;
			decoder->fetch_parameter = RT_NULL;

			while (mp3_decoder_run(decoder) != -1);

			/* delete decoder object */
			mp3_decoder_delete(decoder);
		}
		session = RT_NULL;
	}
}
FINSH_FUNCTION_EXPORT(http_mp3, http mp3 decode test);
#endif
