/***************************************************************************
                          tag.c  -  description
                             -------------------
    begin                : Sat May 11 2002
    copyright            : (C) 2002 by Tim-Philipp Müller
    email                : t.i.m at 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 "tag.h"
#include <string.h>	// for memset, strlen

#undef TAG_DEBUG

/* functions */

static void               tag_free (NGS_tag *tag);

static void               tag_free_content (NGS_tag *tag);


#ifdef TAG_DEBUG

static const gchar       *tag_get_realname_from_tagname (const gchar *tagname);

static void               tag_dump (NGS_tag *tag);

static const gchar       *tag_get_name (NGS_tag *tag);

#endif






/******************************************************************************
 *
 *   tag_new
 *
 *   create a new tag with name and type
 *
 *   returns pointer to new tag or NULL on error
 *
 ***/

NGS_tag *
tag_new (guint8 type, const gchar *name)
{
	NGS_tag *newtag; 

	g_return_val_if_fail ( type > TAGTYPE_UNDEFINED && type < TAGTYPE_LAST_UNDEFINED, NULL );

	newtag = g_new0 (NGS_tag,1);
	newtag->type = type;
	newtag->name = g_strdup(name);
	newtag->data = NULL;

	return newtag;
}


/******************************************************************************
 *
 *   tag_new_hash
 *
 *   create a new hash tag with name
 *
 *   returns pointer to new tag or NULL on error
 *
 ***/
/*
NGS_tag *
tag_new_hash (const gchar *name, const guint8 *hash)
{
	NGS_tag *nt;

	g_return_val_if_fail (hash != NULL, NULL);
	g_return_val_if_fail (name != NULL, NULL);

	nt = tag_new(TAGTYPE_HASH, name);
	if (nt)
	{
		nt->data = g_memdup (hash,16);
	}
	return nt;
}
*/

/******************************************************************************
 *
 *  tag_new_integer
 *
 *  create a new integer tag with name
 *
 *  returns pointer to new tag or NULL on error
 *
 ***/

NGS_tag *
tag_new_integer (const gchar *name, gint32 val)
{
	NGS_tag *nt = tag_new(TAGTYPE_INT, name);

	if (nt)
	{
		nt->data = GUINT_TO_POINTER(val);
	}
	return nt;
}


/******************************************************************************
 *
 *   tag_new_string
 *
 *   create a new string tag with name
 *
 *   returns pointer to new tag or NULL on error
 *
 ***/

NGS_tag *
tag_new_string (const gchar *name, const gchar *stringval)
{
	NGS_tag *nt;

	g_return_val_if_fail ( name      != NULL, NULL );
	g_return_val_if_fail ( stringval != NULL, NULL );

	nt = tag_new(TAGTYPE_STRING, name);

	if (nt)
	{
		nt->data = g_strdup(stringval);
	}

	return nt;
}


/******************************************************************************
 *
 *  tag_new_float
 *
 *  create a new float tag with name
 *
 *  returns pointer to new tag or NULL on error
 *
 ***/

NGS_tag *
tag_new_float (const gchar *name, gfloat fval)
{
	NGS_tag *nt;

	g_return_val_if_fail ( name != NULL, NULL );

	nt = tag_new(TAGTYPE_FLOAT, name);

	if (nt)
	{
		nt->data = g_new0(gfloat,1);
		*((gfloat*)nt->data) = fval;
	}

	return nt;
}


/******************************************************************************
 *
 *   tag_new_from_ramfile
 *
 *   creates a tag from ramfile stream
 *
 *   returns pointer to new tag or NULL on error
 *
 ***/

NGS_tag *
tag_new_from_ramfile (ramfile *rf)
{
	NGS_tag	*nt;

	g_return_val_if_fail ( rf != NULL, NULL );

	nt = g_new0 (NGS_tag,1);

	nt->type = ramfile_read8(rf);
	nt->name = ramfile_read_i4_string(rf);

	switch (nt->type)
	{
		case TAGTYPE_HASH:
		{
			nt->data = g_new0 (guint8,16);
			ramfile_read (rf,nt->data,16);
		}
		break;

		case TAGTYPE_STRING:
		{
			nt->data = (gpointer) ramfile_read_i4_string(rf);
		}
		break;

		case TAGTYPE_INT:
		{
			nt->data = GUINT_TO_POINTER(ramfile_read32(rf));
		}
		break;

		case TAGTYPE_FLOAT:
		{
			nt->data = g_new(gfloat,1);

			ramfile_read (rf, (guint8*)nt->data, 4);

			g_return_val_if_fail ( sizeof(gfloat) == 4, nt);
		}
		break;

		case TAGTYPE_BOOL:
		case TAGTYPE_BOOLARRAY:
		case TAGTYPE_BLOB:
		default:
		{
			g_printerr (_("tag_new_from_ramfile: unknown or unimplemented tag type %u!\n"), nt->type);
			g_free(nt);
			return NULL;
		}
		break;
	}

	return nt;
}


/******************************************************************************
 *
 *   tag_free
 *
 *   frees tag content and structure
 *
 ***/

static void
tag_free (NGS_tag *tag)
{
	tag_free_content (tag);

	memset (tag, 0x00, sizeof(NGS_tag));

	G_FREE(tag);
}


/******************************************************************************
 *
 *   tag_free_content
 *
 *   frees tag's content only, but not the structure (necessary if tag not dyn.allocated)
 *
 ***/

static void
tag_free_content (NGS_tag *tag)
{
	g_return_if_fail ( tag != NULL );

	G_FREE(tag->name);

	switch (tag->type)
	{
		case TAGTYPE_HASH:
		case TAGTYPE_STRING:
		case TAGTYPE_FLOAT:
		{
			G_FREE(tag->data);
		}
		break;

		/* INT: nothing to do */
		case TAGTYPE_INT:
		break;

		case TAGTYPE_BOOL:
		case TAGTYPE_BOOLARRAY:
		case TAGTYPE_BLOB:
		default:
		{
			g_printerr (_("tag_free_content: unknown or unimplemented tag type!\n"));
		}
	}
}



/******************************************************************************
 *
 *   tag_dump 
 *
 ***/

#ifdef TAG_DEBUG

static void
tag_dump (NGS_tag *tag)
{
	g_return_if_fail (tag!=NULL);
	g_return_if_fail (tag->name!=NULL);
	g_print ("\t%s\t = ", tag_get_name(tag));	
	switch (tag->type)
	{
		case TAGTYPE_HASH:
		{
			g_print (_("hash dump not implemented\n"));
		}
		break;

		case TAGTYPE_STRING:
		{
			g_print ("'%s'\n", (gchar*)tag->data);
		}
		break;

		case TAGTYPE_INT:
		{
			g_print ("%u\n", GPOINTER_TO_UINT(tag->data));
		}
		break;

		case TAGTYPE_FLOAT:
		{
			g_print ("%f\n", *((gfloat*)tag->data));
		}
		break;

		case TAGTYPE_BOOL:
		case TAGTYPE_BOOLARRAY:
		case TAGTYPE_BLOB:
		default:
		{
			g_print (_("unknown or unimplemented tag type!\n"));
		}
	}
}

#endif

/******************************************************************************
 *
 *  tag_write_to_ramfile 
 *
 ***/

void
tag_write_to_ramfile (NGS_tag *tag, ramfile *rf)
{
	g_return_if_fail ( tag != NULL);
	g_return_if_fail ( rf  != NULL);

	ramfile_write8 (rf, tag->type);
	ramfile_write_i4_string (rf, (tag->name) ? tag->name : "");

	switch (tag->type)
	{
		case TAGTYPE_HASH:
		{
			if (!tag->data)
			{
				guint8 *emptybuf = g_new0(guint8, 16);
				ramfile_write (rf, emptybuf, 16);
				g_free(emptybuf);
			}
			else
			{
				ramfile_write (rf, (guint8*)tag->data, 16);
			}
		}
		break;

		case TAGTYPE_STRING:
		{
			ramfile_write_i4_string (rf, (tag->data) ? (gchar*) tag->data : "");
		}
		break;

		case TAGTYPE_INT:
		{
			ramfile_write32 (rf, GPOINTER_TO_UINT(tag->data));
		}
		break;

		case TAGTYPE_FLOAT:
		{
			g_print (_("%s: write float tag - IMPLEMENT ME!\n"), __FUNCTION__);
			ramfile_write32 (rf, 0);	// just write something for now
		}
		break;

		case TAGTYPE_BOOL:
		case TAGTYPE_BOOLARRAY:
		case TAGTYPE_BLOB:
		default:
		{
			g_printerr (_("\t* val = unknown or unimplemented tag type!\n"));
		}
	}
}


#ifdef TAG_DEBUG

/* this is only for debugging purposes really and doesn't need translating: */

static const gchar *TAGNAMES_SHORT_CLEARTEXT[] =
{
"undefined(0)",
"file name",
"file size",
"file type",
"file format",
"not implemented yet (5)",
"not implemented yet (6)",
"not implemented yet (7)",
"amount transfered so far",
"gap start",
"gap end",
"server description",
"server ping",
"server connection history",
"server preference",
"server port",
"server IP",
"user's client version (=>hex)",
"temp file name",
"priority",
"state",
"availability",
"qtime (last time client asked to be on Q)",
"parts of file shared",
"not implemented yet(24)"
} ;

#endif

/******************************************************************************
 *
 *   tag_get_realname_from_tagname
 *
 *   expands abbreviated tagname to real name if necessary
 *
 *   tag_get_realname_from_tagname
 *
 ***/

#ifdef TAG_DEBUG

static const gchar *
tag_get_realname_from_tagname (const gchar *tagname)
{
	g_return_val_if_fail (tagname != NULL, NULL);

	if ( strlen(tagname) == 1 )
	{
		guint8 n = (guint8) tagname[0];

		if (n >= (sizeof(TAGNAMES_SHORT_CLEARTEXT)/sizeof(gchar*)))
			return _("not implemented yet");

		return TAGNAMES_SHORT_CLEARTEXT[n];
	}

	return tagname;
}

#endif


/******************************************************************************
 *
 *   tag_get_name
 *
 *   returns name of a tag or NULL on error
 *
 ***/

#ifdef TAG_DEBUG

static const gchar *
tag_get_name (NGS_tag *tag)
{
	g_return_val_if_fail ( tag != NULL, NULL);

	if ( !tag->name )
		return _("(no name)");

	return tag_get_realname_from_tagname(tag->name);
}

#endif

/******************************************************************************
 *
 *                        ---- taglist ----
 *
 *           a taglist is just a simple GPtrArray* of NGS_tag* records
 *
 ******************************************************************************/


/******************************************************************************
 *
 *   taglist_write_to_ramfile
 *
 ***/

void
taglist_write_to_ramfile (GPtrArray *taglist, ramfile *rf)
{
	guint32	i, len;

	g_return_if_fail ( rf != NULL );

	if ( (!taglist) || taglist->len == 0 )
	{
		ramfile_write32(rf,0);  /* number of tags */
		return;
	}

	len = taglist->len;

	ramfile_write32(rf, len);   /* number of tags */

	for ( i = 0;  i < len; i++ )
	{
		NGS_tag *p_tag;

		p_tag = (NGS_tag*) g_ptr_array_index(taglist,i);

		if (p_tag)
			tag_write_to_ramfile (p_tag, rf);
	}
}


/******************************************************************************
 *
 *   taglist_read_from_ramfile
 *
 ***/

GPtrArray *
taglist_read_from_ramfile (ramfile *rf)
{
	GPtrArray   *newtl;
	guint32      no_tags, i;

	g_return_val_if_fail ( rf != NULL, NULL );

	no_tags = ramfile_read32(rf);

	newtl = g_ptr_array_new();

	for ( i = 0; i < no_tags; i++ )
	{
		NGS_tag *newtag = tag_new_from_ramfile (rf);

		if (!newtag)
		{
			taglist_free(newtl);
			return NULL;
		}

		g_ptr_array_add (newtl, newtag);
	}

	return newtl;
}


/******************************************************************************
 *
 *  taglist_free 
 *
 ***/

void
taglist_free (GPtrArray *taglist)
{
	guint  i;

	if (!taglist)
		return;

	for ( i = 0; i < taglist->len; i++ )
	{
		NGS_tag *tag = (NGS_tag *) g_ptr_array_index (taglist, i);

		if (tag)
			tag_free (tag);
	}

	g_ptr_array_free (taglist, TRUE);
}


/******************************************************************************
 *
 *   taglist_dump
 *
 *   for debugging: dumps a taglist
 *
 ***/

#ifdef TAG_DEBUG

void
taglist_dump (GPtrArray *taglist, const gchar *msg)
{
	guint i;

	g_return_if_fail ( msg     != NULL );

	g_print ("dump taglist - %s:\n", msg);

	if ( (!taglist) || taglist->len == 0 )
	{
		g_print ("\t * taglist empty\n");
		return;
	}

	for ( i = 0;  i < taglist->len; i++ )
	{
		NGS_tag *tag = (NGS_tag *) g_ptr_array_index (taglist,i);
		if (tag)
			tag_dump (tag);
	}
}

#endif

/******************************************************************************
 *
 *  taglist_find_tag_from_tagname
 *
 *  find (first) tag in taglist from tagname
 *
 *  returns tag or NULL if not found
 *
 ***/

NGS_tag *
taglist_find_tag_from_tagname (GPtrArray *taglist, const gchar *tagname)
{
	guint	i, len;

	g_return_val_if_fail ( tagname != NULL, NULL);

	if (!taglist)
		return NULL;

	len = taglist->len;

	for ( i = 0; i < len; i++ )
	{
		NGS_tag *tag = (NGS_tag*) g_ptr_array_index (taglist,i);

		if ((tag) && (tag->name))
		{
			if ( tag->name[0] != *tagname )
				continue;

			if (g_ascii_strcasecmp(tag->name, tagname)==0)
				return tag;
		}
	}

	return NULL;
}



/******************************************************************************
 *
 *  tag_addedit_string_tag
 *
 *
 * if tag exists:
 *   - mustnotexist == TRUE => does nothing
 *   - mustnotexist == FALSE => change tag to new value
 *
 * if tag does not exist:
 *   - mustexist == TRUE => does nothing
 *   - mustexist == FALSE => adds tag with new value
 *
 ***/

void
tag_addedit_string_tag (	GPtrArray *taglist,
                        	const gchar *name,
                        	const gchar *newvalue,
                        	gboolean mustexist,
                        	gboolean mustnotexist)
{
	NGS_tag *tag;

	g_return_if_fail (taglist!=NULL);
	g_return_if_fail (name!=NULL);
	g_return_if_fail (newvalue!=NULL);

	tag = taglist_find_tag_from_tagname (taglist, name);

	if (tag)
	{
		if (mustnotexist)
			return;

		if (tag->data)
			g_free(tag->data);

		tag->data = (gpointer) g_strdup(newvalue);
		return;
	}

	if (mustexist)
		return;

	tag = tag_new_string (name, newvalue);
	g_return_if_fail (tag!=NULL);
	g_ptr_array_add(taglist, tag);

	return;
}


/******************************************************************************
 *
 * tag_addedit_integer_tag
 *
 *
 * if tag exists:
 *   - mustnotexist == TRUE => does nothing
 *   - mustnotexist == FALSE => change tag to new value
 *
 * if tag does not exist:
 *   - mustexist == TRUE => does nothing
 *   - mustexist == FALSE => adds tag with new value
 *
 ***/

void
tag_addedit_integer_tag (	GPtrArray *taglist,
                        	const gchar *name,
                        	guint32 newvalue,
                        	gboolean mustexist,
							gboolean mustnotexist)
{
	NGS_tag *tag;

	g_return_if_fail (taglist!=NULL);
	g_return_if_fail (name!=NULL);

	tag = taglist_find_tag_from_tagname (taglist, name);

	if (tag)
	{
		if (mustnotexist)
			return;

		tag->data = GUINT_TO_POINTER(newvalue);
		return;
	}

	if (mustexist)
		return;

	tag = tag_new_integer (name, newvalue);
	g_return_if_fail (tag!=NULL);
	g_ptr_array_add(taglist, tag);

	return;
}


/******************************************************************************
 *
 * tag_addedit_float_tag
 *
 *
 * if tag exists:
 *   - mustnotexist == TRUE => does nothing
 *   - mustnotexist == FALSE => change tag to new value
 *
 * if tag does not exist:
 *   - mustexist == TRUE => does nothing
 *   - mustexist == FALSE => adds tag with new value
 *
 ***/

void
tag_addedit_float_tag  (	GPtrArray *taglist,
                        	const gchar *name,
                        	gfloat newvalue,
                        	gboolean mustexist,
                        	gboolean mustnotexist)
{
	NGS_tag *tag;

	g_return_if_fail (taglist!=NULL);
	g_return_if_fail (name!=NULL);

	tag = taglist_find_tag_from_tagname (taglist, name);

	if (tag)
	{
		if (mustnotexist)
			return;

		tag->data = NULL;

		g_return_if_fail ( sizeof(gfloat) <= sizeof(gpointer) ); /* otherwise we are out of luck */

		memcpy(&tag->data, &newvalue, sizeof(gfloat));
//		tag->data = *((gpointer*)&newvalue);

		return;
	}

	if (mustexist)
		return;

	tag = tag_new_float (name, newvalue);
	g_return_if_fail (tag!=NULL);
	g_ptr_array_add(taglist, tag);

	return;
}



/******************************************************************************
 *
 *  taglist_get_int_tag
 *
 *  returns TRUE if the tag was found, otherwise FALSE
 *
 ***/

gboolean
taglist_get_int_tag ( GPtrArray *taglist,
                      const gchar *tagname,
                      guint *value )
{
	NGS_tag *tag;

	g_return_val_if_fail ( taglist != NULL, FALSE);
	g_return_val_if_fail ( tagname != NULL, FALSE);
	g_return_val_if_fail ( value   != NULL, FALSE);

	tag = taglist_find_tag_from_tagname (taglist, tagname);

	if (tag)
		*value = GPOINTER_TO_UINT(tag->data);

	return (tag!=NULL);
}


/******************************************************************************
 *
 *  taglist_get_string_tag
 *
 *  returns TRUE if the tag was found, otherwise FALSE
 *
 ***/

gboolean
taglist_get_string_tag ( GPtrArray *taglist,
                         const gchar *tagname,
                         const gchar **value )
{
	NGS_tag *tag;

	g_return_val_if_fail ( taglist != NULL, FALSE);
	g_return_val_if_fail ( tagname != NULL, FALSE);
	g_return_val_if_fail ( value   != NULL, FALSE);

	tag = taglist_find_tag_from_tagname (taglist, tagname);

	if (tag)
		*value = (const gchar*) tag->data;

	return (tag!=NULL);
}

