/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
/* Copyright (C) 1998 Redhat Software Inc. 
 * Authors: Jonathan Blandford <jrb@redhat.com>
 */
#include <config.h>
#include "capplet-widget.h"
#include "gnome.h"
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <regex.h>
#include <ctype.h>
#include "mime-info.h"
#include "mime-data.h"
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

#if !defined getc_unlocked && !defined HAVE_GETC_UNLOCKED
# define getc_unlocked(fp) getc (fp)
#endif

typedef struct {
	char       *mime_type;
	GHashTable *keys;
} GnomeMimeContext;

typedef enum {
	STATE_NONE,
	STATE_LANG,
	STATE_LOOKING_FOR_KEY,
	STATE_ON_MIME_TYPE,
	STATE_ON_KEY,
	STATE_ON_VALUE
} ParserState;

static char *current_lang = NULL;

/*
 * A hash table containing all of the Mime records for specific
 * mime types (full description, like image/png)
 */
static GHashTable *specific_types;
static GHashTable *initial_specific_types;

/*
 * A hash table containing all of the Mime records for non-specific
 * mime types (like image/\*)
 */
static GHashTable *generic_types;
static GHashTable *initial_generic_types;

#define SWITCH_TO_MIME_TYPE() { 
static GnomeMimeContext *
context_new (GString *str, gboolean is_default_context)
{
	GnomeMimeContext *context;
	GHashTable *table;
	char *mime_type, *p;
	
	mime_type = g_strdup (str->str);
	
	if (is_default_context) {
		if ((p = strstr (mime_type, "/*")) == NULL){
			table = initial_specific_types;
		} else {
			*(p+1) = 0;
			table = initial_generic_types;
		}
	} else {
		if ((p = strstr (mime_type, "/*")) == NULL){
			table = specific_types;
		} else {
			*(p+1) = 0;
			table = generic_types;
		}
	}
	context = g_hash_table_lookup (table, mime_type);

	if (context)
		return context;
	
	context = g_new (GnomeMimeContext, 1);
	context->mime_type = mime_type;
	context->keys = g_hash_table_new (g_str_hash, g_str_equal);

	g_hash_table_insert (table, context->mime_type, context);
	return context;
}

static gboolean
release_key_and_value (gpointer key, gpointer value, gpointer user_data)
{
	g_free (key);
	g_free (value);

	return TRUE;
}


static gboolean
remove_this_key (gpointer key, gpointer value, gpointer user_data)
{
	if (strcmp ((gchar *)key, (gchar *)user_data) == 0){
		g_free (key);
		g_free (value);
		return TRUE;
	}

	return FALSE;
}
static void
context_add_key (GnomeMimeContext *context, char *key, char *value)
{
	char *v;

	v = g_hash_table_lookup (context->keys, key);
	if (v)
		g_hash_table_foreach_remove (context->keys, remove_this_key, key);

	g_hash_table_insert (context->keys, g_strdup (key), g_strdup (value));
}
static void
context_destroy (GnomeMimeContext *context)
{
	/*
	 * Remove the context from our hash tables, we dont know
	 * where it is: so just remove it from both (it can
	 * only be in one).
	 */
	if (context->mime_type) {
		g_hash_table_remove (specific_types, context->mime_type);
		g_hash_table_remove (generic_types, context->mime_type);
	}
	/*
	 * Destroy it
	 */
	if (context->keys) {
		g_hash_table_foreach_remove (context->keys, release_key_and_value, NULL);
		g_hash_table_destroy (context->keys);
	}
	g_free (context->mime_type);
	g_free (context);
}

static void
load_mime_type_info_from (char *filename)
{
	FILE *mime_file;
	gboolean in_comment, context_used;
	GString *line;
	int column, c;
	ParserState state;
	GnomeMimeContext *context, *default_context;
	char *key;

	mime_file = fopen (filename, "r");
	if (mime_file == NULL)
		return;

	in_comment = FALSE;
	context_used = FALSE;
	column = 0;
	context = NULL;
	default_context = NULL;
	key = NULL;
	line = g_string_sized_new (120);
	state = STATE_NONE;
	
	while ((c = getc_unlocked (mime_file)) != EOF){
		column++;
		if (c == '\r')
			continue;

		if (c == '#' && column == 0){
			in_comment = TRUE;
			continue;
		}
		
		if (c == '\n'){
			in_comment = FALSE;
			column = 0;
			if (state == STATE_ON_MIME_TYPE){
				context = context_new (line, FALSE);
				default_context = context_new (line, TRUE);
				context_used = FALSE;
				g_string_assign (line, "");
				state = STATE_LOOKING_FOR_KEY;
				continue;
			}
			if (state == STATE_ON_VALUE){
				context_used = TRUE;
				context_add_key (context, key, line->str);
				context_add_key (default_context, key, line->str);
				g_string_assign (line, "");
				g_free (key);
				key = NULL;
				state = STATE_LOOKING_FOR_KEY;
				continue;
			}
			continue;
		}

		if (in_comment)
			continue;

		switch (state){
		case STATE_NONE:
			if (c != ' ' && c != '\t')
				state = STATE_ON_MIME_TYPE;
			else
				break;
			/* fall down */
			
		case STATE_ON_MIME_TYPE:
			if (c == ':'){
				in_comment = TRUE;
				break;
			}
			g_string_append_c (line, c);
			break;

		case STATE_LOOKING_FOR_KEY:
			if (c == '\t' || c == ' ')
				break;

			if (c == '['){
				state = STATE_LANG;
				break;
			}

			if (column == 1){
				state = STATE_ON_MIME_TYPE;
				g_string_append_c (line, c);
				break;
			}
			state = STATE_ON_KEY;
			/* falldown */

		case STATE_ON_KEY:
			if (c == '\\'){
				c = getc (mime_file);
				if (c == EOF)
					break;
			}
			if (c == '='){
				key = g_strdup (line->str);
				g_string_assign (line, "");
				state = STATE_ON_VALUE;
				break;
			}
			g_string_append_c (line, c);
			break;

		case STATE_ON_VALUE:
			g_string_append_c (line, c);
			break;
			
		case STATE_LANG:
			if (c == ']'){
				state = STATE_ON_KEY;      
				if (current_lang && line->str [0]){
					if (strcmp (current_lang, line->str) != 0){
						in_comment = TRUE;
						state = STATE_LOOKING_FOR_KEY;
					}
				} else {
					in_comment = TRUE;
					state = STATE_LOOKING_FOR_KEY;
				}
				g_string_assign (line, "");
				break;
			}
			g_string_append_c (line, c);
			break;
		}
	}

	if (context){
		if (key && line->str [0]) {
			context_add_key (context, key, line->str);
			context_add_key (default_context, key, line->str);
		} else 
			if (!context_used) {
				context_destroy (context);
				context_destroy (default_context);
			}
		
	}

	g_string_free (line, TRUE);
	if (key)
		g_free (key);

	fclose (mime_file);
}
void
set_mime_key_value (gchar *mime_type, gchar *key, gchar *value)
{
	GnomeMimeContext *context;

	/* Assume no generic context's for now. */
	context = g_hash_table_lookup (specific_types, mime_type);
	if (context == NULL) {
		GString *str = g_string_new (mime_type);
		context = context_new (str, FALSE);
		g_string_free (str, TRUE);
	}
	context_add_key (context, key, value);
}
void
init_mime_info (void)
{
	gchar *filename;
	
	current_lang = getenv ("LANG");
	specific_types = g_hash_table_new (g_str_hash, g_str_equal);
	generic_types  = g_hash_table_new (g_str_hash, g_str_equal);
	initial_specific_types = g_hash_table_new (g_str_hash, g_str_equal);
	initial_generic_types  = g_hash_table_new (g_str_hash, g_str_equal);

	filename = g_concat_dir_and_file (gnome_util_user_home (), "/.gnome/mime-info/user.keys");
	load_mime_type_info_from (filename);
	g_free (filename);
}

const char *
local_mime_get_value (const char *mime_type, char *key)
{
	char *value, *generic_type, *p;
	GnomeMimeContext *context;
	
	g_return_val_if_fail (mime_type != NULL, NULL);
	g_return_val_if_fail (key != NULL, NULL);
	context = g_hash_table_lookup (specific_types, mime_type);
	if (context){
		value = g_hash_table_lookup (context->keys, key);

		if (value)
			return value;
	}

	generic_type = g_strdup (mime_type);
	p = strchr (generic_type, '/');
	if (p)
		*(p+1) = 0;
	
	context = g_hash_table_lookup (generic_types, generic_type);
	g_free (generic_type);
	
	if (context){
		value = g_hash_table_lookup (context->keys, key);
		if (value)
			return value;
	}
	return NULL;
}
static void
clean_mime_foreach (gpointer mime_type, gpointer gmc, gpointer data)
{
	context_destroy ((GnomeMimeContext *) gmc);
}
static void
write_mime_keys_foreach (gpointer key_name, gpointer value, gpointer data)
{
	gchar *buf;
	if (current_lang && strcmp (current_lang, "C"))
		buf = g_strconcat ("\t[",
				   current_lang,
				   "]",
				   (gchar *) key_name,
				   "=",
				   (gchar *) value,
				   "\n", NULL);
	else
		buf = g_strconcat ("\t",
				   (gchar *) key_name,
				   "=",
				   (gchar *) value,
				   "\n", NULL);
	fwrite (buf, 1, strlen (buf), (FILE *) data);
	g_free (buf);
}
static void
write_mime_foreach (gpointer mime_type, gpointer gmc, gpointer data)
{
	gchar *buf;
	GnomeMimeContext *context = (GnomeMimeContext *) gmc;

	buf = g_strconcat ((gchar *) mime_type, ":\n", NULL);
	fwrite (buf, 1, strlen (buf), (FILE *) data);
	g_free (buf);
	g_hash_table_foreach (context->keys, write_mime_keys_foreach, data);
	fwrite ("\n", 1, strlen ("\n"), (FILE *) data);
}

static void
run_error (gchar *message)
{
	GtkWidget *error_box;

	error_box = gnome_message_box_new (
		message,
		GNOME_MESSAGE_BOX_ERROR,
		GNOME_STOCK_BUTTON_OK,
		NULL);
	gnome_dialog_run_and_close (GNOME_DIALOG (error_box));
}
static void
write_keys (GHashTable *spec_hash, GHashTable *generic_hash)
{
	struct stat s;
	gchar *dirname, *filename;
	FILE *file;
	GtkWidget *error_box;

	dirname = g_concat_dir_and_file (gnome_util_user_home (), ".gnome/mime-info");
	if ((stat (dirname, &s) < 0) || !(S_ISDIR (s.st_mode))){
		if (errno == ENOENT) {
			if (mkdir (dirname, S_IRWXU) < 0) {
				run_error ("We are unable to create the directory\n"
					   "~/.gnome/mime-info\n\n"
					   "We will not be able to save the state.");
				return;
			}
		} else {
			run_error ("We are unable to access the directory\n"
				   "~/.gnome/mime-info\n\n"
				   "We will not be able to save the state.");
			return;
		}
	}
	filename = g_concat_dir_and_file (dirname, "user.keys");
        
        remove (filename);
	file = fopen (filename, "w");
	if (file == NULL) {
		run_error (_("Cannot create the file\n~/.gnome/mime-info/user.keys\n\n"
			     "We will not be able to save the state"));
		return;
	}
	g_hash_table_foreach (spec_hash, write_mime_foreach, file);
	g_hash_table_foreach (generic_hash, write_mime_foreach, file);
	fclose (file);
}
void
write_initial_keys (void)
{
	write_keys (initial_generic_types, initial_specific_types);
}
void
write_user_keys (void)
{
	write_keys (generic_types, specific_types);
}
static void
print_mime_foreach (gpointer mime_info, gpointer mi, gpointer data)
{
        g_print ("mime_info:%s:\n", (char *)mime_info);
        g_print ("\t:%s:\n", ((MimeInfo *)mi)->mime_type);
}
void
discard_key_info (void)
{
	gchar *filename;
	
	current_lang = getenv ("LANG");
	g_hash_table_foreach (generic_types, clean_mime_foreach, NULL);
/*        g_hash_table_foreach (specific_types, print_mime_foreach, NULL);*/
	g_hash_table_foreach (specific_types, clean_mime_foreach, NULL);
	g_hash_table_destroy (generic_types);
	g_hash_table_destroy (specific_types);
	specific_types = g_hash_table_new (g_str_hash, g_str_equal);
	generic_types  = g_hash_table_new (g_str_hash, g_str_equal);

	filename = g_concat_dir_and_file (gnome_util_user_home (), "/.gnome/mime-info/user.keys");
	load_mime_type_info_from (filename);
        reread_list ();
	g_free (filename);
}
void
remove_mime_info (gchar *mime_type)
{
        g_hash_table_remove (generic_types, mime_type);
        g_hash_table_remove (specific_types, mime_type);
}
