/***************************************************************************
               ramfile.c  -  a virtual file in memory 
               --------------------------------------
    begin                : Fri Apr 26 2002
    copyright            : (C) 2002 by Tim-Philipp Müller
    email                : t.i.m@orange.net
 ***************************************************************************/


/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "global.h"
#include "ramfile.h"
#include <stdio.h>
#include <string.h>	/* for memcpy, strlen */
#include <errno.h>

#define RAMFILE_EXTRA_ALLOC_SIZE	1024

/******************************************************************************
 *
 *    ramfile_new
 *
 *    creates new ramfile with initial len 'len'
 *
 *    returns pointer if successful, otherwise NULL
 *
 ***/

ramfile *
ramfile_new (guint len)
{
	gsize     allocnow = RAMFILE_EXTRA_ALLOC_SIZE;
	ramfile  *rf;

	rf = g_new (ramfile, 1);

	if ( len > allocnow )
		allocnow = len;

	rf->data = g_new (guint8, allocnow);

	rf->allocated = allocnow;
	rf->size = 0;
	rf->pos = 0;

	return rf;
}


/******************************************************************************
 *
 *   ramfile_new_from_file
 *
 *    Reads a file from disk into a ramfile and returns it or NULL on error
 *
 ***/

ramfile *
ramfile_new_from_file (const gchar *filename)
{
	ramfile *rf;
	GError  *error    = NULL;
	gchar   *contents = NULL;
	gsize    len      = 0;

	g_return_val_if_fail ( filename != NULL, NULL);

	if (!g_file_get_contents(filename, &contents, &len, &error))
	{
		g_error_free(error);
		return NULL;
	}

	rf = ramfile_new(0);
	g_return_val_if_fail ( rf != NULL, NULL );

	if ( len <= 0 )
		return rf;

	g_return_val_if_fail ( contents != NULL, rf );

	ramfile_write(rf, contents, len);

	return rf;
}



/******************************************************************************
 *
 *   ramfile_free
 *
 *
 ***/

void
ramfile_free (ramfile *rf)
{
	g_return_if_fail (rf != NULL);
	g_return_if_fail (rf->data != NULL);

	memset ( rf->data, 0x00, rf->allocated );
	g_free (rf->data);

	memset ( rf, 0x00, sizeof(ramfile) );
	g_free (rf);
}


/******************************************************************************
 *
 *   ramfile_read8
 *
 *   reads an 8-bit integer from ramfile.
 *
 *   returns 0xff on error
 *
 ***/

guint8
ramfile_read8 (ramfile *rf)
{
	guint8 val;

	g_return_val_if_fail ( rf != NULL, 0xff );
	g_return_val_if_fail ( rf->data != NULL, 0xff );

	if ( rf->pos >= rf->size )
		return 0xff;

	val = rf->data[rf->pos];

	rf->pos++;

	return val;
}


/******************************************************************************
 *
 *   ramfile_read16
 *
 *   reads a 16-bit integer from ramfile.
 *
 *   returns 0xffff on error
 *
 ***/

guint16
ramfile_read16 (ramfile *rf)
{
	guint16 val;

	g_return_val_if_fail ( rf != NULL, 0xffff );
	g_return_val_if_fail ( rf->data != NULL, 0xffff );

	if ( (rf->pos + 2) > rf->size )
		return 0xffff;

//	memcpy (&val, (rf->data + rf->pos), 2);
	val = *((guint16*)(rf->data + rf->pos));

	rf->pos = rf->pos + 2;

	return GUINT16_FROM_LE(val);
}


/******************************************************************************
 *
 *   ramfile_read32
 *
 *   reads a 32-bit integer from ramfile.
 *
 *   returns 0xffffffff on error
 *
 ***/

guint32
ramfile_read32 (ramfile *rf)
{
	guint32 val;

	g_return_val_if_fail ( rf != NULL, 0xffffffff );
	g_return_val_if_fail ( rf->data != NULL, 0xffffffff );

	if ( rf->pos+4 > rf->size )
		return 0xffffffff;

//	memcpy (&val, rf->data + rf->pos, 4);
	val = *((guint32*)(rf->data + rf->pos));

	rf->pos = rf->pos + 4;

	return GUINT32_FROM_LE(val);
}


/******************************************************************************
 *
 *   ramfile_readfloat
 *
 *   returns float or -999.99 on error (XXX)
 *
 ******************************************************************************/

gfloat
ramfile_readfloat (ramfile *rf)
{
	union
	{
		guint32  ui32val;
		gfloat   fpointval;
	} fourbytes;

	g_assert (rf             != NULL);
	g_assert (rf->data       != NULL);
	g_assert (sizeof(gfloat) == 4   );

	if ( rf->pos + sizeof(gfloat) > rf->size )
		return G_MAXFLOAT;

	fourbytes.ui32val = GUINT32_FROM_LE(*((guint32*)(rf->data + rf->pos)));

	rf->pos = rf->pos + 4;

	return fourbytes.fpointval;

/*****************************
	XXX - Daniel's suggestion:
	(Tim: I'm not sure whether this is an improvement. Problem is: As soon as
	 we have sizeof(gfloat)!=4, we run into trouble. Does the standard float
	 representation format guarantee that the conversion in 'case 8' will be
	 valid in any byte order? etc. If we want to make it foolproof, we have to
	 read in the float, disassemble the 32-bit float into its pieces in the
	 standard way, and then re-assemble it as 64-bit float.  or not?)

	gfloat  val;

	g_return_val_if_fail ( rf != NULL, G_MAXFLOAT );
	g_return_val_if_fail ( rf->data != NULL, G_MAXFLOAT );

	if ( rf->pos+sizeof(gfloat) > rf->size )
		return G_MAXFLOAT;

	val = *((gfloat*)(rf->data + rf->pos));

	rf->pos = rf->pos + sizeof(gfloat);

	switch(sizeof(gfloat))
	{
		case 4:
			val = GUINT32_FROM_LE(val);
			break;
		case 5:
		case 6:
		case 7:
		case 8:
			val = GUINT64_FROM_LE(val);
			break;
		default:
			rf->pos -= sizeof(gfloat);	// Tim: what's the point of this? would cause stream de-sync //
	}

	return val;

*****************************/

}

/******************************************************************************
 *
 *   ramfile_writefloat
 *
 *
 ***/

void
ramfile_writefloat (ramfile *rf, gfloat val)
{
	union
	{
		gfloat  fval;
		guint32 ui32val;
	} fourbytes;

	guint32  n2;

	fourbytes.fval = val;
	n2 = GUINT32_TO_LE(fourbytes.ui32val);

	g_return_if_fail ( rf!=NULL );
	g_return_if_fail ( sizeof(gfloat) == 4 );

	ramfile_write (rf, (guint8*)&n2, 4);

/*****************************

	XXX - Daniel's suggestion (Tim: see my comments above)

	guint32 fl32;
	guint64 fl64;

	switch(sizeof(float))
	{
		case 4:
			fl32 = GUINT32_TO_LE( *((guint32*)&val) );
			ramfile_write (rf, (guint8*)&fl32, 4);
			break;
		case 5:
		case 6:
		case 7:
		case 8:
			fl64 = GUINT64_TO_LE( *((guint64*)&val) );
			ramfile_write (rf, (guint8*)&fl64, 8);
			break;
		default:
			abort();
	}
*****************************/

}


/******************************************************************************
 *
 *   ramfile_write8
 *
 *
 ***/

void
ramfile_write8 (ramfile *rf, guint8 val)
{
	ramfile_write (rf, (guint8*)&val, 1);
}


/******************************************************************************
 *
 *   ramfile_write16
 *
 *
 ***/

void
ramfile_write16 (ramfile *rf, guint16 val)
{
	val = GUINT16_TO_LE(val);
	ramfile_write (rf, (guint8*)&val, 2);
}


/******************************************************************************
 *
 *   ramfile_write32
 *
 *
 ***/

void
ramfile_write32 (ramfile *rf, guint32 val)
{
	val = GUINT32_TO_LE(val);
	ramfile_write (rf, (guint8*)&val, 4);
}



/******************************************************************************
 *
 *   ramfile_seek
 *
 *
 ***/

gboolean
ramfile_seek (ramfile *rf, gint inc, GuiRamfileSeekType seektype)
{
	gint newpos;

	g_return_val_if_fail ( rf != NULL, FALSE);
	g_return_val_if_fail ( rf->data != NULL, FALSE);

	switch (seektype)
	{
		case RAMFILE_SEEK_FROM_HERE:
			newpos = rf->pos + inc;
			break;

		case RAMFILE_SEEK_FROM_START:
			newpos = inc;
			break;

		case RAMFILE_SEEK_FROM_END:
			newpos = rf->size - inc;
			break;

		default:
			g_return_val_if_reached (FALSE);
	}

	if (newpos > rf->size)
		return FALSE;

	if (newpos < 0)
		return FALSE;

	rf->pos = newpos;

	return TRUE;
}


/******************************************************************************
 *
 *   ramfile_read
 *
 *   reads in 'len' bytes from ramfile 'rf' from the
 *   current position into buffer 'buf'. If 'buf' is
 *   NULL, a new buffer will be allocated and returned
 *   (this buffer is zero-terminated for the i4str functions).
 *
 *   returns buffer (caller-provided or newly allocated)
 *           or NULL in case of an error.
 *
 ***/

guint8 *
ramfile_read (ramfile *rf, guint8 *buf, gint len)
{
	g_return_val_if_fail ( rf != NULL, NULL);
	g_return_val_if_fail ( rf->data != NULL, NULL);

	if ( len <= 0 )
		return NULL;

	if ( (rf->pos + len) > rf->size )
		return NULL;

	g_return_val_if_fail ( len < 4096, NULL );

	if ( buf == NULL )
		buf = g_malloc0 ( len + 1);

	memcpy (buf, (rf->data + rf->pos), len);

	rf->pos += len;

	return buf;
}


/******************************************************************************
 *
 *   ramfile_read_hash
 *
 *   Returns a pointer to a 128-bit hash and increases
 *    the current position of the ramfile by 16 bytes
 *    to point to the first byte after the hash. The
 *    pointer returned is only valid temporarily and
 *    will become invalid if the ramfile is written
 *    to or freed.
 *
 *   Returns a pointer to a zero hash on error
 *
 ******************************************************************************/

const guint8 *
ramfile_get_hash (ramfile *rf)
{
	static const guint8  zerohash[16]; /* all 0 */
	const guint8        *rethash;

	g_return_val_if_fail ( rf != NULL, zerohash );
	g_return_val_if_fail ( rf->data != NULL, zerohash );

	if ( (rf->pos + 16) > rf->size )
		return zerohash ;

	rethash = rf->data + rf->pos;

	rf->pos += 16;

	return rethash;
}

/******************************************************************************
 *
 *   ramfile_peek
 *
 *   reads in 'len' bytes from ramfile from position 'pos'
 *   into buffer 'buf' without changing the current position.
 *
 *   if 'buf' is NULL, a buffer will be allocated
 *   (caller needs to free this then)
 *
 *   returns: buffer (either caller-provided or newly-allocated).
 *            or NULL on error.
 *
 ***/

guint8 *
ramfile_peek (ramfile *rf, guint8 *buf, gint pos, gint len)
{
	g_return_val_if_fail (rf != NULL, NULL);
	g_return_val_if_fail (rf->data != NULL, NULL);

	if ( len <= 0 )
		return NULL;

	if ( (pos + len) > rf->size)
		return NULL;

	if ( buf == NULL )
		buf = g_malloc0 (len+1);

	memcpy (buf, (rf->data + pos), len);

	return buf;
}


/******************************************************************************
 *
 *   ramfile_write
 *
 *   writes 'len' bytes from buffer 'buf' to
 *   ramfile 'rf' at the current position
 *
 ***/

void
ramfile_write (ramfile *rf, const guint8 *buf, gint len)
{
	g_return_if_fail ( rf != NULL );

	if ( len <= 0)
		return;

	g_return_if_fail ( buf != NULL );

	/* note: special case might be 2 bytes before end but 4 to write... */
	if ( (rf->pos + len) >= rf->allocated)
	{
		do
		{
			rf->allocated += RAMFILE_EXTRA_ALLOC_SIZE;
		} while ( (rf->pos + len) >= rf->allocated);

		rf->data = g_realloc (rf->data, rf->allocated);
	}

	/* only increase len if we write over/at the end */
	if ( (rf->pos + len) >= rf->size)
		rf->size = rf->pos + len;

	memcpy ( (rf->data + rf->pos), buf, len);

	rf->pos += len;
}

/******************************************************************************
 *
 *   ramfile_write_i4_string
 *
 *
 ***/

void
ramfile_write_i4_string (ramfile *rf, const gchar *string)
{
	g_return_if_fail ( rf != NULL );

	if (string)
	{
		guint16 len = strlen(string);
		ramfile_write16 (rf, len);
		ramfile_write   (rf, (guint8*) string, len);
	}
	else
	{
		ramfile_write_i4_string (rf, "");
	}
}


/******************************************************************************
 *
 *  ramfile_read_i4_string
 *
 *  Reads an i4string ( 2 bytes string length, x bytes characters )
 *    from ramfile, and returns it as a newly-allocated
 *    and zero-terminated string.
 *
 *  Returns string or NULL on error
 *
 *  -=-=-=- CALLER NEEDS TO FREE RETURNED STRING -=-=-=-
 *
 ***/

gchar *
ramfile_read_i4_string (ramfile *rf)
{
	gchar   *i4str;
	guint16  len;

	g_return_val_if_fail ( rf != NULL, NULL);

	len = ramfile_read16 (rf);

	if (len == 0)
		return g_strdup("");

	if (len == 0xffff)
		return NULL;

	/* note: there is a bug in overnet v44+v45 which sends search results packets
	 *       one byte short, which means that often the last character of a string
	 *       (usually format string) is 0xe3 (ie. the first byte of the next packet */

	if ( len == 0xe300 )
		return NULL;

	/* a string longer than 32768 chars? unlikely */
	g_return_val_if_fail (len < 32768, NULL);

	/* if char is NOT one byte */
	if ( sizeof(gchar) != sizeof(guint8) )
	{
		GString *s = g_string_sized_new (len);
		guint    i;

		g_return_val_if_fail ( s != NULL, NULL);

		for (i=0; i<len; i++)
		{
			s = g_string_append_c (s, (gchar) ramfile_read8(rf));
		}

		i4str = s->str;

		g_string_free (s, FALSE);	/* don't free character data, only GString structure */
	}
	else
	{
		i4str = g_new0 ( gchar, (len+1) );
		g_return_val_if_fail (i4str != NULL, NULL);

		if ( ramfile_read ( rf, (guint8*)i4str, len) == NULL)
		{
			G_FREE(i4str);
			return NULL;
		}
	}

	return i4str;
}


/******************************************************************************
 *
 *   ramfile_dump
 *
 *   prints out the whole ramfile as a hexdump 
 *   to the console for debugging purposes
 *
 ***/

void
ramfile_dump (const ramfile *rf, const gchar *infostring)
{
	gint i;

	g_return_if_fail ( rf != NULL );
	g_return_if_fail ( infostring != NULL );

	fprintf (stdout, "_____ ramfile dump - %s _____", infostring);

	for (i=0; i < rf->size; i++)
	{
		if ( (i % 16) == 0)
			fprintf (stdout, "\n%04x: ", i);

		fprintf (stdout, "%02x ", rf->data[i] );
	}

	fprintf (stdout, "\n________________________________________\n");

}


/******************************************************************************
 *
 *    ramfile_get_size
 *
 *
 ***/

gsize
ramfile_get_size (ramfile *rf)
{
	g_return_val_if_fail ( rf != NULL, -1 );

	return rf->size;
}

/******************************************************************************
 *
 *   ramfile_get_buffer
 *
 *
 ***/

guint8 *
ramfile_get_buffer(ramfile *rf)
{
	g_return_val_if_fail ( rf != NULL, NULL );
	return rf->data;
}

/******************************************************************************
 *
 *   ramfile_get_pos
 *
 *
 ***/

gint
ramfile_get_pos (ramfile *rf)
{
	g_return_val_if_fail ( rf != NULL, -1 );

	return rf->pos;
}


/******************************************************************************
 *
 *   ramfile_set_pos
 *
 *
 ***/

void
ramfile_set_pos (ramfile *rf, guint newpos)
{
	g_return_if_fail ( rf != NULL );
	g_return_if_fail ( newpos < rf->allocated );
	g_return_if_fail ( newpos < rf->size );

	rf->pos = newpos;
}


