/* Jungle Monkey
 * Copyright (C) 1999-2001  The Regents of the University of Michigan
 *
 * 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.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "elf.h"

static ElfNode* elf_new_vformat (const gchar* name, const gchar* format, va_list args);



/* ************************************************************ */
/* Building							*/

ElfNode* 
elf_new (const gchar* name)
{
  ElfNode* node;

  node = g_new0 (ElfNode, 1);
  if (name)
    node->name = g_strdup (name);

  return node;
}


/**

   elf_new_format (gchar* name, gchar* fmt, ...)

   format:
      s = string
      d = integer
      b = boolean
      B = boolean, but only set if TRUE

   ex:
     elf_new_format ("info", "sdb", "name", "myfile", 
                                   "length", 23120,
				   "read-only", FALSE);
     elf_new_format ("info", "");
     elf_new_format ("info", NULL);

 */
ElfNode* 
elf_new_format (const gchar* name, const gchar* format, ...)
{
  va_list args;
  ElfNode* node;
  
  va_start (args, format);
  node = elf_new_vformat (name, format, args);
  va_end (args);

  return node;
}


static ElfNode* 
elf_new_vformat (const gchar* name, const gchar* format, va_list args)
{
  ElfNode* node;
  
  g_return_val_if_fail (format, NULL);

  node = elf_new (name);

  if (!format)
    return node;

  for (; *format; ++format)
    {
      gchar* attribute;

      attribute = va_arg (args, gchar*);

      switch (*format)
	{
	case 's':
	  {
	    gchar* s;
	    s = va_arg (args, gchar*);
	    elf_set_attribute (node, attribute, s);
	    break;
	  }

	case 'd':
	  {
	    gint d;
	    d = va_arg (args, gint);
	    elf_set_attribute_int (node, attribute, d);
	    break;
	  }

	case 'b':
	  {
	    gboolean b;
	    b = (gboolean) va_arg (args, gint);
	    elf_set_attribute_bool (node, attribute, b);
	    break;
	  }

	case 'B':
	  {
	    gboolean b;
	    b = (gboolean) va_arg (args, gint);
	    if (b)
	      elf_set_attribute_bool (node, attribute, b);
	    break;
	  }
	}
    }

  return node;
}


void
elf_delete (ElfNode* node)
{
  if (node != NULL)
    {
      GList* i;

      /* Check if we already have the attribute */
      for (i = node->attributes; i != NULL; i = i->next)
	{
	  ElfAttribute* attribute = (ElfAttribute*) i->data;
	  g_assert (attribute != NULL);
      
	  g_free(attribute->name);
	  g_free(attribute->value);
	  g_free(attribute);
	}

      g_list_free(node->attributes);
      g_free(node->name);
      g_free(node);
    }
}


void
elf_delete_list (GList* list)
{
  GList* i;

  for (i = list; i != NULL; i = i->next)
    {
      ElfNode* node = (ElfNode*) i->data;
      g_assert (node != NULL);

      elf_delete(node);
    }

  g_list_free(list);
}


ElfNode* 
elf_clone (const ElfNode* node)
{
  ElfNode* n;
  GList* i;

  g_return_val_if_fail (node, NULL);

  n = elf_new (node->name);
  for (i = node->attributes; i != NULL; i = i->next)
    {
      ElfAttribute* attr = (ElfAttribute*) i->data;
      ElfAttribute* a;
      
      a = g_new0 (ElfAttribute, 1);
      a->name   = g_strdup (attr->name);
      a->length = attr->length;
      if (attr->value)
	{
	  a->value = g_malloc (attr->length + 1);
	  memcpy (a->value, attr->value, attr->length);
	  a->value[attr->length] = '\0';
	}

      n->attributes = g_list_prepend (n->attributes, a);
    }

  n->attributes = g_list_reverse (n->attributes);
  
  return n;
}


void
elf_set_name (ElfNode* node, const char* name)
{
  g_return_if_fail (node);
  g_return_if_fail (name);

  g_free (node->name);
  node->name = g_strdup (name);
}


gchar*	 
elf_get_name (ElfNode* node)
{
  g_return_val_if_fail (node, NULL);

  return node->name;

}



void
elf_set_attribute (ElfNode* node, const gchar* name, const gchar* value)
{
  if (value)
    elf_set_attribute_data (node, name, value, strlen (value));
  else
    elf_set_attribute_data (node, name, NULL, 0);
}


void
elf_set_attribute_int (ElfNode* node, const gchar* name, gint value)
{
  gchar buffer[64];

  sprintf (buffer, "%d", value);
  elf_set_attribute (node, name, buffer);
}


void
elf_set_attribute_bool (ElfNode* node, const gchar* name, gboolean value)
{
  if (value)
    elf_set_attribute (node, name, "1");
  else
    elf_set_attribute (node, name, "0");
}


void
elf_set_attribute_data (ElfNode* node, const gchar* name, const gchar* data, gint length)
{
  ElfAttribute* attribute;
  GList* i;

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

  /* Check if we already have the attribute */
  for (i = node->attributes; i != NULL; i = i->next)
    {
      attribute = (ElfAttribute*) i->data;
      g_assert (attribute != NULL);
      
      if (!strcmp(attribute->name, name))
	{
	  g_free (attribute->value);
	  if (data)
	    {
	      attribute->value = g_malloc (length + 1);
	      memcpy (attribute->value, data, length);
	      attribute->value[length] = '\0';
	    }
	  else
	    attribute->value = NULL;
	  attribute->length = length;
	  return;
	}
    }

  /* Add the attribute */
  attribute = g_new0(ElfAttribute, 1);
  attribute->name = g_strdup (name);
  if (data)
    attribute->value = g_memdup (data, length);
  attribute->length = length;

  node->attributes = g_list_prepend(node->attributes, attribute);
}




gchar*
elf_get_attribute (const ElfNode* node, const gchar* name)
{
  gchar* value = NULL;
  gint length = 0;
  
  elf_get_attribute_data (node, name, &value, &length);
  return value;
}


gint
elf_get_attribute_int (const ElfNode* node, const gchar* name)
{
  gchar* attr;

  attr = elf_get_attribute (node, name);
  if (!attr)
    return 0;

  return atoi(attr);
}


gboolean
elf_get_attribute_bool (const ElfNode* node, const gchar* name)
{
  gchar* attr;

  attr = elf_get_attribute (node, name);
  if (!attr)
    return FALSE;

  if (*attr == '1' || *attr == 't' || *attr == 'T')
    return TRUE;

  return FALSE;
}


void
elf_get_attribute_data (const ElfNode* node, const gchar* name, gchar** value, gint* length)
{
  GList* i;

  g_return_if_fail (node != NULL);
  g_return_if_fail (name != NULL);
  g_return_if_fail (value != NULL);
  g_return_if_fail (length != NULL);

  *value = NULL;
  *length = 0;

  for (i = node->attributes; i != NULL; i = i->next)
    {
      ElfAttribute* attribute = (ElfAttribute*) i->data;
      g_assert (attribute != NULL);
      
      if (!strcmp(attribute->name, name))
	{
	  *value = attribute->value;
	  *length = attribute->length;
	}
    }
}


gboolean
elf_has_attribute (const ElfNode* node, const gchar* name)
{
  GList* i;

  g_return_val_if_fail (node != NULL, FALSE);
  g_return_val_if_fail (name != NULL, FALSE);

  for (i = node->attributes; i != NULL; i = i->next)
    {
      ElfAttribute* attribute = (ElfAttribute*) i->data;
      g_assert (attribute != NULL);
      
      if (!strcmp(attribute->name, name))
	return TRUE;
    }

  return FALSE;
}


void
elf_remove_attribute (ElfNode* node, const gchar* name)
{
  GList* i;

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

  /* TODO: Fix, this is inefficient */
  for (i = node->attributes; i != NULL; i = i->next)
    {
      ElfAttribute* attribute = (ElfAttribute*) i->data;
      g_assert (attribute != NULL);
      
      if (!strcmp(attribute->name, name))
	{
	  node->attributes = g_list_remove(node->attributes, attribute);
	  return;
	}
    }
}




/* ************************************************************ */
/* Reading							*/



#define SKIP_WHITESPACE	  { while (i < length && (isspace((int) buffer[i]) && buffer[i] != '\n')) ++i; }
#define CHECK_DONE	  { if (i >= length) goto done; }

static ElfAttribute* elf_read_attribute (const gchar* buffer, gint length, gint* bytes_read);


ElfNode*
elf_read (const gchar* buffer, gint length)
{
  gint bytes_read;
  return elf_read_node (buffer, length, &bytes_read);
}


GList*
elf_read_list (const gchar* buf, gint len)
{
  GList* nodes = NULL;
  gint off = 0;

  while (off < len)
    {
      gint br;
      ElfNode* node;

      node = elf_read_node (&buf[off], len - off, &br);
      if (node == NULL)
	break;

      off += br;
      nodes = g_list_prepend (nodes, node);
    }

  nodes = g_list_reverse (nodes);

  return nodes;
}


GList*
elf_read_list_path (const gchar* path)
{
  FILE*  file = NULL;
  gchar* buf  = NULL;
  gint   len;
  struct stat s;
  GList* list = NULL;

  file = fopen (path, "r");
  if (!file) return NULL;

  if (fstat(fileno(file), &s))
    goto done;
 
  len = s.st_size;
  if (len <= 0)
    goto done;

  buf = g_malloc (len);
  if (fread (buf, len, 1, file) != 1)
    goto done;

  list = elf_read_list (buf, len);

 done:
  if (file) fclose (file);
  if (buf)  g_free (buf);

  return list;
}


ElfNode*
elf_read_node (const gchar* buffer, gint length, gint* bytes_read)
{
  ElfNode* node = NULL;
  gint i = 0;
  const gchar* p;

  *bytes_read = 0;

  /* Skip whitespace */
  SKIP_WHITESPACE;
  CHECK_DONE;

  /* Read in name */
  p = &buffer[i];
  while (i < length && !isspace((int) buffer[i])) ++i;
  if (p == &buffer[i]) goto done;
  node = g_new0 (ElfNode, 1);
  node->name = g_strndup (p, &buffer[i] - p);
  CHECK_DONE;

  /* Skip whitespace */
  SKIP_WHITESPACE;
  CHECK_DONE;

  /* Read the attributes */
  while (i < length)
    {
      gint br;
      ElfAttribute* attribute;

      attribute = 
	elf_read_attribute (&buffer[i], length - i, &br);
      i += br;
      if (attribute == NULL)
	break;
      
      node->attributes = g_list_prepend (node->attributes, attribute);
    }
  node->attributes = g_list_reverse(node->attributes);
  
  /* Skip whitespace and newline */
  SKIP_WHITESPACE;
  if (i < length && buffer[i] == '\n') ++i;

 done:
  *bytes_read = i;

  return node;
}



ElfAttribute*
elf_read_attribute (const gchar* buffer, gint length, gint* bytes_read)
{
  ElfAttribute* attribute = NULL;
  gint i = 0;
  const gchar* p;
  gint j;

  *bytes_read = 0;

  /* Skip whitespace */
  SKIP_WHITESPACE;
  CHECK_DONE;
  if (buffer[i] == '\n')
    {
      ++i;
      goto done;
    }

  /* Read in the name */
  p = &buffer[i];
  while (i < length && !isspace((int) buffer[i]) && buffer[i] != '=') ++i;
  if (p == &buffer[i]) goto done;
  attribute = g_new0 (ElfAttribute, 1);
  attribute->name = g_strndup (p, &buffer[i] - p);
  CHECK_DONE;

  /* Skip whitespace */
  SKIP_WHITESPACE;
  CHECK_DONE;

  /* Skip = */
  if (buffer[i] != '=') goto done;
  ++i;
  CHECK_DONE;

  /* Skip whitespace */
  SKIP_WHITESPACE;
  CHECK_DONE;

  /* Skip '"' */
  if (buffer[i] != '\"') goto done;
  ++i;
  CHECK_DONE;

  /* Read in the value */
  p = &buffer[i];
  while (i < length)
    {
      /* Ignore \" */
      if (buffer[i] == '\\' && (i + 1 < length) && buffer[i + 1] == '\"')
	i += 2;
      else if (buffer[i] == '\"')
	break;

      ++i;
    }

  CHECK_DONE;
  if (buffer[i] != '\"') goto done;

  /* Save the attribute */
  attribute->value = g_new0 (gchar, 1 + &buffer[i] - p);
  attribute->length = &buffer[i] - p;
  j = 0;
  while (p < &buffer[i])
    {
      /* Break if this is the last quote */
      if (*p == '\"')
	{
	  break;
	}
      /* Convert \" to " */
      else if (*p == '\\' && (&p[1] < &buffer[i]) && p[1] == '\"')
	{
	  attribute->value[j] = '\"';
	  p += 2;
	}
      /* Convert \\ to \ */
      else if (*p == '\\' && (&p[1] < &buffer[i]) && p[1] == '\\')
	{
	  attribute->value[j] = '\\';
	  p += 2;
	}
      /* Otherwise, it's just a normal letter */
      else
	{
	  attribute->value[j] = *p;
	  p++;
	}

      ++j;
    }
  attribute->value[j] = '\0';

  /* Move past the \ */
  ++i;

 done:
  *bytes_read = i;
  return attribute;
}


/* ************************************************************ */
/* Writing							*/

static void glue(gchar** dstp, gint* lenp, gchar* src1, gint length1, gchar* src2, gint length2);
static void elf_write_attribute(ElfAttribute* attribute, gchar** bufferp, gint* lengthp);


static void
glue(gchar** dstp, gint* lenp, gchar* src1, gint length1, gchar* src2, gint length2)
{
  gchar* dst;
  gint   len;

  len = length1 + length2;
  dst = g_new0(gchar, len);

  memcpy(dst, src1, length1);
  memcpy(&dst[length1], src2, length2);

  g_free(src1);
  g_free(src2);

  *dstp = dst;
  *lenp = len;
}


void
elf_write_list (GList* nodes, gchar** bufferp, gint* lengthp)
{
  GList* i;

  gchar* buffer = NULL;
  gint length = 0;

  g_return_if_fail (bufferp != NULL);
  g_return_if_fail (lengthp != NULL);

  for (i = nodes; i != NULL; i = i->next)
    {
      ElfNode* node;
      gchar* new_buffer;
      gint  new_length;

      node = (ElfNode*) i->data;
      g_assert (node != NULL);

      elf_write(node, &new_buffer, &new_length);
      glue(&buffer, &length, buffer, length, new_buffer, new_length);
    }

  *bufferp = buffer;
  *lengthp = length;
}


void
elf_write (ElfNode* node, gchar** bufferp, gint* lengthp)
{
  GList* i;

  gchar* buffer;
  gint length;
  gchar* newline;

  g_return_if_fail (node);
  g_return_if_fail (node->name);
  g_return_if_fail (bufferp);
  g_return_if_fail (lengthp);

  buffer = g_strdup_printf ("%s ", node->name);
  length = strlen (buffer);

  for (i = node->attributes; i != NULL; i = i->next)
    {
      ElfAttribute* attribute;
      gchar* new_buffer;
      gint  new_length;

      attribute = (ElfAttribute*) i->data;
      g_assert (attribute != NULL);

      elf_write_attribute(attribute, &new_buffer, &new_length);
      glue (&buffer, &length, buffer, length, new_buffer, new_length);
    }

  newline = g_strdup ("\n");
  glue (bufferp, lengthp, buffer, length, newline, 1);
}


void
elf_write_attribute (ElfAttribute* attribute, gchar** bufferp, gint* lengthp)
{
  gchar* buffer;

  g_return_if_fail (attribute);
  g_return_if_fail (attribute->name);
  g_return_if_fail (bufferp);
  g_return_if_fail (lengthp);

  if (attribute->value)
    {
      gchar* escaped_value;
      gint i, j;
      gint name_len;
      gint buffer_len;

      escaped_value = g_new0 (gchar, 2 * attribute->length + 1);
      for (i = 0, j = 0; i < attribute->length; ++i)
	{	
	  if (attribute->value[i] == '\"')
	    {
	      escaped_value[j++] = '\\';
	      escaped_value[j++] = '\"';
	    }
	  else if (attribute->value[i] == '\\')
	    {
	      escaped_value[j++] = '\\';
	      escaped_value[j++] = '\\';
	    }
	  else
	    {
	      escaped_value[j++] = attribute->value[i];
	    }
	}

      name_len = strlen (attribute->name);
      buffer_len = name_len + 2 + j + 2;

      buffer = g_new (gchar, buffer_len);
      memcpy (buffer, attribute->name, name_len);
      buffer[name_len] = '=';
      buffer[name_len + 1] = '\"';
      memcpy (&buffer[name_len + 2], escaped_value, j);
      buffer[name_len + 2 + j] = '\"';
      buffer[name_len + 2 + j + 1] = ' ';
      g_free (escaped_value);

      *bufferp = buffer;
      *lengthp = buffer_len;
    }
  else
    {
      buffer = g_strdup_printf ("%s ", attribute->name);

      *bufferp = buffer;
      *lengthp = strlen (buffer);
    }
}


void
elf_write_format (gchar** bufferp, gint* lengthp, 
		  const gchar* name, const gchar* format, ...)
{
  va_list args;
  ElfNode* node;

  va_start (args, format);
  node = elf_new_vformat (name, format, args);
  va_end (args);

  if (!node)
    {
      *bufferp = NULL;
      *lengthp = 0;
      return;
    }

  elf_write (node, bufferp, lengthp);
  elf_delete (node);
}


void
elf_write_list_path (GList* list, const gchar* path)
{
  FILE*  file;
  gchar* buf;
  gint   len;

  g_return_if_fail (path);

  file = fopen (path, "w");
  if (!file) return;

  elf_write_list (list, &buf, &len);
  if (buf)
    {
      fwrite (buf, len, 1, file);
      g_free (buf);
    }

  fclose (file);
}


syntax highlighted by Code2HTML, v. 0.9.1