// $Id$

// id3lib: a C++ library for creating and manipulating id3v1/v2 tags
// Copyright 1999, 2000  Scott Thomas Haug

/* This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Library General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/* The id3lib authors encourage improvements and optimisations to be sent to
 * the id3lib coordinator.  Please see the README file for details on where to
 * send such submissions.  See the AUTHORS file for a list of people who have
 * contributed to id3lib.  See the ChangeLog file for a list of changes to
 * id3lib.  These files are distributed with id3lib at
 * http://download.sourceforge.net/id3lib/
 */

#include "header_frame.h"
#include "frame_def.h"
#include "field_def.h"
#include "field_impl.h"

#include <id3/utils.h>
#include <id3/io_helpers.h>

using namespace dami;

void ID3_FrameHeader::SetUnknownFrame(const char *id)
{
	Clear();

	int	 id_len = strlen(id);

	if (id_len > 4) return;

	_frame_def = new ID3_FrameDef;

	if (_frame_def == NULL) return;

	memset(_frame_def, 0, sizeof(ID3_FrameDef));

	_frame_def->eID		 = ID3FID_NOFRAME;
	_frame_def->bTagDiscard	 = false;
	_frame_def->bFileDiscard = false;
	_frame_def->aeFieldDefs	 = ID3_FieldDef::DEFAULT;
	_frame_def->sDescription = NULL;

	if (id_len <= 3) strncpy(_frame_def->sShortTextID, id, 3);
	else		 strncpy(_frame_def->sLongTextID, id, 4);

	_dyn_frame_def = true;
}

bool ID3_FrameHeader::SetFrameID(ID3_FrameID id)
{
	if (id == ID3FID_NOFRAME || id == this->GetFrameID()) return false;

	ID3_FrameDef	*frame_def = ID3_FindFrameDef(id);

	if (frame_def == NULL) return false;

	_frame_def = frame_def;
	_flags.set(TAGALTER, _frame_def->bTagDiscard);
	_flags.set(FILEALTER, _frame_def->bFileDiscard);

	_changed = true;

	return true;
}

size_t ID3_FrameHeader::Size() const
{
	if (!_info) return 0;

	return  _info->frame_bytes_id   +
		_info->frame_bytes_size +
		_info->frame_bytes_flags;
}

bool ID3_FrameHeader::Parse(ID3_Reader& reader)
{
	ID3D_NOTICE("ID3_FrameHeader::Parse(): getCur() = " << reader.getCur());

	io::ExitTrigger	 et(reader);

	if (!_info)				    return false;
	if (reader.getEnd() < reader.getCur() + 10) return false;

	String	 textID = io::readText(reader, _info->frame_bytes_id);

	ID3D_NOTICE("ID3_FrameHeader::Parse: textID = " << textID);
	ID3D_NOTICE("ID3_FrameHeader::Parse: getCur() = " << reader.getCur());

	ID3_FrameID	 fid = ID3_FindFrameID(textID.c_str());

	if (ID3FID_NOFRAME == fid)
	{
		this->SetUnknownFrame(textID.c_str());

		ID3D_NOTICE("ID3_FrameHeader::Parse: unknown frame id");
	}
	else
	{
		this->SetFrameID(fid);
	}

	/* Frame data size must be read as an
	 * unsynchronized integer for ID3v2.4 tags!
	 */
	uint32	 dataSize = 0;

	if (_spec == ID3V2_4_0)	dataSize = io::readUInt28(reader);
	else			dataSize = io::readBENumber(reader, _info->frame_bytes_size);

	ID3D_NOTICE("ID3_FrameHeader::Parse: dataSize = " << dataSize);
	ID3D_NOTICE("ID3_FrameHeader::Parse: getCur() = " << reader.getCur());

	this->SetDataSize(dataSize);

	uint32	 flags = io::readBENumber(reader, _info->frame_bytes_flags);

	_flags.add(flags);

	ID3D_NOTICE("ID3_FrameHeader::Parse: flags = " << flags);
	ID3D_NOTICE("ID3_FrameHeader::Parse: getCur() = " << reader.getCur());

	et.setExitPos(reader.getCur());

	return true;
}

ID3_Err ID3_FrameHeader::Render(ID3_Writer &writer) const
{
	if (_frame_def == NULL)
	{
		ID3D_WARNING("ID3_FrameHeader::Render(): _frame_def is NULL!");

		return ID3E_InvalidFrameID;
	}

	char	*textID;

	if (_info->frame_bytes_id == strlen(_frame_def->sShortTextID))	textID = _frame_def->sShortTextID;
	else								textID = _frame_def->sLongTextID;

	ID3D_NOTICE("ID3_FrameHeader::Render(): writing " << textID << ", " << (int) _info->frame_bytes_size << " bytes");

	writer.writeChars((uchar *) textID, _info->frame_bytes_id);

	/* Frame data size must be written as an
	 * unsynchronized integer for ID3v2.4 tags!
	 */
	if (_spec == ID3V2_4_0)	io::writeUInt28(writer, _data_size);
	else			io::writeBENumber(writer, _data_size, _info->frame_bytes_size);

	io::writeBENumber(writer, _flags.get(), _info->frame_bytes_flags);

	return ID3E_NoError;
}

const char *ID3_FrameHeader::GetTextID() const
{
	char	*textID = NULL;

	if (_info && _frame_def)
	{
		if (_info->frame_bytes_id == strlen(_frame_def->sShortTextID)) textID = _frame_def->sShortTextID;
		else							       textID = _frame_def->sLongTextID;
	}

	return (const char *) textID;
}

ID3_FrameHeader &ID3_FrameHeader::operator =(const ID3_FrameHeader &hdr)
{
	if (this == &hdr) return *this;

	this->Clear();
	this->ID3_Header::operator =(hdr);

	if (!hdr._dyn_frame_def)
	{
		_frame_def = hdr._frame_def;
	}
	else
	{
		_frame_def = new ID3_FrameDef;

		if (_frame_def == NULL) return *this;

		memset(_frame_def, 0, sizeof(ID3_FrameDef));

		_frame_def->eID		 = hdr._frame_def->eID;
		_frame_def->bTagDiscard	 = hdr._frame_def->bTagDiscard;
		_frame_def->bFileDiscard = hdr._frame_def->bFileDiscard;
		_frame_def->aeFieldDefs	 = hdr._frame_def->aeFieldDefs;

		strcpy(_frame_def->sShortTextID, hdr._frame_def->sShortTextID);
		strcpy(_frame_def->sLongTextID, hdr._frame_def->sLongTextID);

		_dyn_frame_def = true;
	}

	return *this;
}

ID3_FrameID ID3_FrameHeader::GetFrameID() const
{
	ID3_FrameID	 eID = ID3FID_NOFRAME;

	if (_frame_def != NULL) eID = _frame_def->eID;

	return eID;
}

const ID3_FrameDef *ID3_FrameHeader::GetFrameDef() const
{
	return _frame_def;
}

bool ID3_FrameHeader::Clear()
{
	bool	 changed = this->ID3_Header::Clear();

	if (_dyn_frame_def)
	{
		delete _frame_def;

		_dyn_frame_def = false;
		changed	       = true;
	}

	if (_frame_def)
	{
		_frame_def = NULL;
		changed	   = true;
	}

	return changed;
}
