  /********************************************************************\
  * BitlBee -- An IRC to other IM-networks gateway                     *
  *                                                                    *
  * Copyright 2002-2013 Wilmer van der Gaast and others                *
  \********************************************************************/

/* Some stuff to register, handle and save user preferences             */

/*
  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 with
  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
  Suite 330, Boston, MA  02111-1307  USA
*/

#define BITLBEE_CORE
#include "bitlbee.h"

/* Used to use NULL for this, but NULL is actually a "valid" value. */
char *SET_INVALID = "nee";

set_t *set_add( set_t **head, const char *key, const char *def, set_eval eval, void *data )
{
	set_t *s = set_find( head, key );
	
	/* Possibly the setting already exists. If it doesn't exist yet,
	   we create it. If it does, we'll just change the default. */
	if( !s )
	{
		if( ( s = *head ) )
		{
			/* Sorted insertion. Special-case insertion at the start. */
			if( strcmp( key, s->key ) < 0 )
			{
				s = g_new0( set_t, 1 );
				s->next = *head;
				*head = s;
			}
			else
			{
				while( s->next && strcmp( key, s->next->key ) > 0 )
					s = s->next;
				set_t *last_next = s->next;
				s->next = g_new0( set_t, 1 );
				s = s->next;
				s->next = last_next;
			}
		}
		else
		{
			s = *head = g_new0( set_t, 1 );
		}
		s->key = g_strdup( key );
	}
	
	if( s->def )
	{
		g_free( s->def );
		s->def = NULL;
	}
	if( def ) s->def = g_strdup( def );
	
	s->eval = eval;
	s->data = data;
	
	return s;
}

set_t *set_find( set_t **head, const char *key )
{
	set_t *s = *head;
	
	while( s )
	{
		if( g_strcasecmp( s->key, key ) == 0 ||
		    ( s->old_key && g_strcasecmp( s->old_key, key ) == 0 ) )
			break;
		s = s->next;
	}
	
	return s;
}

char *set_getstr( set_t **head, const char *key )
{
	set_t *s = set_find( head, key );
	
	if( !s || ( !s->value && !s->def ) )
		return NULL;
	
	return set_value( s );
}

int set_getint( set_t **head, const char *key )
{
	char *s = set_getstr( head, key );
	int i = 0;
	
	if( !s )
		return 0;
	
	if( sscanf( s, "%d", &i ) != 1 )
		return 0;
	
	return i;
}

int set_getbool( set_t **head, const char *key )
{
	char *s = set_getstr( head, key );
	
	if( !s )
		return 0;
	
	return bool2int( s );
}

int set_isvisible( set_t *set )
{
	/* the default value is not stored in value, only in def */
	return !( ( set->flags & SET_HIDDEN ) ||
	          ( ( set->flags & SET_HIDDEN_DEFAULT ) &&
	            ( set->value == NULL ) ) );
}

int set_setstr( set_t **head, const char *key, char *value )
{
	set_t *s = set_find( head, key );
	char *nv = value;
	
	if( !s )
		/*
		Used to do this, but it never really made sense.
		s = set_add( head, key, NULL, NULL, NULL );
		*/
		return 0;
	
	if( value == NULL && ( s->flags & SET_NULL_OK ) == 0 )
		return 0;
	
	/* Call the evaluator. For invalid values, evaluators should now
	   return SET_INVALID, but previously this was NULL. Try to handle
	   that too if NULL is not an allowed value for this setting. */
	if( s->eval && ( ( nv = s->eval( s, value ) ) == SET_INVALID ||
	                 ( ( s->flags & SET_NULL_OK ) == 0 && nv == NULL ) ) )
		return 0;
	
	if( s->value )
	{
		g_free( s->value );
		s->value = NULL;
	}
	
	/* If there's a default setting and it's equal to what we're trying to
	   set, stick with s->value = NULL. Otherwise, remember the setting. */
	if( !s->def || ( strcmp( nv, s->def ) != 0 ) )
		s->value = g_strdup( nv );
	
	if( nv != value )
		g_free( nv );
	
	return 1;
}

int set_setint( set_t **head, const char *key, int value )
{
	char s[24];	/* Not quite 128-bit clean eh? ;-) */
	
	g_snprintf( s, sizeof( s ), "%d", value );
	return set_setstr( head, key, s );
}

void set_del( set_t **head, const char *key )
{
	set_t *s = *head, *t = NULL;
	
	while( s )
	{
		if( g_strcasecmp( s->key, key ) == 0 )
			break;
		s = (t=s)->next;
	}
	if( s )
	{
		if( t )
			t->next = s->next;
		else
			*head = s->next;
		
		g_free( s->key );
		g_free( s->old_key );
		g_free( s->value );
		g_free( s->def );
		g_free( s );
	}
}

int set_reset( set_t **head, const char *key )
{
	set_t *s;
	
	s = set_find( head, key );
	if( s )
		return set_setstr( head, key, s->def );
	
	return 0;
}

char *set_eval_int( set_t *set, char *value )
{
	char *s = value;
	
	/* Allow a minus at the first position. */
	if( *s == '-' )
		s ++;
	
	for( ; *s; s ++ )
		if( !isdigit( *s ) )
			return SET_INVALID;
	
	return value;
}

char *set_eval_bool( set_t *set, char *value )
{
	return is_bool( value ) ? value : SET_INVALID;
}

char *set_eval_list( set_t *set, char *value )
{
	GSList *options = set->eval_data, *opt;
	
	for( opt = options; opt; opt = opt->next )
		if( strcmp( value, opt->data ) == 0 )
			return value;
	
	/* TODO: It'd be nice to show the user a list of allowed values,
	         but we don't have enough context here to do that. May
	         want to fix that. */
	
	return NULL;
}

char *set_eval_to_char( set_t *set, char *value )
{
	char *s = g_new( char, 3 );
	
	if( *value == ' ' )
		strcpy( s, " " );
	else
		sprintf( s, "%c ", *value );
	
	return s;
}

char *set_eval_oauth( set_t *set, char *value )
{
	account_t *acc = set->data;
	
	if( bool2int( value ) && strcmp( acc->pass, PASSWORD_PENDING ) == 0 )
		*acc->pass = '\0';
	
	return set_eval_bool( set, value );
}
