/* libsswf_file.c++ -- written by Alexis WILKE for Made to Order Software Corp. (c) 2002-2008 */

/*

Copyright (c) 2002-2008 Made to Order Software Corp.

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and
associated documentation files (the "Software"), to
deal in the Software without restriction, including
without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial
portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

*/

/** \file
 *
 * \brief The implementation of the sswf::Data class
 *
 * This file declares the body of the functions which are not
 * inline. It is part of the SSWF library.
 */



#include	"sswf/libsswf.h"

using namespace sswf;








/////////////////////////////////////////// Data

/** \class sswf::Data
 *
 * \brief Buffer object used to save the SWF movie
 *
 * This object is a buffer used to save the SWF movie in a memory
 * buffer.
 *
 * It grows as required and has functions specific to SWF such
 * as Align() and WriteBits().
 *
 * \sa sswf::Data::Align()
 * \sa sswf::Data::WriteBits()
 */


/** \brief Initialize a Data buffer
 *
 * This function initializes a Data buffer. By default, a Data
 * buffer is empty.
 */
Data::Data()
{
	f_pos = 0;
	f_size = 0;
	f_data = 0;
}


/** \brief Cleanup a Data buffer
 *
 * This function does nothing since the memory used by the Data
 * buffer is managed by the memory manager directly.
 */
Data::~Data()
{
	// the memory manager takes care of everything here
}


/** \fn sswf::Data::Empty()
 *
 * \brief Reset the size
 *
 * This function marks the Data buffer as empty.
 *
 * All the data previously saved in this buffer is lost.
 */


/** \fn sswf::Data::GetSize() const
 *
 * \brief Returns the current size of the buffer
 *
 * Returns the size of the buffer in bits. This is the exact position
 * in the buffer.
 *
 * In general, one needs to call ByteSize() instead.
 *
 * \return The size of the buffer in bits
 */


/** \fn sswf::Data::ByteSize() const
 *
 * \brief Returns the size of the buffer in bytes
 *
 * Returns the size of the buffer in bytes. The exact size of the
 * buffer is in bits. The byte size is the bit size rounded up.
 *
 * \return The size of the buffer in bytes
 */


/** \brief Align the buffer position to the next byte
 *
 * Whenever writing an SWF movie, it is most often compressed
 * on bytes and quite often on bits. Whenever bits are used to
 * save one structure (such as a rectangle), the following data
 * which are aligned on a byte need to appear on a byte.
 *
 * This function ensures that this is indeed the case.
 *
 * This function can safely be called multiple times in a raw.
 * The position will be adjusted only the first time and only
 * if necessary.
 */
void Data::Align(void)
{
	// align to the next byte
	f_pos = (f_pos + CHAR_BIT - 1) & -CHAR_BIT;
}


/** \brief Make sure that the buffer is at least of that size
 *
 * This function ensures that the output buffer is at least of
 * the specified number of bits (notice: BITS, bot BYTES.)
 *
 * This is used to create a buffer for a tag that can later be
 * modified with the Overwrite functions.
 *
 * \param[in] size The new buffer size in bits
 *
 * \sa sswf::Data::Overwrite
 * \sa sswf::Data::OverwriteByte
 * \sa sswf::Data::OverwriteShort
 * \sa sswf::Data::OverwriteLong
 */
void Data::AdjustSize(size_t size)
{
	int		pos;

	if(size > f_size) {
		// got to enlarge the buffer (adjust to the next 256 bytes)
		pos = f_size / CHAR_BIT;
		f_size = (f_pos + size + CHAR_BIT * 256 - 1) & -(CHAR_BIT * 256);
		f_data = (char *) MemRealloc(f_data, f_size / CHAR_BIT, "Data buffer");

		// clear so bits can safely be saved
		memset(f_data + pos, 0, f_size / CHAR_BIT - pos);
	}
}


/** \brief Set the size in bits
 *
 * This function can be used to shrink the buffer.
 *
 * This is used after an AdjustSize() with a size which was too large.
 *
 * In general, because the resulting size is determined after
 * compression.
 *
 * \param[in] size The new bit size of the buffer
 */
void Data::SetSize(unsigned long size)
{
	assert(size <= f_pos, "can't enlarge a data object with a call to the SetSize() function");

	f_pos = size;
}


/** \brief Overwrite data at the specified offset
 *
 * This function is used to overwrite data at offset on size bytes
 * with the data pointed by ptr. This function will not enlarge
 * the Data buffer and thus is must be used to overwrite only.
 *
 * \sa sswf::Data::OverwriteByte
 * \sa sswf::Data::OverwriteShort
 * \sa sswf::Data::OverwriteLong
 */
void Data::Overwrite(size_t offset, const void *ptr, size_t size)
{
	assert(offset + size < f_pos / CHAR_BIT, "trying to overwrite outside the buffer");
	memcpy(f_data + offset, ptr, size);
}


/** \brief Overwrite data at the specified offset
 *
 * This function is used to overwrite one byte at the specified
 * offset. This function will not enlarge the Data buffer and
 * thus is must be used to overwrite only.
 *
 * \sa sswf::Data::Overwrite
 * \sa sswf::Data::OverwriteShort
 * \sa sswf::Data::OverwriteLong
 */
void Data::OverwriteByte(size_t offset, char c)
{
	assert(offset + 1 < f_pos / CHAR_BIT, "trying to overwrite outside the buffer");
	f_data[offset] = c;
}


/** \brief Overwrite data at the specified offset
 *
 * This function is used to overwrite one short at the specified
 * offset. This function will not enlarge the Data buffer and
 * thus is must be used to overwrite only.
 *
 * The short value is saved in little endian (least significant
 * byte first.)
 *
 * \sa sswf::Data::Overwrite
 * \sa sswf::Data::OverwriteByte
 * \sa sswf::Data::OverwriteLong
 */
void Data::OverwriteShort(size_t offset, short s)
{
	assert(offset + 2 < f_pos / CHAR_BIT, "trying to overwrite outside the buffer");
	f_data[offset] = (char) s;
	f_data[offset + 1] = (char) (s >> 8);
}


/** \brief Overwrite data at the specified offset
 *
 * This function is used to overwrite one long at the specified
 * offset (i.e. 4 bytes). This function will not enlarge the Data
 * buffer and thus is must be used to overwrite only.
 *
 * The long value is saved in little endian (least significant
 * byte first.)
 *
 * \sa sswf::Data::Overwrite
 * \sa sswf::Data::OverwriteByte
 * \sa sswf::Data::OverwriteShort
 */
void Data::OverwriteLong(size_t offset, long l)
{
	assert(offset + 4 < f_pos / CHAR_BIT, "trying to overwrite outside the buffer");
	f_data[offset    ] = (char) l;
	f_data[offset + 1] = (char) (l >> 8);
	f_data[offset + 2] = (char) (l >> 16);
	f_data[offset + 3] = (char) (l >> 24);
}


/** \brief Write data at the end of the buffer
 *
 * This function writes size bytes from ptr at the end of the
 * buffer. It automatically calls Align() to make sure that
 * the alignment is correct.
 *
 * \param[in] ptr The pointer to the data to append
 * \param[in] size The number of bytes to append
 */
void Data::Write(const void *ptr, size_t size)
{
	size_t		bit_size;

	Align();
	bit_size = size * CHAR_BIT;
	AdjustSize(f_pos + bit_size);
	memcpy(f_data + f_pos / CHAR_BIT, ptr, size);
	f_pos += bit_size;

#if DEBUG
MemTest(f_data);
#endif
}



/** \brief Write bits at the end of the buffer
 *
 * This function writes the specified number of bits from the specified
 * value.
 *
 * The least significant bits of value are used. Thus, a 1 bit value is
 * defined as 0 or 1. A 2 bits value is defined as 0, 1, 2 or 3. And so
 * on...
 *
 * It is valid to save up to 31 bits with this function. In debug mode,
 * the function asserts if it looks like the parameters are invalid.
 *
 * \param[in] value The value of which bits are saved
 * \param[in] bits The number of bits to be saved
 */
void Data::WriteBits(long value, size_t bits)
{
	int		top;

	assert(bits >= 32 || ((-1 << bits) & value) == 0 || ((-1 << bits) & value) == (-1 << bits),
			"WriteBits() used to write %d bits of 0x%08lX - bits are lost", (int) bits, value);

	AdjustSize(f_pos + bits);

	top = sizeof(long) * CHAR_BIT - bits;
	if(top != 0) {		// avoid a shift by zero which doesn't always work (system dependent)
		value <<= top;	// put the 1st bit (most significant) in the sign bit
	}

	// TODO: there are much more efficent ways to do that by calculating shift values and
	// shifting 1 to 8 bits in place instead of looping like crazy!!!
	while(bits > 0) {
		bits--;
		if(value < 0) {
			f_data[f_pos / CHAR_BIT] |= (1 << (CHAR_BIT - 1 - (f_pos & (CHAR_BIT - 1))));
		}
		f_pos++;
		value *= 2;
	}
}


/** \brief Save one byte
 *
 * This is a convenient function used to save one byte in the
 * Data buffer.
 *
 * This function implicitly calls Align().
 *
 * \param[in] c The byte to save
 *
 * \sa sswf::Data::PutShort()
 * \sa sswf::Data::PutLong()
 * \sa sswf::Data::PutDLong()
 * \sa sswf::Data::PutShortFloat()
 * \sa sswf::Data::PutLongFloat()
 * \sa sswf::Data::PutDoubleFloat()
 * \sa sswf::Data::PutString()
 */
void Data::PutByte(char c)
{
	Write(&c, 1);
}


/** \brief Save one short
 *
 * This is a convenient function used to save one short in the
 * Data buffer.
 *
 * The short least significant byte is saved first.
 *
 * This function implicitly calls Align().
 *
 * \param[in] s The short to save
 *
 * \sa sswf::Data::PutByte()
 * \sa sswf::Data::PutLong()
 * \sa sswf::Data::PutDLong()
 * \sa sswf::Data::PutShortFloat()
 * \sa sswf::Data::PutLongFloat()
 * \sa sswf::Data::PutDoubleFloat()
 * \sa sswf::Data::PutString()
 */
void Data::PutShort(short s)
{
	PutByte((char) s);
	PutByte((char) (s >> 8));
}


/** \brief Save one 32 bits long
 *
 * This is a convenient function used to save one long in the
 * Data buffer.
 *
 * The long least significant byte is saved first.
 *
 * This function implicitly calls Align().
 *
 * \param[in] l The long to save
 *
 * \sa sswf::Data::PutByte()
 * \sa sswf::Data::PutShort()
 * \sa sswf::Data::PutDLong()
 * \sa sswf::Data::PutShortFloat()
 * \sa sswf::Data::PutLongFloat()
 * \sa sswf::Data::PutDoubleFloat()
 * \sa sswf::Data::PutString()
 */
void Data::PutLong(long l)
{
	PutByte((char) l);
	PutByte((char) (l >> 8));
	PutByte((char) (l >> 16));
	PutByte((char) (l >> 24));
}


/** \brief Save one 64 bits long
 *
 * This is a convenient function used to save one 64 bits long
 * in the Data buffer.
 *
 * The long least significant byte is saved first.
 *
 * This function implicitly calls Align().
 *
 * \param[in] ll The 64 bits long to save
 *
 * \sa sswf::Data::PutByte()
 * \sa sswf::Data::PutShort()
 * \sa sswf::Data::PutLong()
 * \sa sswf::Data::PutShortFloat()
 * \sa sswf::Data::PutLongFloat()
 * \sa sswf::Data::PutDoubleFloat()
 * \sa sswf::Data::PutString()
 */
void Data::PutDLong(int64_t ll)
{
	PutByte((char) ll);
	PutByte((char) (ll >> 8));
	PutByte((char) (ll >> 16));
	PutByte((char) (ll >> 24));
	PutByte((char) (ll >> 32));
	PutByte((char) (ll >> 40));
	PutByte((char) (ll >> 48));
	PutByte((char) (ll >> 56));
}


/** \brief Save one 16 bits floating point in IEEE format.
 *
 * This is a convenient function used to save a floating point
 * on 16 bits. This is not a fixed point value, it really is
 * a floating point defined as:
 *
 * \li 1 bit of sign
 * \li 5 bits of exponent
 * \li 10 bits of mantissa
 *
 * \bug
 * This function assumes that the input floating point is
 * a valid number, not a NaN. Your SWF will work better that
 * way anyway.
 *
 * \param[in] f The floating point to convert and save
 *
 * \sa sswf::Data::PutByte()
 * \sa sswf::Data::PutShort()
 * \sa sswf::Data::PutLong()
 * \sa sswf::Data::PutDLong()
 * \sa sswf::Data::PutLongFloat()
 * \sa sswf::Data::PutDoubleFloat()
 * \sa sswf::Data::PutString()
 */
void Data::PutShortFloat(float f)
{
	float		*fn = &f;
	uint32_t	v = *reinterpret_cast<uint32_t *>(fn);
	uint16_t	s = 0;
	int		exponent;
	unsigned int	mantissa;

// copy the sign
	if(v & 0x80000000) {
		s |= 0x8000;
	}

// extract exponent and mantissa
	exponent = ((v & 0x7F800000) >> 23) - 127;
	mantissa = v & 0x007FFFFF;

	// TODO: check for denormalized numbers, NaN and other
	// goodies (i.e. exponent == -127 here means a special
	// number; also 128 is a special case we do not handle)

// check underflow and overflow of exponent
	if(exponent < -16) {
		// value is so small that it is like zero
		exponent = 0;
		mantissa = 0;
		s = 0;
	}
	else if(exponent > 15) {
		// value is way big, put the maximum supported by this format
		exponent = 15;
		mantissa = 0x007FFFFF;
	}

// save the exponent and mantissa in new floating point
	s |= (exponent & 0x1F) << 10;
	s |= mantissa >> 13;

// save the result
	PutShort(s);
}


/** \brief Save one 32 bits floating point in IEEE format.
 *
 * This is a convenient function used to save a 32 bits floating
 * point.
 *
 * \param[in] f The floating point to save
 *
 * \sa sswf::Data::PutByte()
 * \sa sswf::Data::PutShort()
 * \sa sswf::Data::PutLong()
 * \sa sswf::Data::PutDLong()
 * \sa sswf::Data::PutShortFloat()
 * \sa sswf::Data::PutDoubleFloat()
 * \sa sswf::Data::PutString()
 */
void Data::PutLongFloat(float f)
{
	float *fn = &f;
	long l = *reinterpret_cast<long *>(fn);
	PutLong(l);
}


/** \brief Save one 64 bits floating point in IEEE format.
 *
 * This is a convenient function used to save a 64 bits floating
 * point.
 *
 * \param[in] d The floating point to save
 *
 * \sa sswf::Data::PutByte()
 * \sa sswf::Data::PutShort()
 * \sa sswf::Data::PutLong()
 * \sa sswf::Data::PutDLong()
 * \sa sswf::Data::PutShortFloat()
 * \sa sswf::Data::PutLongFloat()
 * \sa sswf::Data::PutString()
 */
void Data::PutDoubleFloat(double d)
{
	double *dn = &d;
	uint64_t l = *reinterpret_cast<uint64_t *>(dn);
	PutDLong(l);
}


/** \brief Save one string
 *
 * This is a convenient function used to save a string in the
 * Data buffer.
 *
 * It is valid to call the function with a NULL pointer. It is
 * considered to be an empty string and just one zero byte is
 * written.
 *
 * Otherwise, strlen(string) + 1 bytes are saved (i.e. the string
 * characters and the nul terminator.)
 *
 * This function implicitly calls Align().
 *
 * \param[in] string The string to save
 *
 * \sa sswf::Data::PutByte()
 * \sa sswf::Data::PutShort()
 * \sa sswf::Data::PutLong()
 * \sa sswf::Data::PutDLong()
 * \sa sswf::Data::PutShortFloat()
 * \sa sswf::Data::PutLongFloat()
 * \sa sswf::Data::PutDoubleFloat()
 */
void Data::PutString(const char *string)
{
	if(string == 0) {
		// an empty string
		PutByte(0);
	}
	else {
		Write(string, strlen(string) + 1);
	}
}




/** \brief Append another Data buffer
 *
 * This function appends a Data buffer to this Data buffer.
 *
 * The source Data buffer is not modified.
 *
 * The destination is first aligned with a call to Align(). The
 * buffer is grown as required, then the source is copied at the
 * end of the destination.
 *
 * \param[in] data The source Data buffer
 */
void Data::Append(const Data& data)
{
	int		sz;

	Align();
	// data.Align() -- we can't do that on a constant
	// so we compute sz as the aligned f_pos and use
	// that instead
	sz = (data.f_pos + CHAR_BIT - 1) & -CHAR_BIT;
	AdjustSize(f_pos + sz);
	memcpy(f_data + f_pos / CHAR_BIT, data.f_data, sz / CHAR_BIT);
	f_pos += sz;

#if DEBUG
MemTest(f_data);
#endif
}


/** \brief Read the data buffer information
 *
 * This function can be used to get access to the internal buffer
 * pointer and size.
 *
 * In general, this function is used at the end to save the Data
 * buffer in a file.
 *
 * This function implicitly calls Align().
 *
 * \bug
 * Warning: this function returns direct access to the internal buffer
 * pointer and size. If any other function is used, it invalidates
 * these pointers. So use them, and forget about them right away!
 *
 * \param[out] ptr Receives the Data buffer pointer
 * \param[out] size Receives the size in bytes of the buffer pointer
 */
void Data::Read(void *& ptr, size_t& size)
{
	Align();
	ptr = f_data;
	size = f_pos / CHAR_BIT;
}






/* The following options fold the documentation; use 'zi' to turn on and off
 *
 * vim: foldexpr=getline(v\:lnum)!~'^/\\*\\*'&&getline(v\:lnum)!~'^\ \\*'?0\:1 foldcolumn=2 foldmethod=expr
 */
