/* libsswf_memory.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 memory classes
 *
 * 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;



/////////////////////////////////////////// ItemBase
// Added for documentation purposes


/** \class sswf::ItemBase
 *
 * \brief This class is used to derive from.
 *
 * Most of the objects derive from this one. This is used in the
 * memory manager to handle all the object pointers without having
 * to cast the pointers with a reinterpret_cast<> which breaks
 * on objects with multiple inheritance.
 */

/** \fn sswf::ItemBase::ItemBase(void)
 *
 * \brief Initialize an ItemBase object
 *
 * This function does nothing. It is just a place holder which is most
 * certainly not required today. But some C++ compilers had problems
 * without it.
 */


/** \fn virtual sswf::ItemBase::~ItemBase()
 *
 * \brief Destroys an ItemBase object
 *
 * This function does nothing. It is necessary because the classes derived
 * will often use virutal functions.
 */


/////////////////////////////////////////// MemBuffer

/** \class sswf::MemBuffer
 *
 * \brief A type of smart pointer used to manage memory buffers.
 *
 * The MemBuffer class can be viewed as a buffer of memory which needs to be
 * automatically managed.
 *
 * The idea is to derive new classes from MemBuffer. These classes can then
 * be managed by an sswf::MemoryManager. This is necessary for items put in
 * vectors, the vectors themselves and a few othe objects which would otherwise
 * not be managed.
 *
 * A good examples are the actions. All the actions put in a vector are managed
 * by that vector. In effect, when you destroy that vector, it automatically
 * deletes all the actions.
 *
 * \sa sswf::MemoryManager
 * \sa sswf::Buffer
 */

/** \brief Initializes a memory buffer
 *
 * The constructor initializes the memory buffer with a null (i.e. no buffer).
 */
MemBuffer::MemBuffer(void)
{
	f_buffer = 0;
}


/** \brief Cleans up a memory buffer
 *
 * The destructor ensures that any buffer still allocated gets deleted.
 */
MemBuffer::~MemBuffer()
{
	Buffer		*b;

	if(f_buffer != 0) {
		b = f_buffer;
		f_buffer = 0;
		delete b;
	}
}


/** \brief Attaches the specified buffer to this memory buffer.
 *
 * This function is used when you attach a MemBuffer to a Buffer.
 * The Buffer saves itself in the MemBuffer. In effect the item
 * derived from this MemBuffer class becomes part of a MemoryManager.
 *
 * It is an error to get attached to multiple buffers without first
 * clearing the MemBuffer once. This should not be necessary since
 * once an item is in a MemoryManager it does not need to be moved.
 *
 * \param buffer The buffer to attach to this MemBuffer
 */
void MemBuffer::AttachBuffer(Buffer *buffer)
{
	assert(f_buffer == 0 || buffer == 0, "object at 0x%p is being attached to multiple buffers (0x%p [%s] and 0x%p [%s])\n",
				this,
				buffer, buffer == 0 ? "<no info>" : buffer->Info(),
				f_buffer, f_buffer == 0 ? "<no info>" : f_buffer->Info());

	f_buffer = buffer;
}


/** \brief Retrieve the Buffer of this MemBuffer
 *
 * This function is used to retrieve the Buffer which was previously attached with
 * this MemBuffer object.
 *
 * \return A pointer to  the Buffer or NULL when no buffer was attached
 */
Buffer *MemBuffer::GetBuffer(void) const
{
	return const_cast<Buffer *>(f_buffer);
}


/////////////////////////////////////////// Buffer

/** \class sswf::Buffer
 *
 * \brief Linked list of memory buffers for easy memory management.
 *
 * The Buffer class is the placeholder of the memory pointers used inside the
 * MemoryManager.
 *
 * It allocates and frees memory buffers and link all the buffers allocated
 * in a memory manager.
 *
 * If you need to handle a raw memory pointer in many different ways (including
 * reallocating it) you may want to derive your class from the MemBuffer class
 * and use that class to handle your buffer for you.
 *
 * \sa sswf::MemoryManager
 * \sa sswf::MemBuffer
 */

/** \brief Initialize a memory buffer
 *
 * This constructor saves the head pointer, the size and information about the
 * buffer and then allocates a buffer of memory of the specified size.
 *
 * \param head A pointer to the f_head of the attached MemoryManager
 * \param size The size of the buffer of memory to allocate
 * \param info Information about this buffer (a static const string)
 */
Buffer::Buffer(Buffer **head, size_t size, const char *info)
{
	mem_buffer_t		*data;

	assert(head != 0, "any buffer allocation requires a head buffer pointer");

	f_head = head;
	f_next = *head;
	if(f_next != 0) {
		f_next->f_previous = this;
	}
	f_previous = 0;
	f_info = info;
	f_size = size;

#if DEBUG
	// we adjust the size because some systems may otherwise allocate less bytes
	// and yet our own debug stuff will use this exact size...
	size = SSWF_ALIGN(size, sizeof(long));
#endif

	*head = this;		// link at the head (XXX wouldn't work in multi-thread env. XXX)
	data = (mem_buffer_t *) malloc(size + SSWF_SPACE + SSWF_SAFE * 2);
	if(data == 0) {
		f_data = 0;		// just in case
		fprintf(stderr, "ERROR: out of memory trying to allocate %ld bytes.\n", (long) size);
		assert(0, "check the stack now!");
		exit(1);
	}

	data->f_magic = DMAGIC;
	data->f_buffer = this;
#if DEBUG
	int idx = SSWF_SAFE / sizeof(long);
	long *l = (long *) ((char *) data + SSWF_SPACE);
	long *m = (long *) ((char *) data + SSWF_SPACE + SSWF_SAFE + SSWF_ALIGN(size, sizeof(long)));
	do {
		*l++ = *m++ = SSWF_TEST;
		idx--;
	} while(idx > 0);
#endif
	f_data = (char *) data + SSWF_SPACE + SSWF_SAFE;

#if 0
printf("Allocated buffer at %p (Buffer Object: %p - %s)\n", f_data, this, info);
#endif
}


/** \brief Initialize this buffer with a preallocated buffer as defined in MemBuffer
 *
 * This constructor links a memory buffer to a buffer and a memory manager.
 *
 * The size specifies the size of the buffer defined in the MemBuffer object.
 *
 * The info is information about this buffer.
 *
 * \param head A pointer to the f_head variable of the memory manager to be attached to
 * \param ptr A pointer to the memory buffer to reference in this buffer
 * \param size The size of this buffer (should be sizeof(MemBuffer *))
 * \param info Information about this buffer (static const string)
 */
Buffer::Buffer(Buffer **head, MemBuffer *ptr, size_t size, const char *info)
{
	mem_buffer_t		*data;

	assert(head != 0, "any buffer allocation requires a head buffer pointer");

	f_head = head;
	f_next = *head;
	if(f_next != 0) {
		f_next->f_previous = this;
	}
	f_previous = 0;
	f_info = info;
	f_size = size;
	*head = this;		// link at the head (XXX wouldn't work in multi-thread env. XXX)
		// we have to allocate a SAFE area so the destructor works also
	data = (mem_buffer_t *) malloc(sizeof(MemBuffer *) + SSWF_SPACE + SSWF_SAFE);
	if(data == 0) {
		f_data = 0;		// just in case
		fprintf(stderr, "FATAL ERROR: out of memory trying to allocate a memory buffer (MemBuffer) object.\n");
		exit(1);
	}

	data->f_magic = OMAGIC;
	data->f_buffer = this;
	// here we don't initialize the SSWF_SAFE area because it's not really necessary
	f_data = (char *) data + SSWF_SPACE + SSWF_SAFE;
	* (MemBuffer **) f_data = ptr;
	ptr->AttachBuffer(this);

#if 0
printf("Attached pointer %p (Buffer Object: %p - %s)\n", ptr, this, info);
#endif
}


/** \brief Unlink this buffer and free the attached memory
 *
 * The destructor ensures that the buffer is unlinked also managing
 * the f_head of the MemoryManager it is attached to.
 *
 * Then it frees the memory allocated for the buffer of memory.
 */
Buffer::~Buffer()
{
	mem_buffer_t	*data;

#if 0
fflush(stdout);
fprintf(stderr, "Free %p - n/p = %p/%p, n.n/p = %p/%p, p.n/p = %p/%p [%s]\n",
		this,
		f_next, f_previous,
		f_next == 0 ? 0 : f_next->f_next, f_next == 0 ? 0 : f_next->f_previous,
		f_previous == 0 ? 0 : f_previous->f_next, f_previous == 0 ? 0 : f_previous->f_previous,
		f_info);
#endif

	if(f_previous == 0) {
		// we are the head, unlinked ourselves & save the new head
		*f_head = f_next;
	}
	else {
		f_previous->f_next = f_next;
	}
	if(f_next != 0) {
		f_next->f_previous = f_previous;
	}

#if DEBUG
	Test();
#endif

	data = (mem_buffer_t *) ((char *) f_data - SSWF_SPACE - SSWF_SAFE);
	if(data->f_magic == OMAGIC) {
		if(((MemBuffer **) f_data)[0]->GetBuffer() != 0) {
#if 0
fflush(stdout);
fprintf(stderr, "Deleting pointer at %p (%s)\n", * (void **) f_data, f_info);
#endif
			((MemBuffer **) f_data)[0]->AttachBuffer(0);		// need to be detached 1st otherwise it will loop
			delete * (MemBuffer **) f_data;
		}
	}
	else if(data->f_magic != DMAGIC) {
		assert(0, "invalid data magic in Buffer::~Buffer() for %p (0x%08lX)", data, data->f_magic);
	}
#if 0
else {
fflush(stdout);
fprintf(stderr, " Freeing buffer  at %p (%s)\n", f_data, f_info);
}
#endif

	free(data);
}




#if DEBUG
// private function
void Buffer::Test(void)
{
	bool			bad_start, bad_end;
	int			idx, i;
	long			*l, *m;
	struct mem_buffer_t	*data;

	data = (mem_buffer_t *) ((char *) f_data - SSWF_SPACE - SSWF_SAFE);

//fprintf(stderr, "0x%08lX tested!\n", (long) data);

	if(data->f_magic == DMAGIC) {
		bad_start = false;
		bad_end = false;
		idx = SSWF_SAFE / sizeof(long);
		l = (long *) ((char *) data + SSWF_SPACE);
		m = (long *) ((char *) data + SSWF_SPACE + SSWF_SAFE + SSWF_ALIGN(f_size, sizeof(long)));
		do {
			if(*l++ != SSWF_TEST) {
				bad_start = true;
			}
			if(*m++ != SSWF_TEST) {
				bad_end = true;
			}
			idx--;
		} while(idx > 0);	/* we never expect a bad start/end thus testing the flags here is bad - thanks */
		if(bad_start) {
			idx = SSWF_SAFE / sizeof(long);
			i = 0;
			l = (long *) ((char *) data + SSWF_SPACE);
			do {
				if((i & 7) == 0) {
					fprintf(stderr, "%08X- ", i);
				}
				fprintf(stderr, " %08lX", *l++);
				i++;
				if((i & 7) == 0) {
					fprintf(stderr, "\n");
				}
				idx--;
			} while(idx > 0);
		}
		if(bad_end) {
			idx = SSWF_SAFE / sizeof(long);
			i = SSWF_SAFE;
			m = (long *) ((char *) data + SSWF_SPACE + SSWF_SAFE + SSWF_ALIGN(f_size, sizeof(long)));
			do {
				if((i & 7) == 0) {
					fprintf(stderr, "%08X- ", i);
				}
				fprintf(stderr, " %08lX", *m++);
				i++;
				if((i & 7) == 0) {
					fprintf(stderr, "\n");
				}
				idx--;
			} while(idx > 0);
		}
		if(bad_start || bad_end) {
			assert(0, "invalid memory buffer");
		}
	}
	else if(data->f_magic != OMAGIC) {
		assert(0, "invalid data magic in a data buffer");
	}
}
#endif


/** \brief Reallocate the buffer of memory
 *
 * This function is used to change the size of the currently allocated buffer of memory.
 *
 * The size should be positive. It can be larger or smaller than the current size.
 *
 * A buffer allocated via a MemBuffer cannot be reallocated here.
 *
 * \param size The new buffer size
 *
 * \return The new data pointer
 */
void *Buffer::Realloc(size_t size)
{
	mem_buffer_t	*data;

#if DEBUG
	Test();
#endif

	data = (mem_buffer_t *) ((char *) f_data - SSWF_SPACE - SSWF_SAFE);
	assert(data->f_magic == DMAGIC, "can't reallocate a buffer which was not allocated directly (MemAlloc or MemRealloc)");
	data = (mem_buffer_t *) realloc(data, size + SSWF_SPACE + SSWF_SAFE * 2);
	if(data == 0) {
		fprintf(stderr, "ERROR: out of memory reallocating %ld bytes.\n", (long) size);
		exit(1);
	}
#if DEBUG
	int idx = SSWF_SAFE / sizeof(long);
	long *m = (long *) ((char *) data + SSWF_SPACE + SSWF_SAFE + SSWF_ALIGN(size, sizeof(long)));
	do {
		*m++ = SSWF_TEST;
		idx--;
	} while(idx > 0);
#endif

	f_data = (char *) data + SSWF_SPACE + SSWF_SAFE;
	f_size = size;

	return f_data;
}



/** \brief Search for a Buffer given a data pointer
 *
 * This function searches for the Buffer object which references the specified
 * data pointer.
 *
 * The truth is that there is no search. Each data block allocated via a Buffer
 * includes a pointer back to the corresponding buffer object. This is how it
 * works and thus trying to find a Buffer from an invalid pointer will result
 * in an assert() if not a crash.
 *
 * \param ptr The pointer of which you want the buffer object
 *
 * \return The corresponding buffer pointer
 */
Buffer *Buffer::FindBuffer(void *ptr)
{
	mem_buffer_t	*data;

	data = (mem_buffer_t *) ((char *) ptr - SSWF_SPACE - SSWF_SAFE);

#ifndef __MINGW32__
	assert(((long) data & (sizeof(double) - 1)) == 0, "invalid buffer alignment at %p", ptr);
#endif
	assert(data->f_magic == DMAGIC || data->f_magic == OMAGIC, "invalid buffer magic word at %p (%08lX)", ptr, data->f_magic);

#if DEBUG
	data->f_buffer->Test();
#endif

	return data->f_buffer;
}


/** \fn sswf::Buffer::Data(void) const
 *
 * \brief The Data() function returns the current data pointer allocated in this buffer.
 *
 * \return A pointer to the data memory buffer held by this Buffer object
 */

/** \fn sswf::Buffer::Next(void) const
 *
 * \brief Returns a pointer to the next Buffer in a list of Buffers.
 *
 * The last buffer has no next buffer and NULL will be returned.
 *
 * \return A Buffer pointer
 */

/** \fn sswf::Buffer::Previous(void) const
 *
 * \brief Returns a pointer to the previous Buffer in a list of Buffers.
 *
 * The first buffer has no previous buffer and NULL will be returned.
 *
 * \return A Buffer pointer
 */

/** \fn sswf::Buffer::Size(void) const
 *
 * \brief Returns the size of the data buffer in bytes. This is the same size used to
 * allocate the buffer.
 *
 * \note
 * The size can be zero.
 *
 * \return The size of the data memory buffer
 */

/** \fn sswf::Buffer::Info(void) const
 *
 * \brief Return the pointer of the static const string passed down to one of the
 * functions used for this effect.
 */


#if 0
// this is actually completely wrong, sorry about that!
bool Buffer::IsBuffer(void *ptr)
{
	mem_buffer_t	*data;

	data = (mem_buffer_t *) ((char *) ptr - SSWF_SPACE - SSWF_SAFE);

	if(((long) data & (sizeof(double) - 1)) != 0) {
		return false;
	}

	return data->f_magic == DMAGIC || data->f_magic == OMAGIC;
}
#endif



/////////////////////////////////////////// Memory Manager

/** \class sswf::MemoryManager
 *
 * \brief A garbage collector manager.
 *
 * The MemoryManager class is used as a garbage collection for all
 * the object derived from it. These objects will allocate memory
 * buffers and attach them to the collection of buffers in their
 * memory manager.
 *
 * Then when the object is deleted, all the buffers are automatically
 * deleted.
 *
 * The sswf::MemoryManager uses the Buffer and sswf::MemBuffer classes
 * to manage the buffers.
 *
 * \sa sswf::Buffer
 * \sa sswf::MemBuffer
 */

/** \brief Initialize a memory manager object
 *
 * This constructor initialize the memory manager by clearing
 * the head of the memory buffers.
 */
MemoryManager::MemoryManager(void)
{
	f_head = 0;
}


/** \brief Delete all the memory buffers
 *
 * The destructor goes through the list of memory buffers allocated
 * by the memory manager functions and delete them all.
 */
MemoryManager::~MemoryManager()
{
	// delete all of the buffers allocated for this object
	while(f_head != 0) {
		delete f_head;
	}
}


/** \brief Attach a memory buffer to a manager
 *
 * This function takes the specified memory buffer under control.
 *
 * This means it will delete the buffer whenever the object is deleted
 * or when MemFree() is called.
 *
 * \param ptr The pointer to take control over
 * \param size The size of the buffer pointed by ptr
 * \param info Information about the buffer (a static const string)
 */
void MemoryManager::MemAttach(MemBuffer *ptr, size_t size, const char *info)
{
	if(ptr == 0) {		// the new ... failed! we catch it here 8-)
		fprintf(stderr, "FATAL ERROR: out of memory allocating an object of %ld bytes.\n", (unsigned long) size);
		exit(1);
	}
	(void) new Buffer(&f_head, ptr, size, info);
}


/** \brief Allocate a new buffer of memory
 *
 * The memory manager allocates a new buffer and returns the pointer
 * to the data of the buffer.
 *
 * Note that the allocation of the sswf::Buffer object is hidden from
 * view.
 *
 * \param size Size of the memory block to allocate
 * \param info Information about the buffer (a static const string)
 *
 * \return A pointer to the newly allocated memory block
 */
void *MemoryManager::MemAlloc(size_t size, const char *info)
{
	Buffer		*b;

	b = new Buffer(&f_head, size, info);

	return b->Data();
}


/** \brief Allocate or re-allocate a memory buffer pointer
 *
 * This function is used to enlarge or reduce the size of a previously
 * allocated buffer or to allocate a new buffer.
 *
 * When ptr is set to NULL, it behaves like sswf::MemoryManager::MemAlloc().
 *
 * When ptr is not NULL, then the function searches the buffer. If the
 * buffer was not previously allocated by MemAlloc() or some similar
 * function, then an assert(3) will be emited in debug mode. In release,
 * the buffer will be assumed correct and you will corrupt your memory.
 *
 * \param ptr The pointer to re-allocate
 * \param size The size of the new buffer
 * \param info Information about the buffer (a static const string)
 *
 * \return The pointer to the reallocated memory block
 */
void *MemoryManager::MemRealloc(void *ptr, size_t size, const char *info)
{
	Buffer		*b;

	if(ptr == 0) {
		if(size == 0) {
			return 0;
		}
		return MemAlloc(size, info);
	}

	b = Buffer::FindBuffer(ptr);

	if(size == 0) {
		delete b;
		return 0;
	}

	return b->Realloc(size);
}


/** \brief Return the current size of a buffer of memory
 *
 * This function searches for the specified buffer and returns its
 * size.
 *
 * \param ptr The pointer of which the size is to be returned
 *
 * \return The size of the buffer pointed by that pointer
 */
unsigned long MemoryManager::Size(void *ptr)
{
	Buffer		*b;

	if(ptr == 0) {
		return 0;
	}

	b = Buffer::FindBuffer(ptr);

	return b->Size();
}


#if DEBUG
/** \brief Test the pointer for validity (Debug only)
 *
 * This function can be used to check for the validity of a pointer
 * previously allocated with MemAlloc() or a similar function.
 *
 * If the pointer is valid, the function returns normally.
 *
 * When the pointer is not valid, an assert(3) occurs and the
 * program stops.
 *
 * \note
 * This function is only available in DEBUG mode.
 *
 * \param ptr The pointer of memory to be tested
 */
void MemoryManager::MemTest(void *ptr)
{
	if(ptr != 0) {
		Buffer::FindBuffer(ptr);
	}
}
#endif


/** \brief Free a memory buffer
 *
 * This function release the memory associated with the specified pointer.
 *
 * \param ptr The pointer to the memory to release
 */
void MemoryManager::MemFree(void *ptr)
{
	if(ptr != 0) {
		delete Buffer::FindBuffer(ptr);
	}
}


/** \brief Free a memory buffer and clean the holding pointer
 *
 * This function is a convenience function which (1) calls MemFree()
 * on the specified pointer and then clears the pointer.
 *
 * This is particularly useful to not have to do the cleaning of
 * the pointer yourself.
 *
 * \code
 *	char *buf;
 *
 *	buf = MemAlloc(123);
 *	...
 *	if(some flag) {
 *		MemClean(&buf);
 *
 *		// is equivalent to:
 *		//  MemFree(buf);
 *		//  buf = 0;
 *	}
 *	...
 *	if(buf != 0) {
 *		...
 *	}
 * \endcode
 *
 * \note
 * The compilers have a problem with a 'void **' type here. Thus
 * I use 'void *' which can look a bit confusing. Sorry about that!
 *
 * \param ptr The pointer to the pointer to free
 */
void MemoryManager::MemClean(void *ptr)
{
	assert(ptr != 0, "MemClean() can't accept a NULL pointer");

	if(* (void **) ptr != 0) {
		delete Buffer::FindBuffer(* (void **) ptr);
		* (void **) ptr = 0;
	}
}


/** \brief Duplicate a string using a managed buffer
 *
 * This function is used to duplicate a memory which buffer will
 * be held in the memory manager.
 *
 * \param str The string to duplicate
 *
 * \return A pointer to the duplicate of the input string
 */
char *MemoryManager::StrDup(const char *str)
{
	int		l;
	char		*r;

	if(str == 0) {
		l = 0;
	}
	else {
		l = strlen(str);
	}

	r = (char *) MemAlloc(l + 1, "StrDup() -- duplication of a string");

	if(str == 0) {
		r[0] = '\0';
	}
	else {
		strcpy(r, str);
	}

	return r;
}


/** \brief Concatenate two strings using a managed buffer
 *
 * This function concatenates two strings into one. The new
 * string buffer is allocated using the memory manager.
 *
 * \param s1 The string to appear on the left
 * \param s2 The string to appear on the right
 *
 * \return The result is a pointer to the newly allocated string
 */
char *MemoryManager::StrCat(const char *s1, const char *s2)
{
	int		l1, l2;
	char		*r;

	if(s1 == 0) {
		return StrDup(s2);
	}
	if(s2 == 0) {
		return StrDup(s1);
	}

	l1 = strlen(s1);
	l2 = strlen(s2);

	r = (char *) MemAlloc(l1 + l2 + 1, "StrCat() -- concatenate two strings");

	memcpy(r, s1, l1);
	memcpy(r + l1, s2, l2);
	r[l1 + l2] = '\0';

	return r;
}






/* 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
 */
