/*
**      SWISH++
**      conf_var.c
**
**      Copyright (C) 1998  Paul J. Lucas
**
**      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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

// standard
#include <cctype>
#include <cstring>
#include <iostream>

// local
#include "config.h"
#include "conf_var.h"
#include "exit_codes.h"
#include "mmap_file.h"
#include "platform.h"
#include "util.h"

using namespace std;
using namespace PJL;

extern char const   *me;

int conf_var::current_config_file_line_no_ = 0;

//*****************************************************************************
//
// SYNOPSIS
//
        conf_var::conf_var( char const *var_name )
//
// DESCRIPTION
//
//      Construct a configuration variable by mapping a variable name to an
//      instance of a conf_var (really, an instance of some derived class).
//      Only a single instance of any given variable may exist.
//
// PARAMETERS
//
//      var_name    The name of the variable.
//
//*****************************************************************************
    : name_( var_name )
{
    conf_var *&var = map_ref()[ to_lower( name_ ) ];
    if ( var )
        internal_error
            << "conf_var::conf_var(): \"" << name_
            << "\" registered more than once" << report_error;
    var = this;
}

//*****************************************************************************
//
// SYNOPSIS
//
        /* virtual */ conf_var::~conf_var()
//
// DESCRIPTION
//
//      Destructs a conf_var.  It is out-of-line only because it's virtual
//      (so its address is taken and put into the vtbl).
//
//*****************************************************************************
{
    // do nothing
}

//*****************************************************************************
//
// SYNOPSIS
//
        /* static */ conf_var::map_type& conf_var::map_ref()
//
// DESCRIPTION
//
//      Define and initialize (exactly once) a static data member for conf_var
//      and return a reference to it.  The reason for this function is to
//      guarantee that the map is initialized before its first use across all
//      translation units, something that would not guaranteed if it were a
//      static data member initialized at file scope.
//
//      We also load the map with all configuration variable names (in lower
//      case so variables in config files will be case-insensitive) so we can
//      tell the difference between a variable that doesn't exist (and that we
//      will complain about to the user) and one that simply isn't used in a
//      particular executable (and will be silently ignored).
//
// RETURN VALUE
//
//      Returns a reference to a static instance of an initialized map_type.
//
// SEE ALSO
//
//      Margaret A. Ellis and Bjarne Stroustrup.  "The Annotated C++
//      Reference Manual."  Addison-Wesley, Reading, MA, 1990.  p. 19.
//
//*****************************************************************************
{
    static map_type m;
    static bool init;
    if ( !init ) {
        init = true;                    // must set this before init_mod_vars()
        init_mod_vars();                // defined in init_mod_vars.c
        static char const *const var_name_table[] = {
            "associatemeta",
            "excludefile",
            "excludemeta",
            "extractextension",
            "extractfile",
            "extractfilter",
            "filesgrow",
            "filesreserve",
            "filterfile",
            "followlinks",
            "includefile",
            "includemeta",
            "incremental",
            "indexfile",
            "recursesubdirs",
            "resultsformat",
            "resultseparator",
            "resultsmax",
            "stemwords",
            "stopwordfile",
            "tempdirectory",
            "titlelines",
            "verbosity",
            "wordfilesmax",
            "wordpercentmax",
            "wordthreshold",
#ifdef  FEATURE_word_pos
            "storewordpositions",
            "wordsnear",
#endif
#ifdef  SEARCH_DAEMON
            "group",
#ifdef  __APPLE__
            "launchdcooperation",
#endif
            "pidfile",
            "searchbackground",
            "searchdaemon",
            "socketaddress",
            "socketfile",
            "socketqueuesize",
            "sockettimeout",
            "threadsmax",
            "threadsmin",
            "threadtimeout",
            "user",
#endif  /* SEARCH_DAEMON */
            0,
        };
        for ( register char const *const *v = var_name_table; *v; ++v )
            m[ *v ] = 0;
    }
    return m;
}

//*****************************************************************************
//
// SYNOPSIS
//
        ostream& conf_var::msg( ostream &o, char const *label )
//
// DESCRIPTION
//
//      Emit the standard message preamble to the given ostream, that is the
//      program name, a line number if a configuration file is being parsed,
//      and a label.
//
// PARAMETERS
//
//      o   The ostream to write the message to.
//
//      label   The label to emit, usually "error" or "warning".
//
// RETURN VALUE
//
//      Returns the ostream passed in.
//
//*****************************************************************************
{
    o << me;
    if ( current_config_file_line_no_ ) {
        //
        // This is set only by parse_line() during the parsing of a
        // configuration file.
        //
        o << ": config file line " << current_config_file_line_no_;
        current_config_file_line_no_ = 0;
    }
    return o << ": " << label << ": ";
}

//*****************************************************************************
//
// SYNOPSIS
//
        void conf_var::parse_const_value( char const *line )
//
// DESCRIPTION
//
//      Parse a line that can't be modified by simply copying it and calling
//      parse_value() on the copy.
//
//*****************************************************************************
{
    char *const line_copy = new_strdup( line );
    parse_value( line_copy );
    delete[] line_copy;
}

//*****************************************************************************
//
// SYNOPSIS
//
        /* static */ void conf_var::parse_file( char const *file_name )
//
// DESCRIPTION
//
//      Parse the lines in a configuration file setting variables accordingly.
//
// PARAMETERS
//
//      file_name   The name of the configuration file to parse.
//
//*****************************************************************************
{
    mmap_file const conf_file( file_name );
    if ( !conf_file ) {
        if ( !::strcmp( file_name, ConfigFile_Default ) ) {
            //
            // The configuration file couldn't be opened; however, the file
            // name is the default, so assume that none is being used and
            // simply return.
            //
            return;
        }
        cerr    << "could not read configuration from \"" << file_name
            << '"' << error_string( conf_file.error() );
        ::exit( Exit_Config_File );
    }
    conf_file.behavior( mmap_file::bt_sequential );

    register int line_no = 0;
    register mmap_file::const_iterator c = conf_file.begin();

    while ( c != conf_file.end() ) {
        //
        // Find the end of the line.
        //
        mmap_file::const_iterator const
            nl = find_newline( c, conf_file.end() );
        if ( nl == conf_file.end() )
            break;

        ++line_no;
        //
        // See if the line is entirely whitespace optionally followed by a
        // comment starting with '#': if so, skip it.  If we don't end up
        // skipping it, leading whitespace will have been skipped.
        //
        for ( ; c != nl; ++c ) {
            if ( is_space( *c ) )
                continue;
            if ( *c == '#' )
                goto next_line;
            break;
        }
        if ( c != nl ) {
            //
            // The line has something on it worth parsing further: copy it
            // (less leading and trailing whitespace) to a modifyable buffer
            // and null-terminate it to make that task easier.
            //
            char buf[ 256 ];
            ptrdiff_t len = nl - c;
            ::strncpy( buf, c, len );
            while ( len > 0 )
                if ( is_space( buf[ len - 1 ] ) )
                    --len;
                else
                    break;
            buf[ len ] = '\0';
            parse_line( buf, line_no );
        }
next_line:
        c = skip_newline( nl, conf_file.end() );
    }
}

//*****************************************************************************
//
// SYNOPSIS
//
        /* static */ void conf_var::parse_line( char *line, int line_no )
//
// DESCRIPTION
//
//      Parse a non-comment or non-blank line from a the configuration file,
//      the first word of which is the variable name.  Look up the variable in
//      our map and delegate the parsing of the rest of the line to an instance
//      of a derived class that knows how to parse its own line format.
//
// PARAMETERS
//
//      line        A line from a configuration file to be parsed.
//
//      line_no     The line number of the line.
//
//*****************************************************************************
{
    current_config_file_line_no_ = line_no;
    ::strtok( line, " \r\t" );          // just the variable name
    map_type::const_iterator const i = map_ref().find( to_lower( line ) );
    if ( i == map_ref().end() ) {
        warning() << '"' << line << "\" unrecognized; ignored\n";
        return;
    }
    if ( i->second ) {
        //
        // Chop off trailing newline and remove leading whitespace from value.
        //
        register char *value = ::strtok( 0, "\r\n" );
        while ( *value && is_space( *value ) )
            ++value;
        i->second->parse_value( value );
    } // else
    //
    //  This config. variable is not used by the current executable: silently
    //  ignore it.
    //
    current_config_file_line_no_ = 0;
}

//*****************************************************************************
//
// SYNOPSIS
//
        /* static */ void conf_var::register_var( char const *name )
//
// DESCRIPTION
//
//      Register a variable name for so that it can be used in a config. file.
//      Derived indexing modules use this function to register module-specific
//      variables.
//
// PARAMETERS
//
//      name    The name of the variable.
//
//*****************************************************************************
{
    map_type::const_iterator const i = map_ref().find( name );
    if ( i != map_ref().end() )
        internal_error
            << "conf_var::register_var(): \"" << name
            << "\" registered more than once" << report_error;
    map_ref()[ name ] = 0;
}

//*****************************************************************************
//
// SYNOPSIS
//
        /* static */ void conf_var::reset_all()
//
// DESCRIPTION
//
//      Reset all configuration variables to their default values.
//
//*****************************************************************************
{
    map_type &m = map_ref();
    TRANSFORM_EACH( map_type, m, i )
        if ( i->second )
            i->second->reset();
}
/* vim:set et sw=4 ts=4: */
