/*  filter.c
*
*   filters out certain filenames...
*
*   (c) 2001 Tim-Philipp Muller
*
*/

/***************************************************************************
 *                                                                         *
 *   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 "misc.h"
#include "options.h"
#include "status_page.h"
#include "filter.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>


/* global variables */

GSList              *filters = NULL;

/* local variables */

static time_t        last_filterlistfile_modtime = 0;
static const gchar  *filter_delimitors  = "_ ()[]{}.,!*&^%$£-=+#@'<>|/\\;:\"0123456789";


/* functions */

static const gchar    *get_filterlist_filename (void);

static gboolean        as_word_in_string (const gchar *s, const gchar *w);

static void            check_if_filterlistfile_changed (void);

static void            free_filters (void);

static void            read_filters (void);


/******************************************************************************
 *
 *   get_filterlist_filename
 *
 ******************************************************************************/

static const gchar *
get_filterlist_filename (void)
{
	return opt_get_opt_filename_without_instance("gui_filters");
}


/******************************************************************************
 *
 *   filter_this_filename
 *
 *   returns TRUE if the given filename should be filtered out according
 *   to our current filter list
 *
 *   TODO: make end-of-string also a delimitor ??
 *   TODO: what about locale/utf8 and all that?
 *
 ***/

gboolean
filter_this_filename (const gchar *fn_locale)
{
	static gboolean initialised; /* FALSE */

	GSList   *node;
	gchar    *fnlc, *pos;

	g_return_val_if_fail ( fn_locale != NULL, FALSE );

	if (!initialised)
	{
		read_filters();
		g_atexit(&free_filters);
		initialised = TRUE;
	}
	else
	{
		static time_t  last_fl_check;  /* 0 */
		time_t         now;

		now = time(NULL);

		if (last_fl_check == 0 || (now-last_fl_check) > 10)
		{
			check_if_filterlistfile_changed();
			last_fl_check = now;
		}
	}

	/* copy filename and make it lower case
	 *
	 * NOTE: we are using tolower() here, because it
	 *       uses the current locale (as opposed to
	 *       the g_ functions
	 */
	fnlc = g_strdup(fn_locale);
	for ( pos = fnlc; *pos != 0x00; pos++ )
		*pos = tolower(*pos);

	if (as_word_in_string(fnlc, "preteen"))
	{
		g_free(fnlc);
		return TRUE;
	}

	if ( (as_word_in_string(fnlc, "rape")) || (as_word_in_string(fnlc, "raped")) )
	{
		g_free(fnlc);
		return TRUE;
	}

	/* now check for custom filters from 'gui_filters' file */

	if (!filters)
	{
		g_free(fnlc);
		return FALSE;
	}

	for ( node = filters; node != NULL; node = node->next )
	{
		gchar *badword = (gchar*) node->data; /* this should already be lower-case! */

		/* filter if substring matches word */
		if ( badword[0] == '*' )
		{
			if (strstr(fnlc, badword+1))
			{
				g_free(fnlc);
				return TRUE;
			}
		}
		else /* else filter if in there as separate word */
		{
			if (as_word_in_string(fnlc, badword))
			{
				g_free(fnlc);
				return TRUE;
			}
		}
	}

	g_free(fnlc);

	return FALSE;
}


/******************************************************************************
 *
 *  as_word_in_string
 *
 *  checks if string w is in s as word, ie. surrounded by any combination of
 *  word delimiters, so that if you want to filter out "rape", you don't filter
 *  out "grapes"...
 *
 *  note: this is case-sensitive!
 *
 *  TODO: this can be optimised a fair bit
 *
 ***/

static gboolean
as_word_in_string (const gchar *s, const gchar *w)
{
	gint    i, j, wlen, delimlen;
	gchar  *ww;

	g_return_val_if_fail ( s != NULL, FALSE );
	g_return_val_if_fail ( w != NULL, FALSE );

	wlen     = strlen(w);
	delimlen = strlen(filter_delimitors);

	ww = g_new0(gchar, wlen+3);

	strcpy (ww+1, w); /* we _know_ the string will fit in there */

	for ( i=0; i < delimlen; i++ )
	{
		for ( j=0; j < delimlen; j++ )
		{
			ww[0     ] = filter_delimitors[j];
			ww[1+wlen] = filter_delimitors[i];

			if (strstr (s,ww))
			{
				g_free(ww);
				return TRUE;
			}
		}

		/* word beginning as left delimitor... */
		if (strncmp(s, ww+1, strlen(ww)-1)==0)
		{
			g_free(ww);
			return TRUE;
		}
	}

	g_free(ww);

	return FALSE;
}


/******************************************************************************
 *
 *   read_filters
 *
 ******************************************************************************/

static void
read_filters (void)
{
	gchar       **line, **linearray;

	free_filters();

	linearray = misc_get_array_of_lines_from_textfile (get_filterlist_filename(), FALSE, FALSE);

	if (!linearray)
		return;

	for ( line = linearray;  *line != NULL;  line++ )
	{
		gchar *thisline, *pos;

		thisline = g_strchug(*line);	/* remove leading whitespaces */

		if ( *thisline == '#' || *thisline == 0x00 )
			continue;

		if ( *thisline == '*' && thisline[1] == 0x00 )
			continue;

		/* convert to lower case. Use libc funcs, because g_ funcs
		 * do not use locale, but only 'raw' ascii */
		for ( pos = thisline; *pos != 0x00; pos++ )
			*pos = tolower (*pos);

		filters = g_slist_append( filters, g_strdup(thisline) );
	}

	g_strfreev(linearray);

	last_filterlistfile_modtime = time(NULL);

}


/******************************************************************************
 *
 *   write_filters
 *
 ******************************************************************************/

#if 0
static void
write_filters (void)
{
	/* TODO: together with a list for that */
	;
}
#endif

/******************************************************************************
 *
 *  free_filters
 *
 ***/

static void
free_filters (void)
{
	GSList *node;

	for ( node = filters; node != NULL; node = node->next )
		G_FREE(node->data);

	g_slist_free (filters);

	filters=NULL;
}



/******************************************************************************
 *
 *   check_if_filterlistfile_changed
 *
 ******************************************************************************/

static void
check_if_filterlistfile_changed (void)
{
	struct stat flstat;

	if ( stat(get_filterlist_filename(), &flstat) == 0 )
	{
		if ( flstat.st_mtime > last_filterlistfile_modtime )
		{
			status_msg (_("GUI: filterlist file has changed - rereading it.\n"));

			read_filters();
		}
	} /* else: error or file doesn't exist */
}



