/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Module configuration.
 *//*
 * LEGAL:   COPYRIGHT (C) 2007 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define BASE_CONF_CC 1
#include <fstream>
#include "base/module.hh"
#include "base/conf.hh"
#include "base/conf_txt.hh"
#include "base/conf_template_macros.hh"
#include "base/file.hh"
#include "base/stream.hh"
#if DEBUG
#include "base/globals.hh"
#endif

namespace base {

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////  Conf  ///////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

namespace global { Conf gConf; }

INTERN char  sDummyArg0[1] = { 0 };
INTERN char* sDummyArgv[] = { sDummyArg0 };

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
Conf::Conf( void )
:   mValid(false),
    mProgramName(""),
    mArgc(0),  // mArg* are assigned at Init()
    mArgv(sDummyArgv),
    mArgv0(""),  // C++ string
    // cmd-line args
    mArgVerbose(false),
    mArgQuiet(false),
#if OS_UNIX
    mTimerUnixGettimeofday(false),
#endif
    // --- .conf file ---
    mVerbose(true),
    mQuiet(false),
    mRandomSeed(0x2b3e27bd),
    mTimeClockSkewMilliseconds(1 * (60*1000))  // 1 minute
{
}

/*****************************************************************************
 * Initialize/read configuration.
 *****************************************************************************/
void
Conf::Init( const base::InitArgs& initArgs, const string& confTxtFilename )
{
    mProgramName = initArgs.mProgramName;  // first of all (specific to base module)

    CONF_TEMPLATE_Init_ParseCmdline_ReadConfTxt;
}

/*****************************************************************************
 * Parse cmdline args.
 *****************************************************************************/
void
Conf::ParseCmdlineArgs( int argc, char** argv )
{
    // Subtle: -v/-q cmdline overrides .conf.
    // ConfTxt::ProcessFile() ignores Verbose/Quiet conf lines if those args were passed.

    // Some args need to be parsed very early, before .conf is read.
    for ( int i = 1; i < argc; ++i )
    {
        if ( STREQ( argv[i], "-q" ) )  // override .conf
        {
            mArgQuiet   = mQuiet   = true;
            mArgVerbose = mVerbose = false;
            CmdargDone( i );
        }
        else if ( STREQ( argv[i], "-v" ) )  // override .conf
        {
            mArgQuiet   = mQuiet   = false;
            mArgVerbose = mVerbose = true;
            CmdargDone( i );
        }
#if DEBUG
        else if ( STREQ( argv[i], "-debug" )     // for debugger, call abort() instead of throwing exception
               or STREQ( argv[i], "-debugger" )  // BTW, -dump* args now mean to save the scene-graph
               or STREQ( argv[i], "-gdb" ) )
        {
            CDEBUG << "Enabled debugger mode." << std::endl;
            gGlobals.mAssertAbort = true;
            CmdargDone( i );
        }
#endif // DEBUG
#if OS_UNIX
        else if ( STREQ( argv[i], "-timer-gettimeofday" ) )
        {
            mTimerUnixGettimeofday = true;
            CmdargDone( i );
            CLOG << "Forcing timer to use UNIX gettimeofday()." << std::endl;
        }
#endif // OS_UNIX
    }
}

void
Conf::ParseCmdlineArgs2( int argc, char** argv )
{
    // NOP
}

/*****************************************************************************
 * Read from .conf file.
 *****************************************************************************/
void
Conf::ReadConfTxt( const string& confTxtFilename )
{
    CONF_TEMPLATE_ReadConfTxt;
}

void
Conf::ModuleConfTxt::ProcessField( string& field, std::ifstream& conf )
{
    bool error = false;
    if ( field == "Verbose" )  // -v/-q override .conf
    {
        if ( BASE_CONF.mArgQuiet or BASE_CONF.mArgVerbose )
        {
            int ignore; conf >> ignore;
        }
        else
        {
            conf >> BASE_CONF.mVerbose;
            if ( BASE_CONF.mVerbose and BASE_CONF.mQuiet )
                error = true;
        }
    }
    else if ( field == "Quiet" )  // -v/-q override .conf
    {
        if ( BASE_CONF.mArgQuiet or BASE_CONF.mArgVerbose )
        {
            int ignore; conf >> ignore;
        }
        else
        {
            conf >> BASE_CONF.mQuiet;
            if ( BASE_CONF.mVerbose and BASE_CONF.mQuiet )
                error = true;
        }
    }
    else if ( field == "RandomSeed" )
    {
        conf >> BASE_CONF.mRandomSeed;
    }
    else if ( field == "TimeClockSkew" )
    {
        conf >> BASE_CONF.mTimeClockSkewMilliseconds;
    }
#if DEBUG
    else if ( field == "DebugTest" )
    {
        // Ensure setbase(0) was applied to C++ input stream in order to convert hex numbers.
        // By default, C++ input streams assume numbers are decimal-only.
        int decNum = 0;
        conf >> decNum;
        ASSERT( decNum == 1234 );

        int hexNum = 0;
        conf >> hexNum;
        ASSERT( hexNum == 0x1234 );

        int hexNumAlpha = 0;
        conf >> hexNumAlpha;
        ASSERT( hexNumAlpha == 0xabcd );

        int hexNumNeg = 0;
        conf >> hexNumNeg;
        ASSERT( hexNumNeg == -0xabcd );
    }
#endif // DEBUG

    // Errors?
    if ( error )
    {
        CERROR << "ERROR[conf]: parameter for " << field << " is invalid." << std::endl;
        EXIT( 1 );
    }
}

/*****************************************************************************
 * Print Conf configuration.
 *****************************************************************************/
std::ostream& operator<<( std::ostream& strm, const Conf& conf )
{
    std::ios::fmtflags savedFlags = strm.flags();

    strm << "conf [base]:"                                                      << std::endl
         << "RandomSeed                  = " << std::hex << conf.mRandomSeed; //<< std::endl;

    strm.flags(savedFlags);

    return strm;
}

////////////////////////////////////////////////////////////////////////////////
///////////////////////////////  functions  ////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * @return Directory pathname containing the data/ subdirectory.
 * @pre BASE_CONF.mProgramName was assigned.
 *****************************************************************************/
const string&
GetDataDir( void )
{
    const string& programName = BASE_CONF.mProgramName;

    // programName is required to determine pathname of data dir.
    if ( programName.empty() )
    {
        CERROR << "BASE_CONF.mProgramName UNDEFINED!" << std::endl;
        ASSERT(false);
    }

    // Find directory once.
    PERSISTENT string sDataDir;
    if ( sDataDir.empty() )
    {
        // If run from top of src tree.
        sDataDir = "data/";
        if ( IfDirExists( sDataDir ) )
            return sDataDir;

        // If run from bin (unusual).
        sDataDir = "../data/";
        if ( IfDirExists( sDataDir ) )
            return sDataDir;

        // Linux:
        sDataDir = string("/usr/local/games/") + string(programName) + string("/data/");
        if ( IfDirExists( sDataDir ) )
            return sDataDir;

        sDataDir = string("/usr/share/games/") + string(programName) + string("/data/");
        if ( IfDirExists( sDataDir ) )
            return sDataDir;

        sDataDir = string("/usr/games/") + string(programName) + string("/data/");
        if ( IfDirExists( sDataDir ) )
            return sDataDir;

        // FreeBSD:
        sDataDir = string("/usr/local/share/games/") + string(programName) + string("/data/");
        if ( IfDirExists( sDataDir ) )
            return sDataDir;

        // Apple Mac OS X (MacPorts):
        sDataDir = string("/opt/local/share/") + string(programName) + string("/data/");
        if ( IfDirExists( sDataDir ) )
            return sDataDir;

        // Apple Mac OS X (Fink):
        sDataDir = string("/sw/share/") + string(programName) + string("/data/");
        if ( IfDirExists( sDataDir ) )
            return sDataDir;

        // Windows: if run from bin/Release/
        sDataDir = "../../data/";
        if ( IfDirExists( sDataDir ) )
            return sDataDir;

        CERROR << "ERROR: Couldn't find data/ directory." << std::endl;
        EXIT( 1 );
    }

    return sDataDir;
}

} // namespace base
