// The authors have released ID3Lib as Public Domain (PD) and claim no copyright,
// patent or other intellectual property protection in this work. This means that
// it may be modified, redistributed and used in commercial and non-commercial
// software and hardware without restrictions. ID3Lib is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
//
// The ID3Lib authors encourage improvements and optimisations to be sent to the
// ID3Lib coordinator, currently Dirk Mahoney (dirk@id3.org). Approved
// submissions may be altered, and will be included and released under these terms.
//
// Mon Nov 23 18:34:01 1998
// improved/optimized/whatever 10/30/00 JF


#include <string.h>
#include "id3_tag.h"


ID3_Frame::ID3_Frame(ID3_FrameID id)
{
	luint lwordsForFields = 0;

	version = ID3_TAGVERSION;
	revision = ID3_TAGREVISION;
	numFields = 0;
	fields = NULL;
	groupingID[0] = 0;
	encryptionID[0] = 0;
	compression = false;

	lwordsForFields = (((luint) ID3FN_LASTFIELDID) - 1) / (sizeof (luint) * 8);

	if ((((luint) ID3FN_LASTFIELDID) - 1) % (sizeof (luint) * 8) != 0)
		lwordsForFields++;

	if (fieldBits = (bitset)calloc(lwordsForFields, sizeof(*fieldBits)))
	{
		for (luint i = 0; i < lwordsForFields; i++)
			fieldBits[i] = 0;
	}
	else
		ID3_THROW (ID3E_NoMemory);

	SetID (id);
}


ID3_Frame::~ID3_Frame (void)
{
	Clear();

	if (fieldBits)
		free(fieldBits);
}


void ID3_Frame::Clear (void)
{
	if (numFields && fields)
	{
		for (luint i = 0; i < numFields; i++)
			delete fields[i];

		free(fields);

		fields = NULL;
		numFields = 0;
		hasChanged = true;
	}

	return ;
}


void ID3_Frame::SetID (ID3_FrameID id)
{
	ID3_FrameDef *info;

	Clear();

	if (id != ID3FID_NOFRAME)
	{
		if (info = ID3_FindFrameDef (id))
		{
			frameID = id;

			numFields = 0;

			while (info->fieldDefs[numFields].id != ID3FN_NOFIELD)
				numFields++;

			if ((fields = (ID3_Field **)calloc(numFields, sizeof(ID3_Field*))) == NULL)
				ID3_THROW (ID3E_NoMemory);
			else
			{
				for (luint i = 0; i < numFields; i++)
				{
					if ((fields[i] = new ID3_Field) == NULL)
						ID3_THROW (ID3E_NoMemory);
					else
					{
						fields[i]->name = info->fieldDefs[i].id;
						fields[i]->type = info->fieldDefs[i].type;
						fields[i]->fixedLength = info->fieldDefs[i].fixedLength;
						fields[i]->ioVersion = info->fieldDefs[i].version;
						fields[i]->ioRevision = info->fieldDefs[i].revision;
						fields[i]->control = info->fieldDefs[i].control;
						fields[i]->flags = info->fieldDefs[i].flags;

						// tell the frame that this field is present
						BS_SET (fieldBits, fields[i]->name);
					}
				}

				hasChanged = true;
			}
		}
		else
			ID3_THROW (ID3E_InvalidFrameID);
	}

	return ;
}


ID3_FrameID ID3_Frame::GetID (void)
{
	return frameID;
}


void ID3_Frame::SetVersion (uchar ver, uchar rev)
{
	if (version != ver || revision != rev)
		hasChanged = true;

	version = ver;
	revision = rev;

	return ;
}


lsint ID3_Frame::FindField (ID3_FieldID fieldName)
{
	if (BS_ISSET (fieldBits, fieldName))
	{
		lsint num = 0;
		while (num < (lsint) numFields)
		{
			if (fields[num]->name == fieldName) return num;
			num++;
		}
		return -1;
	}
	return 0;
}


ID3_Field& ID3_Frame::Field (ID3_FieldID fieldName)
{
	luint fieldNum = FindField (fieldName);

	if (fieldNum == -1)
		ID3_THROW (ID3E_FieldNotFound);

	return *fields[fieldNum];
}


void ID3_Frame::UpdateFieldDeps (void)
{
	for (luint i = 0; i < numFields; i++)
	{
		if (fields[i]->flags & ID3FF_ADJUSTEDBY)
		{
			switch (fields[i]->type)
			{
			case ID3FTY_BITFIELD:
				{
					//luint value = 0;

					// now find the field on which this
					// field is dependent and get a copy
					// of the value of that field.
					// then adjust the fixedLength of this
					// field to that value / 8.
				}
				break;
			}
		}
	}

	return ;
}


void ID3_Frame::UpdateStringTypes(void)
{
	for (luint i = 0; i < numFields; i++)
	{
		if (fields[i]->flags & ID3FF_ADJUSTENC)
		{
			ID3_TextEnc enc;
			ID3_FieldType newType;

			enc = (ID3_TextEnc) Field(ID3FN_TEXTENC).Get();

			switch (enc)
			{
			case ID3TE_ASCII:
				newType = ID3FTY_ASCIISTRING;
				break;

			case ID3TE_UNICODE:
				newType = ID3FTY_UNICODESTRING;
				break;

			case ID3TE_UTF8:
				newType = ID3FTY_UTF8STRING;
				break;

			default:
				newType = ID3FTY_ASCIISTRING;
				break;
			}

			fields[i]->type = newType;
		}
	}
}


luint ID3_Frame::Size(void)
{
	luint bytesUsed = 0;
	ID3_FrameHeader header;

	header.SetVersion (version, revision);
	bytesUsed = header.Size();

	if (strlen (encryptionID))
		bytesUsed++;

	if (strlen (groupingID))
		bytesUsed++;

	// this call is to tell the string fields
	// what they should be rendered/parsed as
	// (ASCII or Unicode)
	UpdateStringTypes();

	for (luint i = 0; i < numFields; i++)
	{
		fields[i]->SetVersion (version, revision);
		bytesUsed += fields[i]->BinSize();
	}

	return bytesUsed;
}


bool ID3_Frame::HasChanged (void)
{
	if (hasChanged) return hasChanged;

	for (luint i = 0; i < numFields; i++)
	{
		bool changed = fields[i]->HasChanged();
		if (changed) return changed;
	}
	return 0;
}