#include "IStream.h"
#include "IReader.h"
#include "IFileTypes.h"
#include "MetaCodecs.h"
#include "Utils.h"
#include "Log.h"
#include <string.h>

#include "Format_WAVE.h"

/*------------------------------------------------------------------------------
 * MetaFooters
 * They can only be used when the input stream is seekable.
 * They are called before any other codecs and update the source.end value
 * so that the codecs doesn't attempt to decode these footers as sound.
 */

/*---------------
 * ID3 v1 footer
 */

typedef struct
{
	char	tag[3];
	char	title[30];
	char	author[30];
	char	album[30];
	char	year[4];
	char	comment[29];
	char	track;
	char	genre;
} id3v1_t;

static const char ID3v1_TagID[] = "TAG";

const _kernel_oserror* Meta_ID3v1(IStream* s)
{
	const _kernel_oserror* e;
	id3v1_t Info;

	e = FromFile_SetPos(s, s->source.end - s->source.start - 128);
	if (e) return e;

	e = FromFile_Read(s, &Info, sizeof(Info));
	if (e) return e;

	if (strncmp(Info.tag, ID3v1_TagID, 3))
		return NULL;

	// reduce file size by 128
	s->source.end -= 128;

	// store title
	e = IStream_SetText(s, 1, EMeta_StreamTitle, Info.title, sizeof(Info.title));
	if (e) return e;

	// store author
	e = IStream_SetText(s, 1, EMeta_StreamAuthor, Info.author, sizeof(Info.author));
	if (e) return e;

	// store album
	e = IStream_SetText(s, 1, EMeta_StreamAlbum, Info.album, sizeof(Info.album));
	if (e) return e;

	// store track number
	if (Info.track)
	{
		int val = Info.track;
		e = IStream_SetMetadata(s, 1, EMeta_StreamTrackNumber, &val, sizeof(int));
		if (e) return e;
	}

	// store year
	if ((Info.year[0] >= '1')
	&&  (Info.year[0] <= '2')
	&&  (Info.year[1] >= '0')
	&&  (Info.year[1] <= '9')
	&&  (Info.year[2] >= '0')
	&&  (Info.year[2] <= '9')
	&&  (Info.year[3] >= '0')
	&&  (Info.year[3] <= '9'))
	{
		e = IStream_SetText(s, 1, EMeta_StreamDate, Info.year, sizeof(Info.year));
		if (e) return e;
	}

	return NULL;
}

/**
 * APE footer
 */

typedef struct
{
	char      tag[8];
	uint32_t  version;
	uint32_t  size;
	uint32_t  nr_fields;
	uint32_t  flags;
	char      reserved[8];
} ape_footer;

static const char APE_TagID[] = "APETAGEX";

const _kernel_oserror* Meta_APE(IStream* s)
{
	const _kernel_oserror* e;
	ape_footer Info;
	uint32_t end;

	e = FromFile_SetPos(s, s->source.end - s->source.start - 32);
	if (e) return e;

	e = FromFile_Read(s, &Info, sizeof(Info));
	if (e) return e;

    // Valid Header?
	if (strncmp(Info.tag, APE_TagID, 8)
    ||  (Info.version > 2000)
    ||  (Info.nr_fields > 65536)
    ||  (Info.size > (1<<24))
    ||  (Info.flags & (1 << 29)))
    	return NULL;

	// reduce file size
	if (Info.flags & (1u << 31))
		end = s->source.end - Info.size - 2*sizeof(Info);
	else
		end = s->source.end - Info.size;

	if (s->source.start <= end)
		return NULL;

	s->source.end = end;

	return NULL;
}

/*------------------------------------------------------------------------------
 * MetaHeaders
 * They will be called once a stream starts to be filled before any codec
 * attempts to decode the sound. They update the source.start value
 * so that the codecs doesn't attempt to decode these headers as sound.
 */

/*---------------
 * ID3 v2 header
 */

typedef struct
{
	char	tag[3];
	char	vers[2];
	char	flags;
	char	length[4];
} id3v2_t;

typedef struct
{
	int	size;
	id3v2_t header;
} id3v2_work_t;

static const char TagIDv2[] = "ID3";

const _kernel_oserror* Meta_ID3v2(IStream* s, bool* b)
{
	id3v2_work_t* w = s->codec.data;
	const _kernel_oserror* e;

	*b = false;

	if (!w)
	{
		e = IStream_Alloc(s, &s->codec.data, sizeof(*w));
		if (e) return e;
		w = s->codec.data;
		memset(w, 0, sizeof(*w));
	}

	if (!w->size)
	{
		// Attempt to find id3v2 header
		if (!bs_peekBytes(&s->bitb, (uint8_t*) &w->header, 10))
		{
			if (!s->inb->finishflag)
				*b = true; // wait for buffer to be filled

			goto release;
		}

		if ((w->header.tag[0] != TagIDv2[0])
		||  (w->header.tag[1] != TagIDv2[1])
		||  (w->header.tag[2] != TagIDv2[2]))
			goto release;

		LogInfo("Found ID3 v2 header");

		w->size = ((w->header.length[0] & 0x7f) << 21)
		        + ((w->header.length[1] & 0x7f) << 14)
		        + ((w->header.length[2] & 0x7f) << 7)
		        +  (w->header.length[3] & 0x7f)
		        + 10;
	}

	w->size = IStream_SkipBytes(s, w->size);

	if (w->size)
		*b = true;
	else
		// Don't reread them on loops
		s->source.start = IStream_GetInputPos(s);

release:
	if (!*b) IStream_Free(s, &s->codec.data);

	return NULL;
}

typedef struct
{
	int     size;
	int     skiphdrs;
	wave_t  header;
} riff_work_t;

static const wav_tag_t TagRMP3 = {{'R','M','P','3'}};

const _kernel_oserror* Meta_Riff(IStream* s, bool* b)
{
	riff_work_t* w = s->codec.data;
	const _kernel_oserror* e;

	*b = false;

	if (!w)
	{
		e = IStream_Alloc(s, &s->codec.data, sizeof(*w));
		if (e) return e;
		w = s->codec.data;
		memset(w, 0, sizeof(*w));
	}

	while(1)
	{
		if (!w->skiphdrs)
		{
			// Attempt to find RIFF/wave header
			if (!bs_peekBytes(&s->bitb, (uint8_t*) &w->header, sizeof(w->header)))
			{
				// wait for buffer to be filled
				if (!s->inb->finishflag)
					*b = true;

				break;
			}

			// Not an RIFF, exit
			if (w->header.riff.i != TagRIFF.i)
			{
				break;
			}

			if (w->header.tag.i == TagRMP3.i)
			{
				// MPEG
				LogInfo("Found RIFF type RMP3");
				s->source.os_filetype = IFileType_MP3;
				// skip headers
				w->skiphdrs = 1;
				bs_skipBytes(&s->bitb, 12);
				continue;
			}

			// not an interesting RIFF
			LogInfo("Ignored RIFF type %.4s", w->header.tag.c);
			break;
		}

		if (w->size) w->size = IStream_SkipBytes(s, w->size);

		if (w->size
		||  !bs_peekBytes(&s->bitb, (uint8_t*) &w->header, 8))
		{
			// wait for buffer to be filled
			if (!s->inb->finishflag)
				*b = true;

			break;
		}

		// Skip header
		bs_skipBytes(&s->bitb, 8);

		// if header tag is data, stop
		if (w->header.riff.i == Tagdata.i)
			break;

		w->size = w->header.size;

		// corrupted ?
		if ((w->size < 4) || (w->size & 3))
		{
			LogInfo("Corrupted RIFF header %.4s size: %d", w->header.riff.c, w->header.size);
			break;
		}
	}

	if (!*b) IStream_Free(s, &s->codec.data);

	return NULL;
}
