/*****************************************************************************
 * configuration.c management of the modules configuration
 *****************************************************************************
 * Copyright (C) 2001 VideoLAN
 * $Id: configuration.c,v 1.6 2002/03/17 11:12:08 gbazin Exp $
 *
 * Authors: Gildas Bazin <gbazin@netcourrier.com>
 *
 * 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, USA.
 *****************************************************************************/

#include <stdio.h>                                              /* sprintf() */
#include <stdlib.h>                                      /* free(), strtol() */
#include <string.h>                                              /* strdup() */
#include <unistd.h>                                              /* getuid() */

#include <videolan/vlc.h>

#if defined(HAVE_GETPWUID) || defined(HAVE_GETPWUID_R)
#include <pwd.h>                                               /* getpwuid() */
#endif

#include <sys/stat.h>
#include <sys/types.h>

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static char *GetHomeDir( void );

/*****************************************************************************
 * config_GetIntVariable: get the value of an int variable
 *****************************************************************************
 * This function is used to get the value of variables which are internally
 * represented by an integer (MODULE_CONFIG_ITEM_INTEGER and
 * MODULE_CONFIG_ITEM_BOOL).
 *****************************************************************************/
int config_GetIntVariable( const char *psz_name )
{
    module_config_t *p_config;

    p_config = config_FindConfig( psz_name );

    /* sanity checks */
    if( !p_config )
    {
        intf_ErrMsg( "config error: option %s doesn't exist", psz_name );
        return -1;
    }
    if( (p_config->i_type!=MODULE_CONFIG_ITEM_INTEGER) &&
        (p_config->i_type!=MODULE_CONFIG_ITEM_BOOL) )
    {
        intf_ErrMsg( "config error: option %s doesn't refer to an int",
                     psz_name );
        return -1;
    }

    return p_config->i_value;
}

/*****************************************************************************
 * config_GetPszVariable: get the string value of a string variable
 *****************************************************************************
 * This function is used to get the value of variables which are internally
 * represented by a string (MODULE_CONFIG_ITEM_STRING, MODULE_CONFIG_ITEM_FILE,
 * and MODULE_CONFIG_ITEM_PLUGIN).
 *
 * Important note: remember to free() the returned char* because it a duplicate
 *   of the actual value. It isn't safe to return a pointer to the actual value
 *   as it can be modified at any time.
 *****************************************************************************/
char * config_GetPszVariable( const char *psz_name )
{
    module_config_t *p_config;
    char *psz_value = NULL;

    p_config = config_FindConfig( psz_name );

    /* sanity checks */
    if( !p_config )
    {
        intf_ErrMsg( "config error: option %s doesn't exist", psz_name );
        return NULL;
    }
    if( (p_config->i_type!=MODULE_CONFIG_ITEM_STRING) &&
        (p_config->i_type!=MODULE_CONFIG_ITEM_FILE) &&
        (p_config->i_type!=MODULE_CONFIG_ITEM_PLUGIN) )
    {
        intf_ErrMsg( "config error: option %s doesn't refer to a string",
                     psz_name );
        return NULL;
    }

    /* return a copy of the string */
    vlc_mutex_lock( p_config->p_lock );
    if( p_config->psz_value ) psz_value = strdup( p_config->psz_value );
    vlc_mutex_unlock( p_config->p_lock );

    return psz_value;
}

/*****************************************************************************
 * config_PutPszVariable: set the string value of a string variable
 *****************************************************************************
 * This function is used to set the value of variables which are internally
 * represented by a string (MODULE_CONFIG_ITEM_STRING, MODULE_CONFIG_ITEM_FILE,
 * and MODULE_CONFIG_ITEM_PLUGIN).
 *****************************************************************************/
void config_PutPszVariable( const char *psz_name, char *psz_value )
{
    module_config_t *p_config;

    p_config = config_FindConfig( psz_name );

    /* sanity checks */
    if( !p_config )
    {
        intf_ErrMsg( "config error: option %s doesn't exist", psz_name );
        return;
    }
    if( (p_config->i_type!=MODULE_CONFIG_ITEM_STRING) &&
        (p_config->i_type!=MODULE_CONFIG_ITEM_FILE) &&
        (p_config->i_type!=MODULE_CONFIG_ITEM_PLUGIN) )
    {
        intf_ErrMsg( "config error: option %s doesn't refer to a string",
                     psz_name );
        return;
    }

    vlc_mutex_lock( p_config->p_lock );

    /* free old string */
    if( p_config->psz_value ) free( p_config->psz_value );

    if( psz_value ) p_config->psz_value = strdup( psz_value );
    else p_config->psz_value = NULL;

    vlc_mutex_unlock( p_config->p_lock );

}

/*****************************************************************************
 * config_PutIntVariable: set the integer value of an int variable
 *****************************************************************************
 * This function is used to set the value of variables which are internally
 * represented by an integer (MODULE_CONFIG_ITEM_INTEGER and
 * MODULE_CONFIG_ITEM_BOOL).
 *****************************************************************************/
void config_PutIntVariable( const char *psz_name, int i_value )
{
    module_config_t *p_config;

    p_config = config_FindConfig( psz_name );

    /* sanity checks */
    if( !p_config )
    {
        intf_ErrMsg( "config error: option %s doesn't exist", psz_name );
        return;
    }
    if( (p_config->i_type!=MODULE_CONFIG_ITEM_INTEGER) &&
        (p_config->i_type!=MODULE_CONFIG_ITEM_BOOL) )
    {
        intf_ErrMsg( "config error: option %s doesn't refer to an int",
                     psz_name );
        return;
    }

    p_config->i_value = i_value;
}

/*****************************************************************************
 * config_FindConfig: find the config structure associated with an option.
 *****************************************************************************
 * FIXME: This function really needs to be optimized.
 *****************************************************************************/
module_config_t *config_FindConfig( const char *psz_name )
{
    module_t *p_module;
    int i;

    if( !psz_name ) return NULL;

    for( p_module = p_module_bank->first ;
         p_module != NULL ;
         p_module = p_module->next )
    {
        for( i = 0; i < p_module->i_config_lines; i++ )
        {
            if( p_module->p_config[i].i_type & MODULE_CONFIG_HINT )
                /* ignore hints */
                continue;
            if( !strcmp( psz_name, p_module->p_config[i].psz_name ) )
                return &p_module->p_config[i];
        }
    }

    return NULL;
}

/*****************************************************************************
 * config_Duplicate: creates a duplicate of a module's configuration data.
 *****************************************************************************
 * Unfortunatly we cannot work directly with the module's config data as
 * this module might be unloaded from memory at any time (remember HideModule).
 * This is why we need to create an exact copy of the config data.
 *****************************************************************************/
module_config_t *config_Duplicate( module_t *p_module )
{
    int i;
    module_config_t *p_config;

    /* allocate memory */
    p_config = (module_config_t *)malloc( sizeof(module_config_t)
                                          * p_module->i_config_lines );
    if( p_config == NULL )
    {
        intf_ErrMsg( "config error: can't duplicate p_config" );
        return( NULL );
    }

    for( i = 0; i < p_module->i_config_lines ; i++ )
    {
        p_config[i].i_type = p_module->p_config_orig[i].i_type;
        p_config[i].i_value = p_module->p_config_orig[i].i_value;
        p_config[i].b_dirty = p_module->p_config_orig[i].b_dirty;
        p_config[i].p_lock = &p_module->config_lock;
        if( p_module->p_config_orig[i].psz_name )
            p_config[i].psz_name =
                strdup( p_module->p_config_orig[i].psz_name );
        else p_config[i].psz_name = NULL;
        if( p_module->p_config_orig[i].psz_text )
            p_config[i].psz_text =
                strdup( p_module->p_config_orig[i].psz_text );
        else p_config[i].psz_text = NULL;
        if( p_module->p_config_orig[i].psz_longtext )
            p_config[i].psz_longtext =
                strdup( p_module->p_config_orig[i].psz_longtext );
        else p_config[i].psz_longtext = NULL;
        if( p_module->p_config_orig[i].psz_value )
            p_config[i].psz_value =
                strdup( p_module->p_config_orig[i].psz_value );
        else p_config[i].psz_value = NULL;

        /* the callback pointer is only valid when the module is loaded so this
         * value is set in ActivateModule() and reset in DeactivateModule() */
        p_config[i].p_callback = NULL;
    }

    return p_config;
}

/*****************************************************************************
 * config_LoadConfigFile: loads the configuration file.
 *****************************************************************************
 * This function is called to load the config options stored in the config
 * file.
 *****************************************************************************/
int config_LoadConfigFile( const char *psz_module_name )
{
    module_t *p_module;
    FILE *file;
    char line[1024];
    char *p_index, *psz_option_name, *psz_option_value;
    int i;
    char *psz_filename, *psz_homedir;

    /* Acquire config file lock */
    vlc_mutex_lock( &p_main->config_lock );

    psz_homedir = GetHomeDir();
    if( !psz_homedir )
    {
        intf_ErrMsg( "config error: GetHomeDir failed" );
        vlc_mutex_unlock( &p_main->config_lock );
        return -1;
    }
    psz_filename = (char *)malloc( strlen("/" CONFIG_DIR "/" CONFIG_FILE) +
                                   strlen(psz_homedir) + 1 );
    if( !psz_filename )
    {
        intf_ErrMsg( "config error: couldn't malloc psz_filename" );
        free( psz_homedir );
        vlc_mutex_unlock( &p_main->config_lock );
        return -1;
    }
    sprintf( psz_filename, "%s/" CONFIG_DIR "/" CONFIG_FILE, psz_homedir );
    free( psz_homedir );

    intf_WarnMsg( 5, "config: opening config file %s", psz_filename );

    file = fopen( psz_filename, "r" );
    if( !file )
    {
        intf_WarnMsg( 1, "config: couldn't open config file %s for reading",
                         psz_filename );
        free( psz_filename );
        vlc_mutex_unlock( &p_main->config_lock );
        return -1;
    }

    /* Look for the selected module, if NULL then save everything */
    for( p_module = p_module_bank->first ; p_module != NULL ;
         p_module = p_module->next )
    {

        if( psz_module_name && strcmp( psz_module_name, p_module->psz_name ) )
            continue;

        /* The config file is organized in sections, one per module. Look for
         * the interesting section ( a section is of the form [foo] ) */
        rewind( file );
        while( fgets( line, 1024, file ) )
        {
            if( (line[0] == '[') && (p_index = strchr(line,']')) &&
                (p_index - &line[1] == strlen(p_module->psz_name) ) &&
                !memcmp( &line[1], p_module->psz_name,
                         strlen(p_module->psz_name) ) )
            {
                intf_WarnMsg( 5, "config: loading config for module <%s>",
                                 p_module->psz_name );

                break;
            }
        }
        /* either we found the section or we're at the EOF */

        /* Now try to load the options in this section */
        while( fgets( line, 1024, file ) )
        {
            if( line[0] == '[' ) break; /* end of section */

            /* ignore comments or empty lines */
            if( (line[0] == '#') || (line[0] == (char)0) ) continue;

            /* get rid of line feed */
            if( line[strlen(line)-1] == '\n' )
                line[strlen(line)-1] = (char)0;

            /* look for option name */
            psz_option_name = line;
            psz_option_value = NULL;
            p_index = strchr( line, '=' );
            if( !p_index ) break; /* this ain't an option!!! */

            *p_index = (char)0;
            psz_option_value = p_index + 1;

            /* try to match this option with one of the module's options */
            for( i = 0; i < p_module->i_config_lines; i++ )
            {
                if( p_module->p_config[i].i_type & MODULE_CONFIG_HINT )
                    /* ignore hints */
                    continue;
                if( !strcmp( p_module->p_config[i].psz_name,
                             psz_option_name ) )
                {
                    /* We found it */
                    switch( p_module->p_config[i].i_type )
                    {
                    case MODULE_CONFIG_ITEM_BOOL:
                    case MODULE_CONFIG_ITEM_INTEGER:
                        p_module->p_config[i].i_value =
                            atoi( psz_option_value);
                        intf_WarnMsg( 7, "config: found <%s> option %s=%i",
                                      p_module->psz_name,
                                      p_module->p_config[i].psz_name,
                                      p_module->p_config[i].i_value );
                        break;

                    default:
                        vlc_mutex_lock( p_module->p_config[i].p_lock );

                        /* free old string */
                        if( p_module->p_config[i].psz_value )
                            free( p_module->p_config[i].psz_value );

                        p_module->p_config[i].psz_value =
                            strdup( psz_option_value );

                        vlc_mutex_unlock( p_module->p_config[i].p_lock );

                        intf_WarnMsg( 7, "config: found <%s> option %s=%s",
                                      p_module->psz_name,
                                      p_module->p_config[i].psz_name,
                                      p_module->p_config[i].psz_value );
                        break;
                    }
                }
            }
        }

    }
    
    fclose( file );
    free( psz_filename );

    vlc_mutex_unlock( &p_main->config_lock );

    return 0;
}

/*****************************************************************************
 * config_SaveConfigFile: Save a module's config options.
 *****************************************************************************
 * This will save the specified module's config options to the config file.
 * If psz_module_name is NULL then we save all the modules config options.
 * It's no use to save the config options that kept their default values, so
 * we'll try to be a bit clever here.
 *
 * When we save we mustn't delete the config options of the plugins that
 * haven't been loaded. So we cannot just create a new config file with the
 * config structures we've got in memory. 
 * I don't really know how to deal with this nicely, so I will use a completly
 * dumb method ;-)
 * I will load the config file in memory, but skipping all the sections of the
 * modules we want to save. Then I will create a brand new file, dump the file
 * loaded in memory and then append the sections of the modules we want to
 * save.
 * Really stupid no ?
 *****************************************************************************/
int config_SaveConfigFile( const char *psz_module_name )
{
    module_t *p_module;
    FILE *file;
    char p_line[1024], *p_index2;
    int i, i_sizebuf = 0;
    char *p_bigbuffer, *p_index;
    boolean_t b_backup;
    char *psz_filename, *psz_homedir;

    /* Acquire config file lock */
    vlc_mutex_lock( &p_main->config_lock );

    psz_homedir = GetHomeDir();
    if( !psz_homedir )
    {
        intf_ErrMsg( "config error: GetHomeDir failed" );
        vlc_mutex_unlock( &p_main->config_lock );
        return -1;
    }
    psz_filename = (char *)malloc( strlen("/" CONFIG_DIR "/" CONFIG_FILE) +
                                   strlen(psz_homedir) + 1 );
    if( !psz_filename )
    {
        intf_ErrMsg( "config error: couldn't malloc psz_filename" );
        free( psz_homedir );
        vlc_mutex_unlock( &p_main->config_lock );
        return -1;
    }
    sprintf( psz_filename, "%s/" CONFIG_DIR, psz_homedir );
    free( psz_homedir );
#ifndef WIN32
    mkdir( psz_filename, 0755 );
#else
    mkdir( psz_filename );
#endif
    strcat( psz_filename, "/" CONFIG_FILE );


    intf_WarnMsg( 5, "config: opening config file %s", psz_filename );

    file = fopen( psz_filename, "r" );
    if( !file )
    {
        intf_WarnMsg( 1, "config: couldn't open config file %s for reading",
                         psz_filename );
    }
    else
    {
        /* look for file size */
        fseek( file, 0, SEEK_END );
        i_sizebuf = ftell( file );
        rewind( file );
    }

    p_bigbuffer = p_index = malloc( i_sizebuf+1 );
    if( !p_bigbuffer )
    {
        intf_ErrMsg( "config error: couldn't malloc bigbuffer" );
        if( file ) fclose( file );
        free( psz_filename );
        vlc_mutex_unlock( &p_main->config_lock );
        return -1;
    }
    p_bigbuffer[0] = 0;

    /* backup file into memory, we only need to backup the sections we won't
     * save later on */
    b_backup = 0;
    while( file && fgets( p_line, 1024, file ) )
    {
        if( (p_line[0] == '[') && (p_index2 = strchr(p_line,']')))
        {
            /* we found a section, check if we need to do a backup */
            for( p_module = p_module_bank->first; p_module != NULL;
                 p_module = p_module->next )
            {
                if( ((p_index2 - &p_line[1]) == strlen(p_module->psz_name) ) &&
                    !memcmp( &p_line[1], p_module->psz_name,
                             strlen(p_module->psz_name) ) )
                {
                    if( !psz_module_name )
                        break;
                    else if( !strcmp( psz_module_name, p_module->psz_name ) )
                        break;
                }
            }

            if( !p_module )
            {
                /* we don't have this section in our list so we need to back
                 * it up */
                *p_index2 = 0;
                intf_WarnMsg( 5, "config: backing up config for "
                                 "unknown module <%s>", &p_line[1] );
                *p_index2 = ']';

                b_backup = 1;
            }
            else
            {
                b_backup = 0;
            }
        }

        /* save line if requested and line is valid (doesn't begin with a
         * space, tab, or eol) */
        if( b_backup && (p_line[0] != '\n') && (p_line[0] != ' ')
            && (p_line[0] != '\t') )
        {
            strcpy( p_index, p_line );
            p_index += strlen( p_line );
        }
    }
    if( file ) fclose( file );


    /*
     * Save module config in file
     */

    file = fopen( psz_filename, "w" );
    if( !file )
    {
        intf_WarnMsg( 1, "config: couldn't open config file %s for writing",
                         psz_filename );
        free( psz_filename );
        vlc_mutex_unlock( &p_main->config_lock );
        return -1;
    }

    fprintf( file, "###\n###  " COPYRIGHT_MESSAGE "\n###\n\n" );

    /* Look for the selected module, if NULL then save everything */
    for( p_module = p_module_bank->first ; p_module != NULL ;
         p_module = p_module->next )
    {

        if( psz_module_name && strcmp( psz_module_name, p_module->psz_name ) )
            continue;

        if( !p_module->i_config_items )
            continue;

        intf_WarnMsg( 5, "config: saving config for module <%s>",
                         p_module->psz_name );

        fprintf( file, "[%s]\n", p_module->psz_name );

        if( p_module->psz_longname )
            fprintf( file, "###\n###  %s\n###\n", p_module->psz_longname );

        for( i = 0; i < p_module->i_config_lines; i++ )
        {
            if( p_module->p_config[i].i_type & MODULE_CONFIG_HINT )
                /* ignore hints */
                continue;

            switch( p_module->p_config[i].i_type )
            {
            case MODULE_CONFIG_ITEM_BOOL:
            case MODULE_CONFIG_ITEM_INTEGER:
                if( p_module->p_config[i].psz_text )
                    fprintf( file, "# %s\n", p_module->p_config[i].psz_text );
                fprintf( file, "%s=%i\n", p_module->p_config[i].psz_name,
                         p_module->p_config[i].i_value );
                break;

            default:
                if( p_module->p_config[i].psz_value )
                {
                    if( p_module->p_config[i].psz_text )
                        fprintf( file, "# %s\n",
                                 p_module->p_config[i].psz_text );
                    fprintf( file, "%s=%s\n", p_module->p_config[i].psz_name,
                             p_module->p_config[i].psz_value );
                }
            }
        }

        fprintf( file, "\n" );
    }


    /*
     * Restore old settings from the config in file
     */
    fputs( p_bigbuffer, file );
    free( p_bigbuffer );

    fclose( file );
    free( psz_filename );
    vlc_mutex_unlock( &p_main->config_lock );

    return 0;
}

/* Following functions are local. */

/*****************************************************************************
 * GetHomeDir: find the user's home directory.
 *****************************************************************************
 * This function will try by different ways to find the user's home path.
 *****************************************************************************/
static char *GetHomeDir( void )
{
    char *p_tmp, *p_homedir = NULL;

#if defined(HAVE_GETPWUID_R) || defined(HAVE_GETPWUID)
    struct passwd *p_pw = NULL;
#endif

#if defined(HAVE_GETPWUID_R)
    int ret;
    struct passwd pwd;
    char *p_buffer = NULL;
    int bufsize = 128;

    p_buffer = (char *)malloc( bufsize );

    if( ( ret = getpwuid_r( getuid(), &pwd, p_buffer, bufsize, &p_pw ) ) < 0 )
#elif defined(HAVE_GETPWUID)
    if( ( p_pw = getpwuid( getuid() ) ) == NULL )
#endif
    {
        if( ( p_tmp = getenv( "HOME" ) ) == NULL )
        {
            intf_ErrMsg( "config error: unable to get home directory, "
                         "using /tmp instead" );
            p_homedir = strdup( "/tmp" );
        }
        else p_homedir = strdup( p_tmp );
    }
#if defined(HAVE_GETPWUID_R) || defined(HAVE_GETPWUID)
    else
    {
        if( p_pw ) p_homedir = strdup( p_pw->pw_dir );
    }
#endif

#if defined(HAVE_GETPWUID_R)
    if( p_buffer ) free( p_buffer );
#endif

    return p_homedir;
}