/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Copyright (C) 2003-2004 Hiroyuki Ikezoe
 *
 *  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, 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.
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif /* HAVE_CONFIG_H */

#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <glib/gstdio.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#  include <unistd.h>
#endif

#include "utils.h"
#include "glib-utils.h"
#include "gtk-utils.h"
#include "config.h"
#include "kazehakase.h"
#include "kz-search.h"
#include "egg-pixbuf-thumbnail.h"

#define BUFFER_SIZE 1024

void
make_dir (const gchar *dirname)
{
	gint  i = 0, ii = 0, ret;
	gchar *temp, *temp2;

	temp = g_build_filename(g_get_home_dir(), "."PACKAGE, dirname, NULL);
	temp2 = g_alloca(strlen(temp) + 1);

	while (temp[i])
	{
		temp2[ii++] = temp[i];
		temp2[ii] = '\0';
		if ((temp[i + 1] == '/' || !temp[i + 1]) &&
		    !g_file_test(temp2, G_FILE_TEST_IS_DIR))
		{
			ret = g_mkdir(temp2, 0711);

#if 0 /* show dialog? */
			if (ret != 0)
			{
				perror("Faild to create directory");;
			}
#endif
		}
		i++;
	}

	g_free(temp);	
}


void
make_thumbnails_dir (void)
{
	gchar *temp;
	temp = g_strdup_printf("%s/.thumbnails", g_get_home_dir());
	if (!g_file_test(temp, G_FILE_TEST_IS_DIR))
	{
		g_mkdir(temp, 0711);
	}
	g_free(temp);	
	
	temp = g_strdup_printf("%s/.thumbnails/large", g_get_home_dir());
	if (!g_file_test(temp, G_FILE_TEST_IS_DIR))
	{
		g_mkdir(temp, 0711);
	}
	g_free(temp);	
}


gboolean
key_seems_sequential (const gchar *key, const gchar *prefix)
{
	gint len;
	const gchar *tail;
	gint i;
	gboolean valid;

	g_return_val_if_fail(key && *key, FALSE);
	g_return_val_if_fail(prefix && *prefix, FALSE);

	len = strlen(prefix);
	tail = key + len;

	if (strncmp(key, prefix, len)) return FALSE;

	len = strlen(tail);
	if (len <= 0) return FALSE;

	valid = TRUE;
	for (i = 0; i < len; i++)
	{
		if (!isdigit(tail[i]))
		{
			valid = FALSE;
			break;
		}
	}

	return valid;
}


gchar *
remove_tag (const gchar *string, gsize len)
{
	GString *work_string;
	guint i = 0;
	
	work_string = g_string_new(NULL);
	while (string[i] != '\0' && i < len)
	{
		if (string[i] == '<')
		{
			while (string[i] != '>' && string[i] != '\0' && i < len)
			{
				i++;
			}
		}
		else
			g_string_append_c(work_string, string[i]);
		i++;
	}
	
	return g_string_free(work_string, FALSE);
}


gchar *
html_to_text (const gchar *html)
{
	GString *work_string;
	guint i=0;
	
	work_string = g_string_new(NULL);
	while (html[i] != '\0')
	{
		gboolean ignore = FALSE;
		switch (html[i])
		{
		case '<':
		{
			gchar *name = NULL;
			if (!g_ascii_strncasecmp(html+i+1, "script", 6))
			{
				name = "/script";
				ignore = TRUE;
			}
			else if (!g_ascii_strncasecmp(html+i+1, "style", 5)) 
			{
				name = "/style"; 
				ignore = TRUE;
			}
			else if (!g_ascii_strncasecmp(html+i+1, "noscript", 8)) 
			{
				name = "/noscript"; 
				ignore = TRUE;
			}
			while (html[i] != '>' && html[i] != '\0')
				i++;
			if (ignore)
			{
				/* ignore until close tag */
				while (html[i] != '<' && html[i] != '\0' &&
				       g_ascii_strncasecmp(html+i+1, name, strlen(name)))
					i++;
				while (html[i] != '>' && html[i] != '\0')
					i++;
			}
			break;
		}
		case '&':
			if (!strncmp(html+i, "&amp;", 5))
			{
				g_string_append_c(work_string, '&');
				i+=4;
			}
			else if (!strncmp(html+i, "&quot;", 6))
			{
				g_string_append_c(work_string, '"');
				i+=5;
			}
			else if (!strncmp(html+i, "&lt;", 4))
			{
				g_string_append_c(work_string, '<');
				i+=3;
			}
			else if (!strncmp(html+i, "&gt;", 4))
			{
				g_string_append_c(work_string, '>');
				i+=3;
			}
			else
			{
				g_string_append_c(work_string, html[i]);
			}
			break;
		default:
			g_string_append_c(work_string, html[i]);
			break;
		}
		i++;
	}
	
	return g_string_free(work_string, FALSE);
}


gchar *
create_filename_from_uri (const gchar *uri)
{
	gchar *filename;
	gint i = 0;
	
	filename = g_strdup(uri);

	while (filename[i] != '\0')
	{
		if (filename[i] == '/')
			filename[i] = '_';
		i++;
	}

	return filename;
}


gchar *
create_filename_with_path_from_uri (const gchar *uri)
{
	gchar *filename;
	gchar *pos;
	gchar *scheme;
	
	pos = strstr(uri, "://");
	
	if (!pos)
	{
		pos = (gchar*)uri;
		scheme = g_strdup("");
	}
	else
	{
		scheme = g_strndup(uri, pos - uri);
		pos += 3;
	}

	if (g_str_has_suffix(uri, "/"))
	{
		filename = g_strconcat(scheme, G_DIR_SEPARATOR_S, pos, "_", NULL);
	}
	else
	{
		gchar *pos2 = strchr(uri, '?');
		if (pos2)
		{
			gchar *string = g_strndup(pos, pos2 - pos);
			filename = g_strconcat(scheme,
					       G_DIR_SEPARATOR_S,
					       string, 
					       "_"G_DIR_SEPARATOR_S,
					       pos2 + 1,
					       NULL);
			g_free(string);
		}
		else
		{
			filename = g_strconcat(scheme,
						G_DIR_SEPARATOR_S,
						pos,
						NULL);
		}
	}

	g_free(scheme);
	
	return filename;
}


gchar *
create_uri_from_filename (const gchar *filename)
{
	gchar *uri;
	gchar *pos;
	gchar *scheme;
	gchar *path;
	
	pos = strstr(filename, G_DIR_SEPARATOR_S);
	
	if (!pos)
		return NULL;

	scheme = g_strndup(filename, pos - filename);

	if (g_str_has_suffix(filename, G_DIR_SEPARATOR_S"_"))
	{
		path = g_strndup(pos + 1, strlen(pos) - 2); 
	}
	else
	{
		gchar *pos2 = g_strrstr(filename, "_"G_DIR_SEPARATOR_S);
		if (pos2)
		{
			gchar *string = g_strndup(pos + 1, pos2 - pos - 1);
			path = g_strconcat(string, 
					   "?",
					   pos2 + 2,
					   NULL);
			
			g_free(string);
		}
		else
		{
			path = g_strdup(pos + 1);
		}
	}

	if (!strcmp(scheme, "file"))
	{
		uri = g_strconcat(scheme, ":///", path, NULL);
	}
	else
	{
		uri = g_strconcat(scheme, "://", path, NULL);
	}
	
	g_free(scheme);
	g_free(path);

	return uri;
}


gchar *
url_decode(const gchar *src)
{
	GString *dest;
	gint len, i=0;

	if (!src) return NULL;
	len = strlen(src);
	dest = g_string_sized_new(len);

	while (src[i] != '\0' && i < len)
	{
		if (src[i] == '%')
		{
			if (i + 2 <= len &&
			    g_ascii_isxdigit(src[i+1]) && 
			    g_ascii_isxdigit(src[i+2]))
			{				
				g_string_append_c(dest,
						  g_ascii_xdigit_value(src[i+1]) * 16 + 
						  g_ascii_xdigit_value(src[i+2]));
				i+=2;
			}
		}
		else
		{
			g_string_append_c(dest, src[i]);
		}
		i++;
	}

	/* Free gstring and reserve its data.*/
	return g_string_free(dest, FALSE);
}


gchar *
create_profile_key_from_uri (const gchar *string)
{
	gchar *key, *pos;
	gint len, i = 0;
	
	if (!string)
		return NULL;
	pos = strchr(string, '?');
	len = strlen(string);
	if (pos)
		len = pos - string;
	key = g_strndup(string, len);

	while (key[i] != '\0' && i < len)
	{
		if (key[i] == '=')
			key[i] = '_';
		i++;
	}

	return key;
}

/* return hex-encoded UTF-8 data
 * please free returned gchar* if unnecessary
 */
gchar *
url_encode(const gchar* utf8src)
{
	GString *dest;
	const gchar *ch = utf8src;
	unsigned char buf;

	if (!utf8src) return "";

	dest = g_string_sized_new(strlen(utf8src));

	while(*ch != '\0')
	{
		if (((*ch >= 'A') && (*ch <= 'Z')) ||
		    ((*ch >= 'a') && (*ch <= 'z')) ||
		    ((*ch >= '0') && (*ch <= '9')))
		{
			g_string_append_c(dest, *ch);
		}
		else if (*ch == ' ')
		{
			g_string_append_c(dest, '+');

		}
		else
		{
			g_string_append_c(dest, '%');
			buf = (*ch >> 4) & 0x0f;
			g_string_append_c(dest,
					( (buf < 10) ? buf + '0' : buf + ('A' - 10)) );
			buf = *ch & 0x0f;
			g_string_append_c(dest,
					( (buf < 10) ? buf + '0' : buf + ('A' - 10)) );
		}
		ch++;
	}
	
	/* Free gstring and reserve its data.*/
	return g_string_free(dest, FALSE);
}


gchar *
complement_scheme (const gchar* url)
{
	gchar *file;

	if (g_file_test(url, G_FILE_TEST_EXISTS))
	{
		if (!g_path_is_absolute(url))
		{
			gchar *current= g_get_current_dir();
			file = g_strdup_printf("file://%s/%s",
					       current,
					       url);
			g_free(current);
		}
		else
		{
			file = g_strdup_printf("file://%s",
					       url);
		}
		return file;
	}
	else
		return g_strdup(url);
}

gchar *
xml_get_content (const gchar *buffer)
{
	gchar *pos1, *pos2, *pos3;
	gchar *name;
	gchar *content = NULL;
	
	pos1 = strchr(buffer, '>');
	pos3 = strchr(buffer, ' ');
	if (pos1)
	{
		gchar *string;
		guint len;
		if (pos3 && pos1 > pos3)
			len = pos3 - buffer - 1;
		else
			len = pos1 - buffer - 1;
		name = g_strndup(buffer + 1, len); 
		string = g_strconcat("</", name, NULL);
		pos2 = strstr(pos1, string);
		if (pos2)
			content = g_strndup(pos1 +1 , pos2 - pos1 - 1);
		g_free(string);
		g_free(name);
	}
	return content;
}

gchar *
xml_get_attr (const gchar *buffer, const gchar *attr_name)
{
	gchar *pos1, *pos2;
	gchar *string;
	gchar *attr = NULL;
	guint len;

	pos1 = strchr(buffer, '>');
	if (!pos1)
		return NULL;

	string = g_strdup_printf("%s=\"", attr_name);
	pos1 = g_strstr_len(buffer, pos1 - buffer, string); 
	if (pos1)
	{
		len = strlen(string);
		pos1 += len;
		pos2 = strchr(pos1, '"'); 
		if (pos2)
			attr = g_strndup(pos1, pos2 - pos1); 
	}
	g_free(string);
	
	return attr;
}


void
purge_history_file(const gchar *path, time_t limit_seconds)
{
	GDir *gdir;
	const gchar *dir;

	gdir = g_dir_open(path, 0, NULL);

	if (!gdir) return;

	while ((dir = g_dir_read_name(gdir)))
	{
		gchar *dirname;
		dirname = g_build_filename(path, dir, NULL);

		if (g_file_test(dirname, G_FILE_TEST_IS_DIR))
		{
			purge_history_file(dirname, limit_seconds);
		}
		else
		{
			struct stat st;
			time_t t;
			GTimeVal now;

			g_get_current_time(&now);
			t = (time_t)now.tv_sec;

			if (g_stat(dirname, &st) == 0 &&
			    st.st_mtime < t - limit_seconds)
				g_unlink(dirname);
		}
		g_free(dirname);
	}
	g_dir_close(gdir);
}

void
purge_history_file_by_timestamp(const gchar *timestamp_path, time_t limit_seconds)
{
	gchar buf[BUFFER_SIZE];
	gchar *tmp_file;
	gint fd;
	gint i;
	FILE *fp;
	GTimeVal now;
	time_t t;
	KzSearch *search;
	search = kz_search_new("hyperestraier");

	g_get_current_time(&now);
	t = now.tv_sec;

	fd = g_file_open_tmp("kzXXXXXX", &tmp_file, NULL);

	fp = g_fopen(timestamp_path, "a+");
	if(fp)
	{
		while(fgets(buf, sizeof(buf), fp))
		{
			gchar *pos;
			gchar *time , *path;
			gint time_int;
			struct stat st;

			pos = strstr(buf, ",");
			time = g_strndup(buf, pos - buf);
			path = g_strdup(pos+1);

			for(i = 0;i <= strlen(path);i++)
				if(path[i] == '\n')
					path[i] = '\0';

			time_int = atoi(time);

			if(t - time_int > limit_seconds)
			{
				if (g_stat(path, &st) == 0 &&
						t - st.st_mtime > limit_seconds)
				{
					if (search)
					{
						gchar *filename;
						filename = g_strdup_printf("file://%s", path);
						kz_search_unregister_document(search, filename);
						g_free(filename);
					}
					/* remove file over storage period */
					g_unlink(path);
					g_free(time);
					g_free(path);
					continue;
				}
			}

			write(fd, buf, strlen(buf));

			g_free(time);
			g_free(path);
		}
		close(fd);
		fclose(fp);
	}

	if (search)
		g_object_unref(search);
	g_unlink(timestamp_path);
	g_rename(tmp_file, timestamp_path);
}

gchar *
ensure_encode_string(const gchar *text, 
		     const gchar *encode,
		     gboolean urlencode)
{
	GError *e = NULL;
	gchar *encode_string = NULL, *url_string = NULL;

	if (!text) 
	{
		encode_string = g_strdup("");
		return encode_string;
	}

	if (encode)
	{
		encode_string = 
			g_convert(text,
				  strlen(text),
				  encode, "UTF-8",
				  NULL, NULL,
				  &e);
	}

	if (e || !encode)
	{
		encode_string =
		    g_strdup(text);
		if (e)
			g_error_free(e);
	}

	if (urlencode)	
	{
		url_string = url_encode(encode_string);
	}
	else
	{
		url_string = g_strdup(encode_string);
	}

	g_free(encode_string);
	
	return url_string;
}

GTime 
thumbnail_get_last_modified (const gchar *uri)
{
	gchar *thumb_filename;
	struct stat st;
	int ret;
	
	thumb_filename = egg_pixbuf_get_thumb_filename(uri,
						       EGG_PIXBUF_THUMB_LARGE);
	
	ret = g_stat(thumb_filename, &st);
	g_free(thumb_filename);

	if (ret == 0)
		return st.st_mtime;
	else
		return 0;
}

GTime 
history_get_last_modified (const gchar *uri)
{
	gchar *history_filename, *filename;
	struct stat st;
	int ret;
	
	filename = create_filename_with_path_from_uri(uri);
	history_filename = g_build_filename(g_get_home_dir(),
					    HISTORY_DIR,
					    filename,
					    NULL);
	
	ret = g_stat(history_filename, &st);
	g_free(history_filename);
	g_free(filename);

	if (ret == 0)
		return st.st_mtime;
	else
		return 0;
}

typedef struct _KzPixbufConverter
{
	GdkPixbufLoader *loader;
	gchar *filename;
} KzPixbufConverter;


#ifndef MOZ_NSICANVASRENDERINGCONTEXTINTERNAL_HAVE_GETINPUTSTREAM_
static gboolean
idle_dispose_converter(gpointer data)
{
	KzPixbufConverter *converter = data;

	g_free(converter->filename);
	g_free(converter);

	return FALSE;
}

static gboolean
cb_io_in(GIOChannel *iochannel, GIOCondition condition,
	 gpointer data)
{
	GIOStatus iostatus;
	gsize bytes_read;
	gchar buffer[BUFFER_SIZE];
	KzPixbufConverter *converter = data;
	
	if (condition & G_IO_ERR)
	{
		g_idle_add(idle_dispose_converter, converter);
		return FALSE;
	}

	/* Read the data into our buffer */
	iostatus = g_io_channel_read_chars(iochannel, buffer, 
					   sizeof(buffer),
					   &bytes_read,
					   NULL);

	if (bytes_read)
	{
		gdk_pixbuf_loader_write(converter->loader,
					(const guchar*)buffer, bytes_read,
					NULL);
	}

	switch (iostatus)
	{
	 case G_IO_STATUS_EOF:
	 {	
		GdkPixbuf *pixbuf;
		pixbuf = gdk_pixbuf_loader_get_pixbuf(converter->loader);
		if (pixbuf)
		{
			GdkPixbuf *rotate, *scale;
		
			/* scale */
			scale = gdk_pixbuf_scale_simple(pixbuf, 
						        KZ_THUMB_HEIGHT, EGG_PIXBUF_THUMB_LARGE,
						        GDK_INTERP_HYPER);
		
			/* rotate image */
			rotate = gdk_pixbuf_rotate_simple(scale,
        						  GDK_PIXBUF_ROTATE_CLOCKWISE);
			g_object_unref(scale);
			/* save image */
			if (rotate)
			{
				gchar *png_file;
				png_file = g_strndup(converter->filename,
						     strlen(converter->filename) - 3);
				gdk_pixbuf_save(rotate, 
						png_file, "png", NULL, NULL);
				g_object_unref(rotate);
				g_free(png_file);
			}
		}
		gdk_pixbuf_loader_close(converter->loader, NULL);	
		g_unlink(converter->filename);
		g_idle_add(idle_dispose_converter, converter);
		
		return FALSE;
  	 }
	 case G_IO_STATUS_NORMAL:
		return TRUE;
	 default:
		g_idle_add(idle_dispose_converter, converter);
		return FALSE;
	}
}


void
convert_image (const gchar *ps_file, const gchar *png_file)
{
	const gchar *gs = "gs -q -dNOPAUSE -dBATCH -r24 -sDEVICE=ppmraw -sOutputFile=-  %s";
	gchar *command;
	gint argc, out;
	gchar **argv = NULL;
	GSpawnFlags flags;
	GPid pid;
	GIOChannel *io;
	GdkPixbufLoader *loader;
	KzPixbufConverter *converter;

	command = g_strdup_printf(gs, ps_file);	

	g_shell_parse_argv(command,
			   &argc,
			   &argv,
			   NULL);

        /* Check whether ghostscript is installed or not */
	if(g_find_program_in_path (argv[0]) == NULL)
	{
		g_warning("Can't find %s in your PATH.\n"
			  "Currently kazehakase using gecko engine needs ghostscript to create thumbnails.\n", argv[0]);
		return;
	}

	flags = G_SPAWN_SEARCH_PATH;
	g_spawn_async_with_pipes(NULL,
		      		 argv,
				 NULL,
		   		 flags,
		      		 NULL,
		      		 NULL,
		      		 &pid,
				 NULL,
				 &out,
				 NULL,
		      		 NULL);
	g_strfreev(argv);
	g_free(command);

	io = g_io_channel_unix_new(out);
	g_io_channel_set_encoding(io, NULL, NULL);

	loader = gdk_pixbuf_loader_new_with_type("pnm", NULL);
	converter = g_new0(KzPixbufConverter, 1);
	converter->loader = loader;
	converter->filename = g_strdup(ps_file);
	
	g_io_add_watch(io,
		       G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP,
		       cb_io_in, converter);
}
#endif

gboolean
str_isdigit (const gchar *str)
{
	int i;
	size_t len;
	gboolean is_digit = TRUE;

	if (!str) return FALSE;

	len = strlen(str);
	for (i = 0; i < len; i++)
	{
		if (!isdigit(str[i]))
		{
			is_digit = FALSE;
			break;
		}
	}
	return is_digit;
}

/* the following tow functions are picked from utils/gul-general.c in galeon-1.3.18 */
static void
find_file_recursive (const gchar *path,
		     const gchar *fname,
		     GSList **l,
		     gint depth,
		     gint maxdepth)
{
	GDir *d = g_dir_open (path, 0, NULL);
	const gchar *f;
	if (d)
	{
		while ((f = g_dir_read_name (d)))
		{
			gchar *new_path = g_build_filename (path, f, NULL);
			if (depth < maxdepth)
			{
				find_file_recursive (new_path, fname, l,
							 depth + 1, maxdepth);
			}
			if (!strcmp (f, fname))
			{
				*l = g_slist_prepend (*l, new_path);
			}
			else
			{
				g_free (new_path);
			}
		}
		g_dir_close (d);
	}
}

/*
 * Looks for a file in a directory, recursively.
 */
GSList *
find_file (const gchar *path, 
	   const gchar *fname,
	   gint maxdepth)
{
	GSList *ret = NULL;
	find_file_recursive (path, fname, &ret, 0, maxdepth);
	return ret;
}
