/* Jungle Monkey
 * Copyright (C) 1999, 2000  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 <string.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>

/* These are here so MIN/MAX don't get redefined */
#include "util.h"
#include <glib.h>


static gboolean is_blankpathname (const gchar* path, gboolean (*func)(const gchar*));


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


time_t 
get_time_gmt(void)
{
  time_t current_time;

  time(&current_time);
  g_assert(gmtime(&current_time) != NULL);

  return current_time;
}


unsigned int
millitime (void)
{
  struct timeval tv;
  
  g_assert (gettimeofday (&tv, NULL) == 0);

  return ((tv.tv_sec * 1000) + (tv.tv_usec / 1000));
}


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

void
rand_timer_set (guint* timer, guint time, guint delay, 
		  GSourceFunc func, gpointer user_data)
{
  g_return_if_fail (timer);
  g_return_if_fail (func);

  if (!*timer)
    {
      if (delay)
	time += rand() % delay;

      *timer = g_timeout_add (time, func, user_data);
    }
}


void
rand_timer_cancel (guint* timer)
{
  g_return_if_fail (timer);

  if (*timer)
    {
      g_assert (g_source_remove(*timer));
      *timer = 0;
    }
}


void
rand_timer_reset (guint* timer, guint time, guint delay, 
		    GSourceFunc func, gpointer user_data)
{
  rand_timer_cancel (timer);
  rand_timer_set (timer, time, delay, func, user_data);
}



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

char* 
strcasestr(const char* haystack, const char* needle)
{
  unsigned int i, j;

  g_return_val_if_fail (haystack != NULL, NULL);
  g_return_val_if_fail (needle   != NULL, NULL);

  if (needle[0] == '\0')
    return (gchar*) haystack;

  for (i = 0; haystack[i] != '\0'; ++i)
    {
      for (j = 0; needle[j] != '\0'; ++j)
	{
	  if (tolower(haystack[i + j]) != tolower(needle[j]))
	    goto next;
	}
      return (gchar*) &haystack[i];

    next:
      ;
    }

  return NULL;

}




char*
strtokq (char* s, const char* delim, char** next)
{
  gchar* start = NULL;
  gchar last_char = 0;
  gboolean is_quote = 0;
  gint i;

  /* Set s if necessary */
  if (s == NULL)
    s = *next;

  /* Find the beginning of the next token */
  for (;;)
    {
      if (*s == '\0')
	break;

      /* Try to find a skipable character */
      for (i = 0; delim[i] != *s && delim[i] != '\0'; ++i);

      /* We found non-skippable character - break */
      if (delim[i] == '\0')
	break;

      /* Check if it's a quote */
      if (delim[i] == '\"')
	{
	  /* If it's not part of a \", break */
	  if (last_char != '\\')
	    {
	      is_quote = TRUE;
	      ++s;
	      break;
	    }
	}

      /* Skip it */
      last_char = *s++;
    }


  /* Return NULL if we're done */
  if (*s == '\0')
    {
      *next = (gchar*) s;
      return NULL;
    }

  /* Read the token */
  start = s;
  last_char = '\0';

  for (;;)
    {
      if (*s == '\0')
	break;

      /* Try to find a skipable character */
      for (i = 0; delim[i] != *s && delim[i] != '\0'; ++i);

      /* Check if we found a quote (endquote or otherwise, it doesn't matter) */
      if (delim[i] == '\"' && last_char != '\\')
	break;

      /* Check if we found a skippable character */
      else if (is_quote == FALSE && delim[i] != '\0')
	break;

      /* This character is part of the token, continue */
      last_char = *s++;
    }

  /* If we didn't hit the end, write '\0' and update next.
     Otherwise, set next to the end so that the next call returns NULL */
  if (*s != '\0')
    {
      *s++ = '\0';
      *next = s;
    }
  else
    *next = s;		/* This causes next call to return NULL */

  return start;
}


char*
stresc (const char* s, const char* esc, char escc)
{
  char* start;
  char* r;
  size_t size;

  size = 1 + strlen(s) + strchrcount(s, esc);
  if (!strchr(esc, escc))
    for (r = (char*) s; *r; r++)
      if (*r == escc) 
	++size;
  start = r = g_malloc (size);
  
  while (*s)
    {
      if (strchr(esc, *s) || *s == escc)
	*r++ = escc;
      *r++ = *s++;
    }
  *r = '\0';

  return start;
}


char*
strunesc (const char* s, const char* esc, char escc)
{
  char* start;
  char* r;

  start = r = g_malloc (strlen(s)+1);

  while (*s)
    {
      if (*s == escc && s[1] && (s[1] == escc || (esc && strchr(esc, s[1]))) )
	s++;
      *r++ = *s++;
    }
  *r = '\0';

  return start;
}


char*
strescape (const char* s)
{
  gchar escextra;
  gchar* r;
  gchar* start;

  int size;

  size = 1 + strlen(s) + strchrcount(s, "\a\b\f\n\r\t\v\\\'\"");
  size += strchrcount(s, "&") * 3;

  start = r = g_new(gchar, size);
  
  for (;;)
    {
      switch (*s)
	{
	case '\a':      	{ escextra = 'a';  goto esc_extra; }
	case '\b':		{ escextra = 'b';  goto esc_extra; }
	case '\f':		{ escextra = 'f';  goto esc_extra; }
	case '\n':	      	{ escextra = 'n';  goto esc_extra; }
	case '\r':      	{ escextra = 'r';  goto esc_extra; }
	case '\t':	      	{ escextra = 't';  goto esc_extra; }
	case '\v':	      	{ escextra = 'v';  goto esc_extra; }
	case '\\' :       	{ escextra = '\\'; goto esc_extra; }
	case '\'' :       	{ escextra = '\''; goto esc_extra; }
	case '\"' :       	{ escextra = '\"'; goto esc_extra; }
	  {
	  esc_extra:
	    *r++ = '\\';
	    *r++ = escextra;
	    ++s;
	    break;
	  }
	case '\0':	  	{ *r = *s;	    return start; }
	case '&': 		{ *r++ = *s++; 
	                          *r++ = 'a';
				  *r++ = 'm';
				  *r++ = 'p';			  }
	default:                { *r++ = *s++;			  }
	}
    }

  g_assert_not_reached();
  return NULL;
}


char*
strunescape (const char* s)
{
  char* r;
  char* start;
  int size;

  size = 1 + strlen(s);		/* This is an overestimate */
  start = r = g_new(gchar, size);
  
  for (;;)
    {
      switch (*s)
	{
	case '\\':
	  {
	    switch (*++s)
	      {
	      case 'a':      	{ *r++ = '\a';	break;	}
	      case 'b':		{ *r++ = '\b';  break; 	}
	      case 'f':		{ *r++ = '\f';  break; 	}
	      case 'n':	      	{ *r++ = '\n';  break; 	}
	      case 'r':      	{ *r++ = '\r';  break; 	}
	      case 't':	      	{ *r++ = '\t';  break; 	}
	      case 'v':	      	{ *r++ = '\v';  break; 	}
	      case '\\' :       { *r++ = '\\'; 	break;	}
	      case '\'' :       { *r++ = '\''; 	break; 	}
	      case '\"' :       { *r++ = '\"'; 	break; 	}
	      case '\0':	{ *r   = '\0';	return start; }
	      default:         	{ *r++ = '\\';  *r++ = *s;    }
	      }
	    ++s;
	    break;
	  }
	case '&':
	  {
	    *r++ = *s++;

	    if (strncmp("amp", s, 3) == 0)
	      s += 3;
	  }
	case '\0': 		{ *r   = '\0';	return start; 	}
	default: 		{ *r++ = *s++; 		}
	}
    }

  g_assert_not_reached();
  return NULL;
}


int
strchrcount (const char* s, const char* chars)
{
  int i;
  int count = 0;

  for (; *s != '\0'; ++s)
    {
      for (i = 0; chars[i] != '\0'; ++i)
	{
	  if (chars[i] == *s)
	    {
	      ++count;
	      goto next;
	    }
	}
    next:;
    }

  return count;
}


int
strsuffixcmp (const char* s1, const char* s2)
{
  int l_s1 = strlen(s1);
  int l_s2 = strlen(s2);

  if (l_s1 > l_s2)
    return strcmp(&s1[l_s1 - l_s2], s2);
  else
    return strcmp(s1, &s2[l_s2 - l_s1]);

  g_assert_not_reached();
}


static char bits2hex[16] = { '0', '1', '2', '3', 
			     '4', '5', '6', '7',
			     '8', '9', 'a', 'b',
			     'c', 'd', 'e', 'f' };

char* 
strtoreadable (const char* s)
{
  gint length = 0;
  const gchar* p = s;
  gchar* str;
  gint i;

  g_return_val_if_fail (s, NULL);

  for (p = s; *p; ++p)
    {
      if (((guint) *p < 32) || ((guint) *p > 127))
	length += 3;
      else
	++length;
    }
  ++length;

  str = g_new(gchar, length);

  for (i = 0, p = s; *p; ++p)
    {
      if (((guint) *p < 32) || ((guint) *p > 127))
	{
	  str[i++] = '\\';
	  str[i++] = bits2hex[(*p & 0xF0) >> 4];
	  str[i++] = bits2hex[(*p & 0x0F)];
	}
      else
	{
	  str[i++] = *p;
	}
    }
  str[i] = '\0';

  return str;
}


size_t
strnlen (const char* s, size_t len)
{
  size_t l = 0;
  const char* end = s + len;

  while (s < end && *s) 
    { 
      ++s; 
      ++l;
    }

  return l;
}


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


char* 
filesize_to_string(int size)
{
  gchar* str;

  g_return_val_if_fail (size >= 0, NULL);

  if (size < 1024)
    str = g_strdup_printf("%d bytes", size);
  else if (size < (1024 * 1024))
    str = g_strdup_printf("%.1fk", (double) size / 1024.0);
  else
    str = g_strdup_printf("%.1fM", (double) size / 1048576.0);

  return str;
}


char* 
filesize_fraction_to_string(int offset, int size)
{
  char* str;

  g_return_val_if_fail (offset >= 0, NULL);
  g_return_val_if_fail (size >= 0, NULL);

  if (size < 1024)
    str = g_strdup_printf("%d/%d bytes", offset, size);
  else if (size < (1024 * 1024))
    str = g_strdup_printf("%.1f/%.1fk", 
			  (double) offset / 1024.0,
			  (double) size / 1024.0);
  else
    str = g_strdup_printf("%.1f/%.1fM", 
			  (double) offset / 1048576.0,
			  (double) size / 1048576.0);

  return str;
}


int
file_exists (const char* path)
{
  struct stat s;

  g_return_val_if_fail (path, FALSE);

  if (stat (path, &s))
    return FALSE;

  return TRUE;
}


int 
file_size (const char* path)
{
  struct stat s;

  g_return_val_if_fail (path, -1);

  if (stat (path, &s))
    return -1;

  return s.st_size;
}


int
FILE_size (FILE* file)
{
  struct stat s;

  if (fstat(fileno(file), &s))
    return -1;

  return s.st_size;
}


char* 
tilde_expand (const char* path)
{
  char* str = NULL;

  g_return_val_if_fail (path, NULL);

  if (*path == '~')
    {
      char* home;

      home = g_getenv ("HOME");
      g_assert (home);

      str = g_strconcat (home, G_DIR_SEPARATOR_S, &path[1], NULL);
      g_free (home);
    }
  else
    {
      str = g_strdup (path);
    }

  return str;
}



int
is_file (const char* path)
{
  struct stat filestat;

  if (stat(path, &filestat) == 0)
    return (S_ISREG(filestat.st_mode) != 0);

  return FALSE;
}


int
is_dir (const char* path)
{
  struct stat filestat;

  if (stat(path, &filestat) == 0)
    return (S_ISDIR(filestat.st_mode) != 0);

  return FALSE;
}


gboolean
mkdirr(const gchar* path, gint mode)
{
  gchar* parent;
  gboolean rv;

  /* Try to make the directory as given */
  if (mkdir(path, mode) == 0 || errno == EEXIST)
    return TRUE;

  /* Failed - try to make the parent directory */
  parent = g_dirname(path);
  rv = mkdirr(parent, mode);
  g_free(parent);

  /* Try to make the directory again if we created the parent.  We
     check EEXIST because the original path may have ended with a
     directory separator. */
  if (rv)
    return (mkdir(path, mode) == 0 || errno == EEXIST);

  return FALSE;
}


gboolean
is_okfilename (const gchar* name)
{
  g_return_val_if_fail (name != NULL, FALSE);

  /* Skip any whitespace */
  while (isspace((int) *name))
    ++name;

  /* Can't be blank */
  if (name[0] == '\0')
    return FALSE;

  /* The name can't be . or .. */
  if (name[0] == '.')
    {
      if (name[1] == '\0')
	return FALSE;
      else if (name[1] == '.' && name[2] == '\0')
	return FALSE;
    }

  /* The name can't have [/\|;:] in it */
  while (*name)
    {
      if (*name == '/' || *name == '\\' || *name == '|' ||
	  *name == ',' || *name == ';'  || *name == ':')
	return FALSE;

      ++name;
    }

  return TRUE;
}


gboolean
is_okpathname (const gchar* path)
{
  return is_blankpathname (path, is_okfilename);
}



gboolean
is_goodfilename (const gchar* name)
{
  int i = 0;

  g_return_val_if_fail (name != NULL, FALSE);

  /* Skip any whitespace */
  while (isspace((int) *name))
    ++name;

  /* Good file names begin with an alphanumerical character */
  if (name[0] == '\0' || !isalnum((int) name[i]))
    return FALSE;
      

  /* Good names have alpha-num characters and certain punctuation only */
  /* We start at 0 to weed out names that begin with weird punctuation (like /) */
  for (i = 0; name[i] != '\0'; ++i)
    {
      /* Don't allow /,\, or : because the are directory seperators on
         some systems. */

      if (isalnum((int) name[i]) || 
	  name[i] == '.' || name[i] == '-' || 
	  name[i] == '_' || name[i] == ' ' ||
	  name[i] == '!' || name[i] == '\'' ||
	  name[i] == '(' || name[i] == ')' ||
	  name[i] == '&' || name[i] == ',' ||
	  name[i] == '\"' || name[i] == '+')
	continue;

      return FALSE;
    }

  return TRUE;
}


gboolean
is_goodpathname (const gchar* path)
{
  return is_blankpathname(path, is_goodfilename);
}


gboolean
is_blankpathname (const gchar* path, gboolean (*func)(const gchar*))
{
  gchar** tokens;
  guint i;
  gboolean rv = FALSE;

  g_return_val_if_fail(path != NULL, FALSE);
  g_return_val_if_fail(func != NULL, FALSE);

  /* Skip leading '/' */
  if (path[0] == G_DIR_SEPARATOR)
    path = &path[1];

  tokens = g_strsplit(path, G_DIR_SEPARATOR_S, 0);

  if (tokens == NULL || tokens[0] == NULL)
    goto done;

  for (i = 0; tokens[i] != NULL; ++i)
    if (!func(tokens[i]))
      goto done;

  rv = TRUE;

 done:
  g_strfreev(tokens);
  return rv;
  

}


void
my_error (const gchar* format, ...)
{
  va_list args;
  
  va_start (args, format);
  g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, format, args);
  va_end (args);

  exit (EXIT_FAILURE);
}


gboolean
remove_by_value_hrfunc (gpointer key, gpointer value, gpointer user_data)
{
  if (value == user_data)
    return TRUE;

  return FALSE;
}


syntax highlighted by Code2HTML, v. 0.9.1