//
// The developer of the original code and/or files is Tripwire, Inc.
// Portions created by Tripwire, Inc. are copyright (C) 2000-2018 Tripwire,
// Inc. Tripwire is a registered trademark of Tripwire, Inc.  All rights
// reserved.
//
// This program is free software.  The contents of this file are subject
// to 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.  You may redistribute it and/or modify it
// only in compliance with the GNU General Public License.
//
// This program is distributed in the hope that it will be useful.
// However, this program is distributed AS-IS WITHOUT ANY
// WARRANTY; INCLUDING THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS
// FOR A PARTICULAR PURPOSE.  Please 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-1307,
// USA.
//
// Nothing in the GNU General Public License or any other license to use
// the code or files shall permit you to use Tripwire's trademarks,
// service marks, or other intellectual property without Tripwire's
// prior written consent.
//
// If you have any questions, please contact Tripwire, Inc. at either
// info@tripwire.org or www.tripwire.org.
//
///////////////////////////////////////////////////////////////////////////////
// twprintcmdline.cpp
//
#include "stdtwprint.h"
#include "twprintcmdline.h"
#include "twprintstrings.h" // strings specific to twprint
#include "twprintcmdline.h" // functions for the cmd line
#include "twprinterrors.h"

#include "core/cmdlineparser.h"
#include "core/errorgeneral.h"
#include "core/errorbucketimpl.h" // for the error table
#include "core/usernotify.h"      // for notifying the user of even
#include "core/serializerimpl.h"  // cSerializerImpl
#include "core/archive.h"         // cArchive and friends

#include "tw/configfile.h"
#include "tw/fcodatabasefile.h" // cFCODatabaseFile
#include "tw/fcoreport.h"       // for reports
#include "tw/textdbviewer.h"
#include "tw/textreportviewer.h" // text report viewer
#include "tw/twutil.h"
#include "tw/headerinfo.h"
#include "tw/systeminfo.h"
#include "tw/twerrors.h"
#include "tw/dbdatasource.h"
#include "tw/twstrings.h"

#include "twcrypto/keyfile.h" // cKeyFile -- used for encryption

#include "util/fileutil.h"

#include "fco/fcogenre.h"
#include "fco/genreswitcher.h"
#include "fco/twfactory.h"
#include "fco/fcospeclist.h" // cFCOSpecList
#include "fco/fcopropdisplayer.h"
#include "fco/signature.h"

#include <set>

///////////////////////////////////////////////////////////////////////////////
// Static functions --
///////////////////////////////////////////////////////////////////////////////
static void InitCmdLineCommon(cCmdLineParser& parser);
// A method for initializing the command line parser with arguments that
// are common to all modes of twprint.

//#############################################################################
// cTWPrintCmdLine
//#############################################################################


// this is used to make required condition checking in the Init() functions a little more compact
#define TEST_INIT_REQUIREMENT(t, n)                  \
    if (!t)                                          \
    {                                                \
        TCERR << TSS_GetString(cTW, n) << std::endl; \
        return false;                                \
    }

// TODO: get Matt to update this with changes he made to tripwire

///////////////////////////////////////////////////////////////////////////////
// InitCmdLineCommon --
//          Initialize the cmdlineparser with the arguments that are common
//          to all modes.  Other parameters must be added in the
//          InitCmdLineParser method of the derived mode.
///////////////////////////////////////////////////////////////////////////////
static void InitCmdLineCommon(cCmdLineParser& parser)
{
    // help
    parser.AddArg(cTWPrintCmdLine::HELP, TSTRING(_T("?")), TSTRING(_T("help")), cCmdLineParser::PARAM_NONE);

    // mode
    parser.AddArg(cTWPrintCmdLine::MODE, TSTRING(_T("m")), TSTRING(_T("")), cCmdLineParser::PARAM_ONE);
    parser.AddArg(
        cTWPrintCmdLine::MODE_DBPRINT, TSTRING(_T("")), TSTRING(_T("print-dbfile")), cCmdLineParser::PARAM_NONE);
    parser.AddArg(
        cTWPrintCmdLine::MODE_REPORTPRINT, TSTRING(_T("")), TSTRING(_T("print-report")), cCmdLineParser::PARAM_NONE);

    // reporting
    parser.AddArg(cTWPrintCmdLine::VERBOSE, TSTRING(_T("v")), TSTRING(_T("verbose")), cCmdLineParser::PARAM_NONE);
    parser.AddArg(cTWPrintCmdLine::SILENT, TSTRING(_T("s")), TSTRING(_T("silent")), cCmdLineParser::PARAM_NONE);
    parser.AddArg(cTWPrintCmdLine::SILENT, TSTRING(_T("")), TSTRING(_T("quiet")), cCmdLineParser::PARAM_NONE);

    // config file overrides
    parser.AddArg(cTWPrintCmdLine::CFG_FILE, TSTRING(_T("c")), TSTRING(_T("cfgfile")), cCmdLineParser::PARAM_ONE);
    parser.AddArg(
        cTWPrintCmdLine::LOCAL_KEY_FILE, TSTRING(_T("L")), TSTRING(_T("local-keyfile")), cCmdLineParser::PARAM_ONE);
    parser.AddArg(
        cTWPrintCmdLine::SITE_KEY_FILE, TSTRING(_T("S")), TSTRING(_T("site-keyfile")), cCmdLineParser::PARAM_ONE);

    // unattended operation
    parser.AddArg(cTWPrintCmdLine::PASSPHRASE, TSTRING(_T("P")), TSTRING(_T("passphrase")), cCmdLineParser::PARAM_ONE);


    parser.AddArg(
        cTWPrintCmdLine::HEXADECIMAL, TSTRING(_T("h")), TSTRING(_T("hexadecimal")), cCmdLineParser::PARAM_NONE);

    // the paramters to the command line ... for now, this will take "many", even though in some
    // modes, this is not valid to do...

    // NOTE -- print report doesn't take any naked parameters, and neither does print database (we took that
    //      functionality out) so I changed this to PARAM_NONE.

    // NOTE -- We __DO__ take "naked" parameters in print-database mode, contrary to the above comment!!! It is an error
    // to add this argument here, in the "common" code between the two modes of twprint.  I'm moving the PARAMS arg to
    // twprint --print-database's "personal" initialization (below). -DRA 8/9/99

    //parser.AddArg(cTWPrintCmdLine::PARAMS,            TSTRING(_T("")),    TSTRING(_T("")),                cCmdLineParser::/*PARAM_NONE*/PARAM_MANY);
    // Error for print-report!!!!

    parser.AddMutEx(cTWPrintCmdLine::VERBOSE, cTWPrintCmdLine::SILENT);
}

///////////////////////////////////////////////////////////////////////////////
// GetMode -- processes the command line arguments and creates an appropriate
//      structure for the selected mode, or NULL if an error occurs
///////////////////////////////////////////////////////////////////////////////
iTWMode* cTWPrintCmdLine::GetMode(int argc, const TCHAR* const* argv)
{
    // note -- it is assumed the executable name is the first parameter
    if (argc < 2)
    {
        TCOUT << TSS_GetString(cTWPrint, twprint::STR_TWPRINT_VERSION) << std::endl;
        TCOUT << TSS_GetString(cTW, tw::STR_VERSION) << std::endl;
        TCOUT << TSS_GetString(cTW, tw::STR_GET_HELP) << std::endl;
        return NULL;
    }

    int          mode = MODE;
    const TCHAR* pcMode;
    if (_tcscmp(argv[1], _T("-m")) == 0)
    {
        if (argc < 3)
        {
            TCOUT << TSS_GetString(cTWPrint, twprint::STR_TWPRINT_VERSION) << std::endl;
            TCOUT << TSS_GetString(cTW, tw::STR_VERSION) << std::endl;
            TCOUT << TSS_GetString(cTW, tw::STR_ERR_NO_MODE) << std::endl;
            TCOUT << TSS_GetString(cTW, tw::STR_GET_HELP) << std::endl;
            return NULL;
        }
        pcMode = argv[2];
        if (_tcscmp(argv[2], _T("d")) == 0)
            mode = MODE_DBPRINT;
        else if (_tcscmp(argv[2], _T("r")) == 0)
            mode = MODE_REPORTPRINT;
    }
    else
    {
        pcMode = argv[1];
        if (_tcscmp(argv[1], _T("--print-dbfile")) == 0)
            mode = MODE_DBPRINT;
        else if (_tcscmp(argv[1], _T("--print-report")) == 0)
            mode = MODE_REPORTPRINT;
        else if (_tcscmp(argv[1], _T("--version")) == 0)
        {
            TCOUT << TSS_GetString(cTW, tw::STR_VERSION) << std::endl;
            return NULL;
        }
        else if (_tcscmp(argv[1], _T("--help")) == 0 || _tcscmp(argv[1], _T("-?")) == 0)
            mode = MODE_HELP;
    }

    if (mode == MODE)
    {
        // unknown mode switch
        cDebug d("cTWPrintCmdLine::GetMode");
        d.TraceError(_T("Error: Bad mode switch: %s\n"), pcMode);
        TCERR << TSS_GetString(cTW, tw::STR_UNKNOWN_MODE_SPECIFIED) << pcMode << std::endl;
        TCERR << TSS_GetString(cTW, tw::STR_GET_HELP) << std::endl;
        return NULL;
    }


    iTWMode* pRtn = 0;
    switch (mode)
    {
    case cTWPrintCmdLine::MODE_DBPRINT:
        pRtn = new cTWPrintDBMode;
        break;
    case cTWPrintCmdLine::MODE_REPORTPRINT:
        pRtn = new cTWPrintReportMode;
        break;
    case cTWPrintCmdLine::MODE_HELP:
        pRtn = new cTWPrintHelpMode;
        break;
    default:
        ASSERT(false);
    }

    return pRtn;
}

//#############################################################################
// cTWPrintModeCommon -- contains data common to all modes; all cTWMode*_i will
//      derive from this class.
//#############################################################################
class cTWPrintModeCommon
{
public:
    int         mVerbosity;  // must be 0 <= n <= 2
    std::string mPassPhrase; // pass phrase for private key
    TSTRING     mLocalKeyFile;
    TSTRING     mSiteKeyFile;
    TSTRING     mConfigFilePath;

    cTWPrintModeCommon() : mVerbosity(1)
    {
    }
    cTextReportViewer::ReportingLevel mReportLevel; // The level of reporting to use.
};

///////////////////////////////////////////////////////////////////////////////
// FillOutCommonConfigInfo -- fills out all the common info with config file information
///////////////////////////////////////////////////////////////////////////////
static void FillOutCommonConfigInfo(cTWPrintModeCommon* pModeInfo, const cConfigFile& cf)
{
    TSTRING str;
    if (cf.Lookup(TSTRING(_T("LOCALKEYFILE")), str))
        pModeInfo->mLocalKeyFile = str;

    if (cf.Lookup(TSTRING(_T("SITEKEYFILE")), str))
        pModeInfo->mSiteKeyFile = str;

    //
    // turn all of the file names into full paths (they're relative to the exe dir)
    //
    TSTRING fullPath;
    if (iFSServices::GetInstance()->FullPath(fullPath, pModeInfo->mLocalKeyFile, cSystemInfo::GetExeDir()))
        pModeInfo->mLocalKeyFile = fullPath;
}

///////////////////////////////////////////////////////////////////////////////
// FillOutCmdLineInfo -- fills out info common to all modes that appears on the
//      command line.
///////////////////////////////////////////////////////////////////////////////
static void FillOutCmdLineInfo(cTWPrintModeCommon* pModeInfo, const cCmdLineParser& cmdLine)
{
    cCmdLineIter iter(cmdLine);
    for (iter.SeekBegin(); !iter.Done(); iter.Next())
    {
        switch (iter.ArgId())
        {
        case cTWPrintCmdLine::VERBOSE:
            pModeInfo->mVerbosity = 2;
            break;
        case cTWPrintCmdLine::SILENT:
            pModeInfo->mVerbosity = 0;
            break;
        case cTWPrintCmdLine::LOCAL_KEY_FILE:
            ASSERT(iter.NumParams() > 0); // should be caught by cmd line parser
            pModeInfo->mLocalKeyFile = iter.ParamAt(0);
            break;
        case cTWPrintCmdLine::SITE_KEY_FILE:
            ASSERT(iter.NumParams() > 0); // should be caught by cmd line parser
            pModeInfo->mSiteKeyFile = iter.ParamAt(0);
            break;
        case cTWPrintCmdLine::HEXADECIMAL:
            cArchiveSigGen::SetHex(true);
            break;
        case cTWPrintCmdLine::PASSPHRASE:
        {
            // this bites! I have to make sure it is a narrow char string
            ASSERT(iter.NumParams() > 0); // should be caught by cmd line parser
            pModeInfo->mPassPhrase = iter.ParamAt(0);
            break;
        }
        default:
            break;
        }
    }

    // use the verbosity information
    ASSERT((pModeInfo->mVerbosity >= 0) && (pModeInfo->mVerbosity < 3));
    iUserNotify::GetInstance()->SetVerboseLevel(pModeInfo->mVerbosity);
}

//#############################################################################
// cTWPrintReportMode
//#############################################################################
class cTWPrintReportMode_i : public cTWPrintModeCommon
{
public:
    TSTRING                 mReportFile;
    std::set<TSTRING>       mFilesToCheck;

    // ctor can set up some default values
    cTWPrintReportMode_i() : cTWPrintModeCommon()
    {
    }
};

void cTWPrintReportMode::SetConfigFile(TSTRING configFilePath)
{
    mpData->mConfigFilePath = configFilePath;
}

///////////////////////////////////////////////////////////////////////////////
// FillOutReportModeConfigInfo -- fills out all the common info with config file information
///////////////////////////////////////////////////////////////////////////////
void cTWPrintReportMode::FillOutReportModeConfigInfo(cTWPrintReportMode_i* pModeInfo, const cConfigFile& cf)
{
    TSTRING str;
    if (cf.Lookup(TSTRING(_T("REPORTFILE")), str))
        pModeInfo->mReportFile = str;

    // Find out what level of reporting we should use, use default level if none
    // has been specified.
    if (cf.Lookup(TSTRING(_T("REPORTLEVEL")), str))
    {
        if (_tcsicmp(str.c_str(), _T("0")) == 0)
            /*throw eTWPrintReportLevelZeroCfg( TSS_GetString( cTWPrint, twprint::STR_ERR2_REPORT_LEVEL_ZERO_CFG ) );
        // We error if reporting-level 0 is specified in the config. file
        // Level zero must be specified on the command line. - changed, 7/14/99 dra */
            pModeInfo->mReportLevel = cTextReportViewer::SINGLE_LINE;
        else if (_tcsicmp(str.c_str(), _T("1")) == 0)
            pModeInfo->mReportLevel = cTextReportViewer::PARSEABLE;
        else if (_tcsicmp(str.c_str(), _T("2")) == 0)
            pModeInfo->mReportLevel = cTextReportViewer::SUMMARY_ONLY;
        else if (_tcsicmp(str.c_str(), _T("3")) == 0)
            pModeInfo->mReportLevel = cTextReportViewer::CONCISE_REPORT;
        else if (_tcsicmp(str.c_str(), _T("4")) == 0)
            pModeInfo->mReportLevel = cTextReportViewer::FULL_REPORT;
        else
        {
            // They specified an illegal level, error.
            TSTRING errStr = _T("Invalid Level: ");
            errStr += str;
            throw eTWPrintInvalidReportLevelCfg(errStr);
        }
    }
    else
        // Use the default level of reporting, they specified none in configuration file.
        pModeInfo->mReportLevel = cTextReportViewer::CONCISE_REPORT;

    //
    // turn all of the file names into full paths (they're relative to the exe dir)
    //
    TSTRING fullPath;
    if (iFSServices::GetInstance()->FullPath(fullPath, pModeInfo->mReportFile, cSystemInfo::GetExeDir()))
        pModeInfo->mReportFile = fullPath;
}

///////////////////////////////////////////////////////////////////////////////
// ctor, dtor
///////////////////////////////////////////////////////////////////////////////
cTWPrintReportMode::cTWPrintReportMode()
{
    mpData = new cTWPrintReportMode_i;
}

cTWPrintReportMode::~cTWPrintReportMode()
{
    delete mpData;
}

///////////////////////////////////////////////////////////////////////////////
// InitCmdLineParser
///////////////////////////////////////////////////////////////////////////////
void cTWPrintReportMode::InitCmdLineParser(cCmdLineParser& parser)
{
    InitCmdLineCommon(parser);

    parser.AddArg(cTWPrintCmdLine::REPORT_FILE, TSTRING(_T("r")), TSTRING(_T("twrfile")), cCmdLineParser::PARAM_ONE);

    // multiple levels of reporting
    parser.AddArg(
        cTWPrintCmdLine::REPORTLEVEL, TSTRING(_T("t")), TSTRING(_T("report-level")), cCmdLineParser::PARAM_ONE);

    // For the variable object list.
    parser.AddArg(cTWPrintCmdLine::PARAMS, TSTRING(_T("")), TSTRING(_T("")), cCmdLineParser::PARAM_MANY);
}

///////////////////////////////////////////////////////////////////////////////
// Init
///////////////////////////////////////////////////////////////////////////////
bool cTWPrintReportMode::Init(const cConfigFile& cf, const cCmdLineParser& cmdLine)
{
    // first, fill out everything with the config file info...
    FillOutCommonConfigInfo(mpData, cf);
    FillOutReportModeConfigInfo(mpData, cf);

    // TODO -- should I error or warn for (1) mutual exclustion errors or (2) unneeded cmd line
    //      parameters? I think I should, but I won't right now.
    // TODO -- error at the end of Init() if I am missing any requires parameter values..

    // now, parse the command line...
    // this takes care of the common stuff...
    FillOutCmdLineInfo(mpData, cmdLine);

    // now do the stuff specific to this mode...
    cCmdLineIter iter(cmdLine);
    for (iter.SeekBegin(); !iter.Done(); iter.Next())
    {
        switch (iter.ArgId())
        {
        case cTWPrintCmdLine::REPORT_FILE:
            ASSERT(iter.NumParams() > 0); // should be caught by cmd line parser
            mpData->mReportFile = iter.ParamAt(0);
            break;
        case cTWPrintCmdLine::REPORTLEVEL:
        {
            if (iter.ParamAt(0) == _T("0"))
                mpData->mReportLevel = cTextReportViewer::SINGLE_LINE;
            else if (iter.ParamAt(0) == _T("1"))
                mpData->mReportLevel = cTextReportViewer::PARSEABLE;
            else if (iter.ParamAt(0) == _T("2"))
                mpData->mReportLevel = cTextReportViewer::SUMMARY_ONLY;
            else if (iter.ParamAt(0) == _T("3"))
                mpData->mReportLevel = cTextReportViewer::CONCISE_REPORT;
            else if (iter.ParamAt(0) == _T("4"))
                mpData->mReportLevel = cTextReportViewer::FULL_REPORT;
            else
            {
                // They specified an illegal level, error.
                TSTRING errStr = _T("Invalid Level: ");
                errStr += iter.ParamAt(0);
                throw eTWPrintInvalidReportLevel(errStr);
            }
            break;
        }
        case cTWPrintCmdLine::PARAMS:
        {
            // pack all of these onto the files to check list...
            mpData->mFilesToCheck.clear();
            for (int i = 0; i < iter.NumParams(); i++)
            {
                mpData->mFilesToCheck.insert(iter.ParamAt(i));
            }
        }
        //done with report-level stuff.
        break;
        default:
            // should I do anything, or just ignore this?
            ;
        }
    }

    //----------------------------------------
    // I require the following information:
    //
    // * local key file
    // * report file
    //-----------------------------------------
    TEST_INIT_REQUIREMENT((!mpData->mLocalKeyFile.empty()), tw::STR_ERR_MISSING_LOCAL_KEY);
    TEST_INIT_REQUIREMENT((!mpData->mReportFile.empty()), tw::STR_ERR_MISSING_REPORT);

    // check that the config file and site key file are in sync...
    //
    if (!mpData->mConfigFilePath.empty())
        try
        {
            if (cTWUtil::VerifyCfgSiteKey(mpData->mConfigFilePath, mpData->mSiteKeyFile) == false)
                cTWUtil::PrintErrorMsg(eTWCfgUnencrypted(_T(""), eError::NON_FATAL | eError::SUPRESS_THIRD_MSG));
        }
        catch (eTWUtilCfgKeyMismatch& e)
        {
            e.SetFatality(false);
            cTWUtil::PrintErrorMsg(e);
        }

    return true;
}

///////////////////////////////////////////////////////////////////////////////
// Execute
///////////////////////////////////////////////////////////////////////////////
int cTWPrintReportMode::Execute(cErrorQueue* pQueue)
{
    try
    {
        ASSERT(!mpData->mReportFile.empty());
        // make sure the report file exists...
        cFileUtil::TestFileExists(mpData->mReportFile);

        cKeyFile                    localKeyfile;
        const cElGamalSigPublicKey* pKey;
        cFCOReport                  report;
        cFCOReportHeader            reportHeader;

        cTWUtil::OpenKeyFile(localKeyfile, mpData->mLocalKeyFile);
        pKey = localKeyfile.GetPublicKey();

        if (!cTWUtil::IsObjectEncrypted(mpData->mReportFile.c_str(),
                                        cFCOReport::GetFileHeaderID(),
                                        TSS_GetString(cTW, tw::STR_ERR_REPORT_READ)))
        {
            // note (rather than warn) if the database is not encrytped
            iUserNotify::GetInstance()->Notify(iUserNotify::V_NORMAL,
                                               TSS_GetString(cTW, tw::STR_REPORT_NOT_ENCRYPTED).c_str());
        }

        // open the report...
        bool bDummy;
        cTWUtil::ReadReport(mpData->mReportFile.c_str(), reportHeader, report, pKey, false, bDummy);

        // print it
        cTextReportViewer trv(reportHeader, report);
        if (!mpData->mFilesToCheck.empty())
        {
            trv.SetObjects(mpData->mFilesToCheck);
        }

        trv.PrintTextReport(_T("-"), mpData->mReportLevel);
    }
    catch (eError& e)
    {
        cTWUtil::PrintErrorMsg(e);
        return 1;
    }

    return 0;
}

///////////////////////////////////////////////////////////////////////////////
// GetModeUsage -- returns a mode-specific usage statement.
///////////////////////////////////////////////////////////////////////////////
TSTRING cTWPrintReportMode::GetModeUsage()
{
    return TSS_GetString(cTWPrint, twprint::STR_TWPRINT_HELP_PRINT_REPORT);
}

//#############################################################################
// cTWPrintDBMode
//#############################################################################
class cTWPrintDBMode_i : public cTWPrintModeCommon
{
public:
    TSTRING                    mDbFile;
    std::vector<TSTRING>       mFilesToCheck;
    cTextDBViewer::DbVerbosity mDbVerbosity;

    // ctor can set up some default values
    cTWPrintDBMode_i() : cTWPrintModeCommon(), mDbVerbosity(cTextDBViewer::VERBOSE)
    {
    }
};

///////////////////////////////////////////////////////////////////////////////
// ctor, dtor
///////////////////////////////////////////////////////////////////////////////
cTWPrintDBMode::cTWPrintDBMode()
{
    mpData = new cTWPrintDBMode_i;
}

cTWPrintDBMode::~cTWPrintDBMode()
{
    delete mpData;
}

void cTWPrintDBMode::SetConfigFile(TSTRING configFilePath)
{
    mpData->mConfigFilePath = configFilePath;
}

///////////////////////////////////////////////////////////////////////////////
// FillOutDBModeConfigInfo -- fills out all the common info with config file information
///////////////////////////////////////////////////////////////////////////////
void cTWPrintDBMode::FillOutDBModeConfigInfo(cTWPrintDBMode_i* pModeInfo, const cConfigFile& cf)
{
    TSTRING str;
    if (cf.Lookup(TSTRING(_T("DBFILE")), str))
        pModeInfo->mDbFile = str;

    if (cf.Lookup(TSTRING(_T("DBPRINTLEVEL")), str))
    {
        if (_tcsicmp(str.c_str(), _T("0")) == 0)
            pModeInfo->mDbVerbosity = cTextDBViewer::SUMMARY;
        else if (_tcsicmp(str.c_str(), _T("1")) == 0)
            pModeInfo->mDbVerbosity = cTextDBViewer::CONCISE;
        else if (_tcsicmp(str.c_str(), _T("2")) == 0)
            pModeInfo->mDbVerbosity = cTextDBViewer::VERBOSE;
        else
        {
            // They specified an illegal level, error.
            TSTRING errStr = _T("Invalid Level: ");
            errStr += str;
            throw eTWPrintInvalidDbPrintLevelCfg(errStr);
        }
    }
    else
        // Use the default level of reporting, they specified none in configuration file.
        pModeInfo->mVerbosity = cTextDBViewer::VERBOSE;

    //
    // turn all of the file names into full paths (they're relative to the exe dir)
    //
    TSTRING fullPath;
    if (iFSServices::GetInstance()->FullPath(fullPath, pModeInfo->mDbFile, cSystemInfo::GetExeDir()))
        pModeInfo->mDbFile = fullPath;
}

///////////////////////////////////////////////////////////////////////////////
// InitCmdLineParser
///////////////////////////////////////////////////////////////////////////////
void cTWPrintDBMode::InitCmdLineParser(cCmdLineParser& parser)
{
    InitCmdLineCommon(parser);

    parser.AddArg(cTWPrintCmdLine::DB_FILE, TSTRING(_T("d")), TSTRING(_T("dbfile")), cCmdLineParser::PARAM_ONE);

    // multiple levels of reporting
    parser.AddArg(
        cTWPrintCmdLine::REPORTLEVEL, TSTRING(_T("t")), TSTRING(_T("output-level")), cCmdLineParser::PARAM_ONE);

    // For the variable object list.
    parser.AddArg(cTWPrintCmdLine::PARAMS, TSTRING(_T("")), TSTRING(_T("")), cCmdLineParser::PARAM_MANY);
}

///////////////////////////////////////////////////////////////////////////////
// Init
///////////////////////////////////////////////////////////////////////////////
bool cTWPrintDBMode::Init(const cConfigFile& cf, const cCmdLineParser& cmdLine)
{
    // first, fill out everything with the config file info...
    FillOutCommonConfigInfo(mpData, cf);
    FillOutDBModeConfigInfo(mpData, cf);

    // TODO -- should I error or warn for (1) mutual exclustion errors or (2) unneeded cmd line
    //      parameters? I think I should, but I won't right now.
    // TODO -- error at the end of Init() if I am missing any requires parameter values..

    // now, parse the command line...
    // this takes care of the common stuff...
    FillOutCmdLineInfo(mpData, cmdLine);

    // now do the stuff specific to this mode...
    cCmdLineIter iter(cmdLine);
    for (iter.SeekBegin(); !iter.Done(); iter.Next())
    {
        switch (iter.ArgId())
        {

        case cTWPrintCmdLine::DB_FILE:
        {
            ASSERT(iter.NumParams() > 0); // should be caught by cmd line parser
            mpData->mDbFile = iter.ParamAt(0);
            break;
        }
        case cTWPrintCmdLine::REPORTLEVEL:
        {
            if (iter.ParamAt(0) == _T("0"))
                mpData->mDbVerbosity = cTextDBViewer::SUMMARY;
            else if (iter.ParamAt(0) == _T("1"))
                mpData->mDbVerbosity = cTextDBViewer::CONCISE;
            else if (iter.ParamAt(0) == _T("2"))
                mpData->mDbVerbosity = cTextDBViewer::VERBOSE;
            else
            {
                // They specified an illegal level, error.
                TSTRING errStr = _T("Invalid Level: ");
                errStr += iter.ParamAt(0);
                throw eTWPrintInvalidDbPrintLevel(errStr);
            }
            break;
        }
        case cTWPrintCmdLine::PARAMS:
        {
            // pack all of these onto the files to check list...
            mpData->mFilesToCheck.clear();
            for (int i = 0; i < iter.NumParams(); i++)
            {
                mpData->mFilesToCheck.push_back(iter.ParamAt(i));
            }
        }
        default:
            // TODO: should I do anything, or just ignore this?
            ;
        }
    }

    //----------------------------------------
    // I require the following information:
    //
    // * local key file
    // * report file
    //-----------------------------------------
    TEST_INIT_REQUIREMENT((!mpData->mLocalKeyFile.empty()), tw::STR_ERR_MISSING_LOCAL_KEY);
    TEST_INIT_REQUIREMENT((!mpData->mDbFile.empty()), tw::STR_ERR_MISSING_DB);

    // check that the config file and site key file are in sync...
    //
    if (!mpData->mConfigFilePath.empty())
        try
        {
            if (cTWUtil::VerifyCfgSiteKey(mpData->mConfigFilePath, mpData->mSiteKeyFile) == false)
                cTWUtil::PrintErrorMsg(eTWCfgUnencrypted(_T(""), eError::NON_FATAL | eError::SUPRESS_THIRD_MSG));
        }
        catch (eTWUtilCfgKeyMismatch& e)
        {
            e.SetFatality(false);
            cTWUtil::PrintErrorMsg(e);
        }

    return true;
}

///////////////////////////////////////////////////////////////////////////////
// Execute
///////////////////////////////////////////////////////////////////////////////
int cTWPrintDBMode::Execute(cErrorQueue* pQueue)
{
    cDebug d("cTWPrintDBMode::Execute");
    int    ret = 0;

    try
    {
        ASSERT(!mpData->mDbFile.empty());
        // make sure the database file exists...
        cFileUtil::TestFileExists(mpData->mDbFile);

        cFCODatabaseFile            db;
        cKeyFile                    localKeyfile;
        const cElGamalSigPublicKey* pKey = 0;

        cTWUtil::OpenKeyFile(localKeyfile, mpData->mLocalKeyFile);
        pKey = localKeyfile.GetPublicKey();

        if (!cTWUtil::IsObjectEncrypted(
                mpData->mDbFile.c_str(), cFCODatabaseFile::GetFileHeaderID(), TSS_GetString(cTW, tw::STR_ERR_DB_READ)))
        {
            // warn if the database is not encrytped
            if (iUserNotify::GetInstance()->GetVerboseLevel() > iUserNotify::V_SILENT)
            {
                cTWUtil::PrintErrorMsg(eTWDbNotEncrypted(_T(""), eError::NON_FATAL | eError::SUPRESS_THIRD_MSG));
            }
        }

        // open the database; note that ReadDatabase will set bEncrypted and open the
        // key file if necessary
        bool bDummy;
        cTWUtil::ReadDatabase(mpData->mDbFile.c_str(), db, pKey, bDummy);

        if (mpData->mFilesToCheck.size() > 0)
        {
            bool details = (mpData->mDbVerbosity == cTextDBViewer::VERBOSE);
            //------------------------------------------------
            // print specific FCOs from the database
            //------------------------------------------------
            cTWUtil::GenreObjList listOut;
            cTWUtil::ParseObjectList(listOut, mpData->mFilesToCheck);

            // now, iterate through the list of objects...
            //
            cFCODatabaseFile::iterator dbIter(db);
            for (cTWUtil::GenreObjList::iterator genreIter = listOut.begin(); genreIter != listOut.end(); ++genreIter)
            {
                dbIter.SeekToGenre(genreIter->first);
                if (!dbIter.Done())
                {
                    cGenreSwitcher::GetInstance()->SelectGenre((cGenre::Genre)dbIter.GetGenre());
                    cDbDataSourceIter dsIter(&dbIter.GetDb());
                    dsIter.SetErrorBucket(pQueue);
                    iFCONameTranslator* pNT = cGenreSwitcher::GetInstance()
                                                  ->GetFactoryForGenre((cGenre::Genre)dbIter.GetGenre())
                                                  ->GetNameTranslator();
                    //
                    // iterate over all the objects in this genre....
                    //
                    for (cTWUtil::ObjList::iterator it = genreIter->second.begin(); it != genreIter->second.end(); ++it)
                    {
                        try
                        {
                            cFCOName name = cTWUtil::ParseObjectName(*it);
                            dsIter.SeekToFCO(name, false);
                            if ((!dsIter.Done()) && (dsIter.HasFCOData()))
                            {
                                cTextDBViewer::OutputFCO(
                                    dsIter, dbIter.GetGenreHeader().GetPropDisplayer(), pNT, &TCOUT, details);
                            }
                            else
                            {
                                eTWUtilObjNotInDb e(*it);
                                e.SetFatality(false);
                                cTWUtil::PrintErrorMsg(e);
                            }
                        }
                        catch (eError& e)
                        {
                            e.SetFatality(false);
                            cTWUtil::PrintErrorMsg(e);
                        }
                    }
                }
                else
                {
                    eTWDbDoesntHaveGenre e(
                        cGenreSwitcher::GetInstance()->GenreToString((cGenre::Genre)genreIter->first, true));
                    e.SetFatality(false);
                    cTWUtil::PrintErrorMsg(e);
                }
            }
        }
        else
        {
            //------------------------------------------------
            // printing the entire db
            //------------------------------------------------
            cTextDBViewer::PrintDB(db, _T("-"), mpData->mDbVerbosity);
        }
    }
    catch (eError& e)
    {
        cTWUtil::PrintErrorMsg(e);
        return 1;
    }

    return ret;
}

///////////////////////////////////////////////////////////////////////////////
// GetModeUsage -- returns a mode-specific usage statement.
///////////////////////////////////////////////////////////////////////////////
TSTRING cTWPrintDBMode::GetModeUsage()
{
    return TSS_GetString(cTWPrint, twprint::STR_TWPRINT_HELP_PRINT_DATABASE);
}


//#############################################################################
// cTWPrintHelpMode
//#############################################################################

class cTWPrintHelpMode_i
{
public:
    cTWPrintHelpMode_i()
    {
    }
    ~cTWPrintHelpMode_i()
    {
    }

    // A list of modes to output usage statements for:
    std::set<TSTRING> mlModes;
    std::set<TSTRING> mPrinted;
};


///////////////////////////////////////////////////////////////////////////////
// ctor, dtor
///////////////////////////////////////////////////////////////////////////////
cTWPrintHelpMode::cTWPrintHelpMode()
{
    mpData = new cTWPrintHelpMode_i();
}

cTWPrintHelpMode::~cTWPrintHelpMode()
{
    delete mpData;
}

void cTWPrintHelpMode::SetConfigFile(TSTRING configFilePath)
{
}

///////////////////////////////////////////////////////////////////////////////
// InitCmdLineParser
///////////////////////////////////////////////////////////////////////////////
void cTWPrintHelpMode::InitCmdLineParser(cCmdLineParser& cmdLine)
{
    // We're only interested in one parameter, that being help.  Anything else
    // passed to this mode should be a cmdlineparser error.
    cmdLine.AddArg(cTWPrintCmdLine::MODE, TSTRING(_T("m")), TSTRING(_T("")), cCmdLineParser::PARAM_MANY, true);
    cmdLine.AddArg(cTWPrintCmdLine::MODE_HELP, TSTRING(_T("?")), TSTRING(_T("help")), cCmdLineParser::PARAM_MANY);
    cmdLine.AddArg(cTWPrintCmdLine::MODE_HELP_ALL, TSTRING(_T("")), TSTRING(_T("all")), cCmdLineParser::PARAM_MANY);
    cmdLine.AddArg(
        cTWPrintCmdLine::MODE_REPORTPRINT, TSTRING(_T("r")), TSTRING(_T("print-report")), cCmdLineParser::PARAM_MANY);
    cmdLine.AddArg(
        cTWPrintCmdLine::MODE_DBPRINT, TSTRING(_T("d")), TSTRING(_T("print-dbfile")), cCmdLineParser::PARAM_MANY);
}

///////////////////////////////////////////////////////////////////////////////
// Init
///////////////////////////////////////////////////////////////////////////////
bool cTWPrintHelpMode::Init(const cConfigFile& cf, const cCmdLineParser& cmdLine)
{
    cCmdLineIter iter(cmdLine);

    // Grab the arguments from the help parameter:
    for (iter.SeekBegin(); !iter.Done(); iter.Next())
    {
        switch (iter.ArgId())
        {
        case cTWPrintCmdLine::MODE_HELP:
        {
            int i;
            for (i = 0; i < iter.NumParams(); ++i)
                mpData->mlModes.insert(iter.ParamAt(i));
        }
        break;
        case cTWPrintCmdLine::MODE:
        {
            int i;
            for (i = 0; i < iter.NumParams(); ++i)
            {
                mpData->mlModes.insert(iter.ParamAt(i));
            }
        }
        break;
        // Begin ugly hack so we can allow users to enter the mode
        // names with "--" prepended.  We have to do this, since
        // the cmdlineparser treats them as switches.
        case cTWPrintCmdLine::MODE_HELP_ALL:    // fall through
        case cTWPrintCmdLine::MODE_REPORTPRINT: // fall through
        case cTWPrintCmdLine::MODE_DBPRINT:
        {
            int     i;
            TSTRING str = iter.ActualParam();
            // Kill off the initial "--" or "-"
            str.erase(0, 1);
            if (str.length() != 1)
                str.erase(0, 1);

            // push back the parameter that was actually passed.
            mpData->mlModes.insert(str);
            // grab all the "parameters" following the mode/switch, since
            // they may be valid modes without the "--" prepended.
            for (i = 0; i < iter.NumParams(); ++i)
                mpData->mlModes.insert(iter.ParamAt(i));
        }
        break;
        default:
            // should I do anything, or just ignore this?
            ;
        }
    }

    return true;
}

///////////////////////////////////////////////////////////////////////////////
// Execute
///////////////////////////////////////////////////////////////////////////////
int cTWPrintHelpMode::Execute(cErrorQueue* pQueue)
{
    cDebug d("cTWPrintHelpMode::Execute");

    // The iterator we will use to traverse the list of arguments:
    std::set<TSTRING>::iterator it = mpData->mlModes.begin();

    // We'll want to output the version information, regardless:
    TCOUT << TSS_GetString(cTWPrint, twprint::STR_TWPRINT_VERSION) << std::endl;
    TCOUT << TSS_GetString(cTW, tw::STR_VERSION) << std::endl;

    if (it == mpData->mlModes.end()) // all that was passed was --help
    {
        // Output a short usage statement for each mode
        TCOUT << TSS_GetString(cTWPrint, twprint::STR_TWPRINT_USAGE_SUMMARY);
        //
        // That's it.  return
        return 1;
    }

    for (; it != mpData->mlModes.end(); ++it)
    {
        if (_tcscmp((*it).c_str(), _T("all")) == 0)
        {
            //Since --help all was passed, emit all help messages and return.
            TCOUT << TSS_GetString(cTWPrint, twprint::STR_TWPRINT_HELP_PRINT_REPORT);
            TCOUT << TSS_GetString(cTWPrint, twprint::STR_TWPRINT_HELP_PRINT_DATABASE);

            //We're done, return
            return 1;
        }
    }

    //We need some subset of the usage statements.  Figure out which modes have
    //been specified:
    it = mpData->mlModes.begin();
    for (; it != mpData->mlModes.end(); ++it)
    {
        if (_tcscmp((*it).c_str(), _T("print-report")) == 0 || _tcscmp((*it).c_str(), _T("r")) == 0)
        {
            // make sure we don't print the same help twice...
            if (mpData->mPrinted.find(_T("print-report")) == mpData->mPrinted.end())
            {
                TCOUT << TSS_GetString(cTWPrint, twprint::STR_TWPRINT_HELP_PRINT_REPORT);
                mpData->mPrinted.insert(_T("print-report"));
            }
        }
        else if (_tcscmp((*it).c_str(), _T("print-dbfile")) == 0 || _tcscmp((*it).c_str(), _T("d")) == 0)
        {
            if (mpData->mPrinted.find(_T("print-dbfile")) == mpData->mPrinted.end())
            {
                TCOUT << TSS_GetString(cTWPrint, twprint::STR_TWPRINT_HELP_PRINT_DATABASE);
                mpData->mPrinted.insert(_T("print-dbfile"));
            }
        }
        else
        {
            cTWUtil::PrintErrorMsg(eTWPrintInvalidParamHelp((*it), eError::NON_FATAL));
            TCOUT << std::endl;
            // emit error/warning string, this mode does not exist
        }
    }

    //Everything went okay
    return 1;
}
