#include <mpg123.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "mp3.h"
#include "playback.h"

static size_t*			buffSize;
static mpg123_handle	*mh = NULL;
static uint32_t			rate;
static uint8_t			channels;

static int initMp3(const char* file);
static uint32_t rateMp3(void);
static uint8_t channelMp3(void);
static uint64_t decodeMp3(void* buffer);
static void exitMp3(void);
static size_t getFileSamplesMp3(void);

/**
 * Set decoder parameters for MP3.
 *
 * \param	decoder Structure to store parameters.
 */
void setMp3(struct decoder_fn* decoder)
{
	decoder->init = &initMp3;
	decoder->rate = &rateMp3;
	decoder->channels = &channelMp3;
	/*
	 * buffSize changes depending on input file. So we set buffSize later when
	 * decoder is initialised.
	 */
	buffSize = &(decoder->buffSize);
	decoder->decode = &decodeMp3;
	decoder->exit = &exitMp3;
	decoder->getFileSamples = &getFileSamplesMp3;
}

static size_t getFileSamplesMp3(void)
{
	off_t len = mpg123_length(mh);
	if(len == MPG123_ERR)
		return 0;
	
	return len * (size_t)channels;
}

/**
 * Initialise MP3 decoder.
 *
 * \param	file	Location of MP3 file to play.
 * \return			0 on success, else failure.
 */
int initMp3(const char* file)
{
	int err = 0;
	int encoding = 0;

	if((err = mpg123_init()) != MPG123_OK)
		return err;

	if((mh = mpg123_new(NULL, &err)) == NULL)
	{
		printf("Error: %s\n", mpg123_plain_strerror(err));
		return err;
	}

	if(mpg123_open(mh, file) != MPG123_OK ||
			mpg123_getformat(mh, (long *) &rate, (int *) &channels, &encoding) != MPG123_OK)
	{
		printf("Trouble with mpg123: %s\n", mpg123_strerror(mh));
		return -1;
	}

	/*
	 * Ensure that this output format will not change (it might, when we allow
	 * it).
	 */
	mpg123_format_none(mh);
	mpg123_format(mh, rate, channels, encoding);

	/*
	 * Buffer could be almost any size here, mpg123_outblock() is just some
	 * recommendation. The size should be a multiple of the PCM frame size.
	 */
	*buffSize = mpg123_outblock(mh) * 16;

	return 0;
}

/**
 * Get sampling rate of MP3 file.
 *
 * \return	Sampling rate.
 */
uint32_t rateMp3(void)
{
	return rate;
}

/**
 * Get number of channels of MP3 file.
 *
 * \return	Number of channels for opened file.
 */
uint8_t channelMp3(void)
{
	return channels;
}

/**
 * Decode part of open MP3 file.
 *
 * \param buffer	Decoded output.
 * \return			Samples read for each channel.
 */
uint64_t decodeMp3(void* buffer)
{
	size_t done = 0;
	mpg123_read(mh, buffer, *buffSize, &done);
	return done / (sizeof(int16_t));
}

/**
 * Free MP3 decoder.
 */
void exitMp3(void)
{
	mpg123_close(mh);
	mpg123_delete(mh);
	mpg123_exit();
}

/**
 * Check if a file is a valid MP3 file.
 * 
 * \param path	Path to the MP3 file.
 * \return		0 if valid MP3, 1 if not valid.
 */
int isMp3(const char *path)
{
#if 0
	int err;
	int result = 1;
	mpg123_handle *mh = NULL;
	long rate;
	int channels, encoding;

	// Initialise the library
	if (mpg123_init() != MPG123_OK)
		goto out;

	// Create a decoder handle
	mh = mpg123_new(NULL, &err);
	if (!mh)
		goto exit_init;

	// skip ID3v2 tags rather than parsing them (so tag-only files don’t count as valid mp3)
	mpg123_param(mh, MPG123_SKIP_ID3V2, 1, 0);

	// limit how many bytes to scan for a frame sync (e.g. 2048 bytes)
	mpg123_param(mh, MPG123_RESYNC_LIMIT, 2048, 0);

	// Try opening the file
	err = mpg123_open(mh, path);
	if (err != MPG123_OK)
		goto exit_handle;

	// Query the decoded format
	if (mpg123_getformat(mh, &rate, &channels, &encoding) != MPG123_OK)
		goto close_handle;

	// Parse first frame in file
	err = mpg123_framebyframe_next(mh);
	if (err != MPG123_OK)
	{
		// If we can't read the first frame, it's not a valid MP3
		goto close_handle;
	}

	// All checks passed: valid MP3
	result = 0;

close_handle:
	mpg123_close(mh);

exit_handle:
	mpg123_delete(mh);

exit_init:
	mpg123_exit();

out:
	return result;
#else
    unsigned char buf[4];
	FILE *f = fopen(path, "rb");
	int ret = 1;

	if(!f) return 1;

    if(fread(buf, 1, 4, f) < 4)
		goto out;

    // ID3v2 tag?
    if(buf[0]=='I' && buf[1]=='D' && buf[2]=='3') {
		ret = 0;
		goto out;
	}

    // MPEG frame sync: 11 one-bits in a row
    if(buf[0]==0xFF && (buf[1]&0xE0)==0xE0) {
		ret = 0;
		goto out;
	}

out:
	fclose(f);
    return ret;
#endif
}

