#include "tag.h"
#include "frameheader.h"
#include "frames.h"
#include "foundation/error.h"
#include <string.h>
#include <new>
#include "nsid3v2.h" // for serialize flags

/* === ID3v2 common === */
static bool IdentifierMatch(const int8_t *id1, const int8_t *id2)
{
	return !memcmp(id1, id2, 4);
}


ID3v2::Tag::Tag(const ID3v2::Header &_header) :  Header(_header)
{
}

void ID3v2::Tag::RemoveFrame(ID3v2::Frame *frame)
{
	frames.erase(frame);
	delete frame;
}

ID3v2::Frame *ID3v2::Tag::FindFirstFrame(const int8_t *id) const
{
	if (!id)
		return 0;

	for (FrameList::const_iterator itr=frames.begin();itr != frames.end();itr++)
	{
		if (IdentifierMatch(itr->GetIdentifier(), id))
			return *itr;
	}
	return 0;
}

ID3v2::Frame *ID3v2::Tag::FindNextFrame(const Frame *frame) const
{
	FrameList::const_iterator itr=frame;
	for (itr++;itr != frames.end();itr++)
	{
		if (IdentifierMatch(itr->GetIdentifier(), frame->GetIdentifier()))
			return *itr;
	}
	return 0;
}

void ID3v2::Tag::RemoveFrames(const int8_t *id)
{
	// TODO: not exactly the fastest way
	Frame *frame;
	while (frame = FindFirstFrame(id))
		frames.erase(frame);
}
void ID3v2::Tag::AddFrame(ID3v2::Frame *frame)
{
	frames.push_back(frame);
}

ID3v2::Frame *ID3v2::Tag::EnumerateFrame(const ID3v2::Frame *position) const
{
	if (!position)
		return frames.front();
	else
		return (ID3v2::Frame *)position->next;

}
/* === ID3v2.2 === */

ID3v2_2::Tag::Tag(const ID3v2::Header &_header) : ID3v2::Tag(_header), extendedHeader(_header)
{
}

ID3v2_2::Tag::~Tag()
{
	frames.deleteAll();
}

static inline void Advance(const void *&data, size_t &len, size_t amount)
{
	data = (const uint8_t *)data + amount;
	len -= amount;
}

int ID3v2_2::Tag::Parse(const void *data, size_t len)
{
	/* Is there an extended header? */
	if (Header::HasExtendedHeader())
	{
		size_t read=0;
		if (extendedHeader.Parse(data, len, &read) != 0)
		{
			return 1;
		}
		Advance(data, len, read);
	}

	/* Read each frame */
	while (len >= FrameHeader::SIZE)
	{
		/* if next byte is zero, we've hit the padding area, GTFO */
		if (*(uint8_t *)data == 0x0)
			break;

		/* Read frame header first */
		FrameHeader frame_header(*this, data);
		Advance(data, len, FrameHeader::SIZE);

		if (!frame_header.IsValid())
			return 1;

		/* read frame data */
		Frame *new_frame = new (std::nothrow) Frame(frame_header);
		if (!new_frame)
			return NErr_OutOfMemory;

		size_t read=0;
		if (new_frame->Parse(data, len, &read) == 0)
		{
			Advance(data, len, read);
			frames.push_back(new_frame);
		}
		else
		{
			delete new_frame;
			return 1;
		}
	}
	return 0;
}

int ID3v2_2::Tag::SerializedSize(uint32_t *length, uint32_t padding_size, int flags) const
{
	size_t current_length=0;

	Header new_header(*this);
	/* TODO: going to clear this, for now */
	new_header.ClearExtendedHeader();
	switch(flags & Serialize_UnsynchronizeMask)
	{
	case Serialize_Unsynchronize:
		// TODO:
		break;
	case Serialize_NoUnsynchronize:
		new_header.ClearUnsynchronized();
		break;
	}

	current_length += Header::SIZE;

	if (new_header.HasExtendedHeader())
	{
		// TODO: deal with extended header
	}

	for (FrameList::const_iterator itr=frames.begin();itr != frames.end();itr++)
	{
		uint32_t written;
		const ID3v2_2::Frame *frame = (const ID3v2_2::Frame *)*itr;
		int ret = frame->SerializedSize(&written, new_header, flags);
		if (ret != NErr_Success)
			return ret;
		current_length += written;
	}

	switch(flags & SerializedSize_PaddingMask)
	{
	case SerializedSize_Padding:
		current_length += padding_size;
		break;
	case SerializedSize_AbsoluteSize:
		if (current_length < padding_size)
			current_length = padding_size;
		break;
	case SerializedSize_BlockSize:
		{
			uint32_t additional = current_length % padding_size;
			current_length += additional;
		}
		break;
	}

	*length = current_length;
	return NErr_Success;
}

int ID3v2_2::Tag::Serialize(void *data, uint32_t len, int flags) const
{
	uint8_t *data_itr = (uint8_t *)data;
	uint32_t current_length=0;

	// write header.  note the passed-in length is guaranteed to be correct, as it was generated by SerializedSize
	Header new_header(this, len-10);
	/* TODO: going to clear this, for now */
	new_header.ClearExtendedHeader();
	switch(flags & Serialize_UnsynchronizeMask)
	{
	case Serialize_Unsynchronize:
		// TODO:
		break;
	case Serialize_NoUnsynchronize:
		new_header.ClearUnsynchronized();
		break;
	}

	new_header.Serialize(data);

	current_length += Header::SIZE;
	data_itr += Header::SIZE;

	if (new_header.HasExtendedHeader())
	{
		// TODO: write extended header
	}

	for (FrameList::const_iterator itr=frames.begin();itr != frames.end();itr++)
	{
		uint32_t written;
		const ID3v2_2::Frame *frame = (const ID3v2_2::Frame *)*itr;
		int ret = frame->Serialize((void *)data_itr, &written, new_header, flags);
		if (ret != NErr_Success)
			return ret;
		current_length += written;
		data_itr += written;
	}

	// write padding
	memset(data_itr, 0, len-current_length);

	return NErr_Success;
}

static bool IdentifierMatch3(const int8_t *id1, const int8_t *id2)
{
	return !memcmp(id1, id2, 3);
}

ID3v2_2::Frame *ID3v2_2::Tag::FindFirstFrame(int frame_id) const
{
	if (!ValidFrameID(frame_id))
		return 0;
	return (ID3v2_2::Frame *)ID3v2::Tag::FindFirstFrame(frame_ids[frame_id].v2);
};


void ID3v2_2::Tag::RemoveFrames(int frame_id)
{
	// TODO: not exactly the fastest way
	Frame *frame;
	while (frame = FindFirstFrame(frame_id))
		frames.erase(frame);
}

ID3v2_2::Frame *ID3v2_2::Tag::NewFrame(int frame_id, int flags) const
{
	if (!ValidFrameID(frame_id) || !frame_ids[frame_id].v2)
		return 0;
	return new (std::nothrow) ID3v2_2::Frame(*this, frame_ids[frame_id].v2, flags); 
}

/* === ID3v2.3 === */

ID3v2_3::Tag::Tag(const ID3v2::Header &_header) : ID3v2::Tag(_header), extendedHeader(_header)
{
}

ID3v2_3::Tag::~Tag()
{
	frames.deleteAll();
}

int ID3v2_3::Tag::Parse(const void *data, size_t len)
{
	/* Is there an extended header? */
	if (Header::HasExtendedHeader())
	{
		size_t read=0;
		if (extendedHeader.Parse(data, len, &read) != 0)
		{
			return 1;
		}
		Advance(data, len, read);
	}

	/* Read each frame */
	while (len >= FrameHeader::SIZE)
	{
		/* if next byte is zero, we've hit the padding area, GTFO */
		if (*(uint8_t *)data == 0x0)
			break;

		/* Read frame header first */
		FrameHeader frame_header(*this, data);
		Advance(data, len, FrameHeader::SIZE);

		if (!frame_header.IsValid())
			return 1;

		/* read frame data */
		Frame *new_frame = new (std::nothrow) Frame(frame_header);
		if (!new_frame)
			return NErr_OutOfMemory;

		size_t read=0;
		if (new_frame->Parse(data, len, &read) == 0)
		{
			Advance(data, len, read);
			frames.push_back(new_frame);
		}
		else
		{
			delete new_frame;
			return 1;
		}
	}
	return 0;
}

int ID3v2_3::Tag::SerializedSize(uint32_t *length, uint32_t padding_size, int flags) const
{
	size_t current_length=0;

	Header new_header(*this);
	/* TODO: going to clear this, for now */
	new_header.ClearExtendedHeader();
	switch(flags & Serialize_UnsynchronizeMask)
	{
	case Serialize_Unsynchronize:
		// TODO:
		break;
	case Serialize_NoUnsynchronize:
		new_header.ClearUnsynchronized();
		break;
	}

	current_length += Header::SIZE;

	if (new_header.HasExtendedHeader())
	{
		// TODO: deal with extended header
	}

	for (FrameList::const_iterator itr=frames.begin();itr != frames.end();itr++)
	{
		uint32_t written;
		const ID3v2_3::Frame *frame = (const ID3v2_3::Frame *)*itr;
		int ret = frame->SerializedSize(&written, new_header, flags);
		if (ret != NErr_Success)
			return ret;
		current_length += written;
	}

	switch(flags & SerializedSize_PaddingMask)
	{
	case SerializedSize_Padding:
		current_length += padding_size;
		break;
	case SerializedSize_AbsoluteSize:
		if (current_length < padding_size)
			current_length = padding_size;
		break;
	case SerializedSize_BlockSize:
		{
			uint32_t additional = padding_size - (current_length % padding_size);
			if (additional == padding_size)
				additional=0;
			current_length += additional;
		}
		break;
	}

	*length = current_length;
	return NErr_Success;
}

int ID3v2_3::Tag::Serialize(void *data, uint32_t len, int flags) const
{
	uint8_t *data_itr = (uint8_t *)data;
	uint32_t current_length=0;

	// write header.  note the passed-in length is guaranteed to be correct, as it was generated by SerializedSize
	Header new_header(this, len-10);
	/* TODO: going to clear this, for now */
	new_header.ClearExtendedHeader();
	switch(flags & Serialize_UnsynchronizeMask)
	{
	case Serialize_Unsynchronize:
		// TODO:
		break;
	case Serialize_NoUnsynchronize:
		new_header.ClearUnsynchronized();
		break;
	}

	new_header.Serialize(data);

	current_length += Header::SIZE;
	data_itr += Header::SIZE;

	if (new_header.HasExtendedHeader())
	{
		// TODO: write extended header
	}

	for (FrameList::const_iterator itr=frames.begin();itr != frames.end();itr++)
	{
		uint32_t written;
		const ID3v2_3::Frame *frame = (const ID3v2_3::Frame *)*itr;
		int ret = frame->Serialize((void *)data_itr, &written, new_header, flags);
		if (ret != NErr_Success)
			return ret;
		current_length += written;
		data_itr += written;
	}

	// write padding
	memset(data_itr, 0, len-current_length);

	return NErr_Success;
}

ID3v2_3::Frame *ID3v2_3::Tag::FindFirstFrame(int frame_id) const
{
	if (!ValidFrameID(frame_id))
		return 0;
	return (ID3v2_3::Frame *)ID3v2::Tag::FindFirstFrame(frame_ids[frame_id].v3);
};

void ID3v2_3::Tag::RemoveFrames(int frame_id)
{
	// TODO: not exactly the fastest way
	Frame *frame;
	while (frame = FindFirstFrame(frame_id))
		frames.erase(frame);
}

ID3v2_3::Frame *ID3v2_3::Tag::NewFrame(int frame_id, int flags) const
{
	if (!ValidFrameID(frame_id) || !frame_ids[frame_id].v3)
		return 0;
	return new (std::nothrow) ID3v2_3::Frame(*this, frame_ids[frame_id].v3, flags); 
}

/* === ID3v2.4 === */
ID3v2_4::Tag::Tag(const ID3v2::Header &_header) : ID3v2::Tag(_header), extendedHeader(_header)
{
}

ID3v2_4::Tag::~Tag()
{
	frames.deleteAll();
}

int ID3v2_4::Tag::Parse(const void *data, size_t len)
{
	/* Is there an extended header? */
	if (Header::HasExtendedHeader())
	{
		size_t read=0;
		if (extendedHeader.Parse(data, len, &read) != 0)
		{
			return 1;
		}
		Advance(data, len, read);
	}

	/* Read each frame */
	while (len >= FrameHeader::SIZE)
	{
		/* if next byte is zero, we've hit the padding area, GTFO */
		if (*(uint8_t *)data == 0x0)
			break;

		/* Read frame header first */
		FrameHeader frame_header(*this, data);
		Advance(data, len, FrameHeader::SIZE);

		if (!frame_header.IsValid())
			return 1;

		/* read frame data */
		Frame *new_frame = new (std::nothrow) Frame(frame_header);
		if (!new_frame)
			return NErr_OutOfMemory;

		size_t read=0;
		if (new_frame->Parse(data, len, &read) == 0)
		{
			Advance(data, len, read);
			frames.push_back(new_frame);
		}
		else
		{
			delete new_frame;
			return 1;
		}
	}
	return 0;
}

int ID3v2_4::Tag::SerializedSize(uint32_t *length, uint32_t padding_size, int flags) const
{
	size_t current_length=0;
	
	Header new_header(*this);
	/* TODO: going to clear this, for now */
	new_header.ClearExtendedHeader();
	switch(flags & Serialize_UnsynchronizeMask)
	{
	case Serialize_Unsynchronize:
		// TODO:
		break;
	case Serialize_NoUnsynchronize:
		new_header.ClearUnsynchronized();
		break;
	}

	current_length += Header::SIZE;

	if (new_header.HasExtendedHeader())
	{
		// TODO: deal with extended header
	}

	for (FrameList::const_iterator itr=frames.begin();itr != frames.end();itr++)
	{
		uint32_t written;
		const ID3v2_4::Frame *frame = (const ID3v2_4::Frame *)*itr;
		int ret = frame->SerializedSize(&written, new_header, flags);
		if (ret != NErr_Success)
			return ret;
		current_length += written;
	}	

	switch(flags & SerializedSize_PaddingMask)
	{
	case 0:
		/* we can only write a footer if there is no padding */
		if (new_header.FooterValid() || new_header.HasFooter())
		{
			current_length += Header::SIZE;
		}
		break;
	case SerializedSize_Padding:
		current_length += padding_size;
		break;
	case SerializedSize_AbsoluteSize:
		if (current_length < padding_size)
			current_length = padding_size;
		break;
	case SerializedSize_BlockSize:
		{
			uint32_t additional = current_length % padding_size;
			current_length += additional;
		}
		break;
	}

	*length = current_length;
	return NErr_Success;
}

int ID3v2_4::Tag::Serialize(void *data, uint32_t len, int flags) const
{
	uint8_t *data_itr = (uint8_t *)data;
	uint32_t current_length=0;

	// write header.  note the passed-in length is guaranteed to be correct, as it was generated by SerializedSize
	bool write_footer=false;
	if ((flags & SerializedSize_PaddingMask) == 0 && (FooterValid() || HasFooter()))
		write_footer=true;		

	Header new_header(this, write_footer?(len-20):(len-10));
	new_header.SetFooter(write_footer);

	/* TODO: going to clear this, for now */
	new_header.ClearExtendedHeader();
	switch(flags & Serialize_UnsynchronizeMask)
	{
	case Serialize_Unsynchronize:
		// TODO:
		break;
	case Serialize_NoUnsynchronize:
		new_header.ClearUnsynchronized();
		break;
	}

	new_header.SerializeAsHeader(data);

	current_length += Header::SIZE;
	data_itr += Header::SIZE;

	if (new_header.HasExtendedHeader())
	{
		// TODO: write extended header
	}

	for (FrameList::const_iterator itr=frames.begin();itr != frames.end();itr++)
	{
		uint32_t written;
		const ID3v2_4::Frame *frame = (const ID3v2_4::Frame *)*itr;
		int ret = frame->Serialize((void *)data_itr, &written, new_header, flags);
		if (ret != NErr_Success)
			return ret;
		current_length += written;
		data_itr += written;
	}

	if (write_footer)
	{
		new_header.SerializeAsFooter(data_itr);
		current_length += Header::SIZE;
		data_itr += Header::SIZE;
	}

	// write padding
	memset(data_itr, 0, len-current_length);

	return NErr_Success;
}

ID3v2_4::Frame *ID3v2_4::Tag::FindFirstFrame(int frame_id) const
{
	if (!ValidFrameID(frame_id))
		return 0;
	return (ID3v2_4::Frame *)ID3v2::Tag::FindFirstFrame(frame_ids[frame_id].v4);
};

void ID3v2_4::Tag::RemoveFrames(int frame_id)
{
	// TODO: not exactly the fastest way
	Frame *frame;
	while (frame = FindFirstFrame(frame_id))
		frames.erase(frame);
}

ID3v2_4::Frame *ID3v2_4::Tag::NewFrame(int frame_id, int flags) const
{
	if (!ValidFrameID(frame_id) || !frame_ids[frame_id].v4)
		return 0;
	return new (std::nothrow) ID3v2_4::Frame(*this, frame_ids[frame_id].v4, flags); 
}
