//
// The developer of the original code and/or files is Tripwire, Inc.
// Portions created by Tripwire, Inc. are copyright (C) 2000 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.
//
///////////////////////////////////////////////////////////////////////////////
// textdbviewer.cpp -- implementation for dbviewer class
//

//=========================================================================
// DIRECTIVES
//=========================================================================


//=========================================================================
// INCLUDES
//=========================================================================
#include "stdtw.h"

#include <fstream>
#include <iomanip>

#include "core/tchar.h"
#include "textdbviewer.h"
#include "core/debug.h"
#include "fcodatabasefile.h"
#include "dbdatasource.h"
#include "fco/fcospec.h"
#include "fco/iterproxy.h"
#include "fco/fcopropvector.h"
#include "fco/fcopropset.h"
#include "fco/fcoprop.h"
#include "core/fsservices.h"
#include "fco/fcospecattr.h"
#include "core/timeconvert.h"
#include "fco/fcopropdisplayer.h"
#include "headerinfo.h"
#include "fco/genreswitcher.h"
#include "core/twlocale.h"
#include "fco/twfactory.h"
#include "fco/fconametranslator.h"
#include "fco/fcoundefprop.h"
#include "tw/twstrings.h"
#include "core/displayencoder.h"

//=========================================================================
// USING NAMESPACE
//=========================================================================

using namespace std;

//=========================================================================
// GLOBALS
//=========================================================================

static const TCHAR* g_sz79Dashes = _T("-------------------------------------------------------------------------------");
static const TCHAR* g_sz79Equals = _T("===============================================================================");

//=========================================================================
// UTIL FUNCTION PROTOTYES
//=========================================================================
static void InitOStream( TOSTREAM* pOut );
static void DisplayFCOProps( const iFCO* const pFCO, const iFCOPropDisplayer* pPD, TOSTREAM* pOut );

static void OutputDatabaseHeader	( const cFCODbHeader& dbHeader, TOSTREAM* pOut );
// Currently we don't have a rule summary.  See function definition below
//static void OutputRulesSummary	( const cFCODatabaseFileIter& genreIter, const iFCONameTranslator* pNT, TOSTREAM* pOut );


static void OutputSectionDelimiter( int nString, TOSTREAM* pOut );
static void OutputGenreDelimiter( cGenre::Genre g, TOSTREAM* pOut );

static void OutputObjectSummary( cFCODatabaseFile& rd, TOSTREAM* pOut, int margin );
static void OutputObjectDetail( cFCODatabaseFile& rd, TOSTREAM* pOut );

static void OutputObjectSummary( cFCODatabaseFileIter& genreIter, const iFCONameTranslator* pNT, TOSTREAM* pOut, int margin );
static void OutputObjectDetail( cFCODatabaseFileIter& genreIter, const iFCONameTranslator* pNT, TOSTREAM* pOut );


static void OutputIterChildren  ( cDbDataSourceIter dbIter, const iFCOPropDisplayer* pPD, const iFCONameTranslator* pNT, TOSTREAM* pOut, bool fDetails );
static void OutputIterPeers     ( cDbDataSourceIter dbIter, const iFCOPropDisplayer* pPD, const iFCONameTranslator* pNT, TOSTREAM* pOut, bool fDetails );
//static void OutputIter          ( cDbDataSourceIter& dbIter, const iFCOPropDisplayer* pPD, TOSTREAM* pOut, bool fDetails );
static void PrintFCOShort		( const iFCO* pFCO, const iFCOPropDisplayer* pPD, const iFCONameTranslator* pNT, TOSTREAM* pOut, int margin );
//static void OutputSpecHeader( const cFCODbSpecIter& dbi, TOSTREAM* pOut );
//static void OutputDetailHeader( const cFCODbSpecIter& dbi, int nObjects, TOSTREAM* pOut );

static TSTRING util_Encode( const TSTRING& sIn )
{
    static cDisplayEncoder e;
    TSTRING sOut = sIn;
    e.Encode( sOut );
    return sOut;
}

//=========================================================================
// METHOD CODE
//=========================================================================

void cTextDBViewer::PrintDB( cFCODatabaseFile& rd, const TSTRING& strFilename )
{
    TOSTREAM* pOut;
    TOFSTREAM fileOut;
    bool fIsFile = false;

    if( strFilename == _T("-") )
    {
        pOut = &TCOUT;
    }
    else
    {
		//Gonna have to insert a lame hack here, since ostr.open DEMANDS a const char*!!
#ifdef _UNICODE
		char ncBuf[ iFSServices::TW_MAX_PATH ];
		wcstombs( ncBuf, strFilename.c_str(), sizeof(char));
		fileOut.open( ncBuf );
#else //narrow character mode
        fileOut.open( strFilename.c_str() );
#endif

        if( fileOut.is_open() )
        {
            pOut = &fileOut;
            fIsFile = true;
        }
        else
        {
            ASSERT( false );
			throw eArchiveOpen( strFilename, iFSServices::GetInstance()->GetErrString() );
        }
    }

    InitOStream( pOut );
    
    //
    // output header, just like it sez....
    //
    OutputDatabaseHeader( rd.GetHeader(), pOut );

    OutputObjectSummary( rd, pOut, DETAILS_MARGIN );

    OutputObjectDetail( rd, pOut );

    
    // we're done
    (*pOut) << g_sz79Dashes << endl;
    (*pOut) << TSS_GetString( cTW, tw::STR_END_OF_DB ) << endl << endl;
    (*pOut) << TSS_GetString( cTW, tw::STR_COPYRIGHT ) << endl;    

    if( fIsFile )
        static_cast<TOFSTREAM*>(pOut)->close();

    return;
}


///////////////////////////////////////////////////////////////////////////////
// OutputFCO
///////////////////////////////////////////////////////////////////////////////
void cTextDBViewer::OutputFCO( cDbDataSourceIter& dbIter, const iFCOPropDisplayer* pPD, const iFCONameTranslator* pNT, TOSTREAM* pOut, bool fDetails )
{    
    InitOStream( pOut );

    if( dbIter.HasFCOData() )
    {
		iFCO* pFCO = dbIter.CreateFCO();
        
		if( fDetails )
        {
            (*pOut) << TSS_GetString( cTW, tw::STR_OBJECT_NAME ) << _T(" ") << pNT->ToStringDisplay( pFCO->GetName() ).c_str() << endl << endl;
            DisplayFCOProps( pFCO, pPD, pOut );
            (*pOut) << endl << endl;
        }
        else
			PrintFCOShort( pFCO, pPD, pNT, pOut, DETAILS_MARGIN );

		pFCO->Release();
    }
}


//=========================================================================
//  UTIL FUNCTION CODE
//=========================================================================

void InitOStream( TOSTREAM* pOut )
{
    // align left
    (*pOut).flags( ( (*pOut).flags() & ~std::ios::adjustfield ) | std::ios::left );
}

///////////////////////////////////////////////////////////////////////////////
// PrintFCOShort
///////////////////////////////////////////////////////////////////////////////
static void PrintFCOShort( const iFCO* pFCO, const iFCOPropDisplayer* pPD, const iFCONameTranslator* pNT, TOSTREAM* pOut, int margin )
{
    ASSERT( pOut != 0);

    TSTRING strDetails;

    // 
    // output "<name of FCO>"\n\t<details of FCO>"
    //
    (*pOut) << _T(" ");
    (*pOut) << pNT->ToStringDisplay( pFCO->GetName() ).c_str();    
    (*pOut) << endl;
    (*pOut) << setw( margin ) << _T("");
    pPD->GetDetails( pFCO, strDetails );
    (*pOut) << strDetails;
    (*pOut) << _T("\n");
}


static void OutputIterChildren( cDbDataSourceIter dbIter, const iFCOPropDisplayer* pPD, const iFCONameTranslator* pNT, TOSTREAM* pOut, bool fDetails )
{
    dbIter.Descend();
    OutputIterPeers( dbIter, pPD, pNT, pOut, fDetails );
}

static void OutputIterPeers( cDbDataSourceIter dbIter, const iFCOPropDisplayer* pPD, const iFCONameTranslator* pNT, TOSTREAM* pOut, bool fDetails )
{
    for ( dbIter.SeekBegin(); ! dbIter.Done(); dbIter.Next() )
    {
        // TODO: this obviously needs to be expanded upon.
		cTextDBViewer::OutputFCO( dbIter, pPD, pNT, pOut, fDetails );

        if( dbIter.CanDescend() )
            OutputIterChildren( dbIter, pPD, pNT, pOut, fDetails );
    }
}


static void OutputDatabaseHeader( const cFCODbHeader& dbHeader, TOSTREAM* pOut )
{
    const int headerColumnWidth = 30;
    (*pOut) << TSS_GetString( cTW, tw::STR_DBPRINT_TITLE ) << endl << endl;
    
    (*pOut).width(headerColumnWidth);
    (*pOut) << TSS_GetString( cTW, tw::STR_DB_GENERATED_BY ) << util_Encode( dbHeader.GetCreator() ) << endl;
    
    TSTRING tstrDummy;
    int64 i64CreateTime = dbHeader.GetCreationTime();
    (*pOut).width(headerColumnWidth);
    (*pOut) << TSS_GetString( cTW, tw::STR_DB_CREATED_ON ) << cTWLocale::FormatTime( i64CreateTime, tstrDummy ).c_str() << endl;
        
    (*pOut).width(headerColumnWidth);
    (*pOut) << TSS_GetString( cTW, tw::STR_DB_LAST_UPDATE );

    int64 i64LastDBUTime = dbHeader.GetLastDBUpdateTime();
    if( i64LastDBUTime == 0 )
    {        
        (*pOut) << TSS_GetString( cTW, tw::STR_NEVER ) << endl << endl;
    }
    else
    {        
        (*pOut) << cTWLocale::FormatTime( i64LastDBUTime, tstrDummy ).c_str() << endl << endl;
    }

    OutputSectionDelimiter( tw::STR_DB_SUMMARY, pOut );

    (*pOut).width(headerColumnWidth);
    (*pOut) << TSS_GetString( cTW, tw::STR_HOST_NAME ) << dbHeader.GetSystemName().c_str() << endl;    
    (*pOut).width(headerColumnWidth);
    (*pOut) << TSS_GetString( cTW, tw::STR_HOST_IP ) << dbHeader.GetIPAddress() << endl;

    
// only output host ID on UNIX systems
#if IS_UNIX
    (*pOut).width(headerColumnWidth);    
    (*pOut) << TSS_GetString( cTW, tw::STR_HOST_ID );

    if(! dbHeader.GetHostID().empty() )
        (*pOut) << dbHeader.GetHostID() << endl;
    else
        (*pOut) << TSS_GetString( cTW, tw::STR_NONE ) << endl;
#endif

    (*pOut) << setw(headerColumnWidth)
            << TSS_GetString( cTW, tw::STR_POLICY_FILE_USED ) 
            << util_Encode( dbHeader.GetPolicyFilename() )
            << endl;

    (*pOut) << setw(headerColumnWidth)
            << TSS_GetString( cTW, tw::STR_CONFIG_FILE_USED ) 
            << util_Encode( dbHeader.GetConfigFilename() )
            << endl;

    (*pOut) << setw(headerColumnWidth)
            << TSS_GetString( cTW, tw::STR_DB_FILE_USED ) 
            << util_Encode( dbHeader.GetDBFilename() )
            << endl;

    (*pOut) << setw(headerColumnWidth)
            << TSS_GetString( cTW, tw::STR_CMD_LINE_USED ) 
            << util_Encode( dbHeader.GetCommandLineParams() )
            << endl
            << endl;
}

#if 0 
// The rules summary is not working due to changes in the databasse infrastructure.  Until 
// we hear different from the product manager, it is out of here.
static void OutputRulesSummary(const cFCODatabaseFileIter& genreIter, const iFCONameTranslator* pNT, TOSTREAM* pOut )
{
    const cHierDatabase& db = genreIter.GetDb();  

    // these should add up to 79
    const int nameWidth = 50;
    const int numObjectsWidth = 10; 

    (*pOut) << g_sz79Dashes << endl;
    (*pOut) << TSS_GetString( cTW, tw::STR_RULE_SUMMARY ) << endl;
    (*pOut) << g_sz79Dashes << endl << endl;

    (*pOut).width(nameWidth);
    (*pOut) << TSS_GetString( cTW, tw::STR_RULE_NAME );
    (*pOut).width(numObjectsWidth);
    (*pOut) << TSS_GetString( cTW, tw::STR_OBJECTS ) << endl;

    (*pOut) << setw( nameWidth ) << _T("---------");
    (*pOut) << setw( numObjectsWidth ) << _T("-------");
    (*pOut) << endl;

    // output spec stats
    cFCOSpecListCanonicalIter specIter( genreIter.GetSpecList() );

    // iterate over all database elements 
    int nTotalObjects = 0;
    for( specIter.SeekBegin(); !specIter.Done(); specIter.Next() )
    {
        (*pOut) << setw( nameWidth ) << specIter.Spec()->GetName();

        // if the name is too long, put it on its own line
        if( specIter.Spec()->GetName().length() >= nameWidth )
        {
            (*pOut) << endl;

            // output space holder
            (*pOut).width(nameWidth);
            (*pOut) << _T("");
        }

        (*pOut).width(numObjectsWidth);
        (*pOut) << _T("TODO");
        //(*pOut) << specIter.GetFCOSet()->Size(); TODO: Do this right
        (*pOut) << endl;
    }
    
    (*pOut) << endl;

	// NOTE 4 Jan 99 mdb -- this used to use dbHeader.GetTotalObjectsScanned() but it was not always consistent with the 
	//		total number of objects in the database. TODO -- if we are really not going to use this number, then we should 
	//		not store it in the database at all.
	//
    (*pOut) << TSS_GetString( cTW, tw::STR_TOTAL_NUM_FILES );
    //(*pOut) << _T(" ") << nTotalObjects << endl;
    (*pOut) << _T("TODO") << endl;
    (*pOut) << endl;
}
#endif



void OutputSectionDelimiter( int nString, TOSTREAM* pOut )
{
    (*pOut) << g_sz79Equals << endl;
    (*pOut) << TSS_GetString( cTW, nString ) << endl;
    (*pOut) << g_sz79Equals << endl << endl;
}

void OutputGenreDelimiter( cGenre::Genre g, TOSTREAM* pOut )
{
    (*pOut) << g_sz79Dashes << endl;
    (*pOut) << _T("# ") << TSS_GetString( cTW, tw::STR_SECTION );
    (*pOut) << _T(": ") << cGenreSwitcher::GetInstance()->GenreToString( g, true ) << endl;
    (*pOut) << g_sz79Dashes << endl << endl;
}

void OutputObjectSummary( cFCODatabaseFile& rd, TOSTREAM* pOut, int margin )
{    
    OutputSectionDelimiter( tw::STR_OBJECT_SUMMARY, pOut );

    cFCODatabaseFileIter genreIter(rd);
    for (genreIter.SeekBegin(); !genreIter.Done(); genreIter.Next())
    {
        cGenreSwitcher::GetInstance()->SelectGenre( (cGenre::Genre) genreIter.GetGenre() );
        
        iFCONameTranslator* pNT = iTWFactory::GetInstance()->GetNameTranslator();
        
        OutputGenreDelimiter( (cGenre::Genre) genreIter.GetGenre(), pOut );

        // Currently we don't have a rule summary.  See function definition below
        //OutputRulesSummary( genreIter, pNT, pOut);

        OutputObjectSummary( genreIter, pNT, pOut, margin );
    }
}

void OutputObjectSummary( cFCODatabaseFileIter& genreIter, const iFCONameTranslator* pNT, TOSTREAM* pOut, int margin )
{
    const cDbDataSourceIter dbIter( &genreIter.GetDb() );
    ASSERT(dbIter.AtRoot());

    (*pOut) << setw( margin ) << _T("");
    TSTRING strBuf;
    (*pOut) << genreIter.GetGenreHeader().GetPropDisplayer()->GetDetailsHeader( strBuf, margin ).c_str() << endl;

    OutputIterPeers( dbIter, genreIter.GetGenreHeader().GetPropDisplayer(), pNT, pOut, false );
    (*pOut) << endl;
}


void OutputObjectDetail( cFCODatabaseFile& rd, TOSTREAM* pOut )
{
    OutputSectionDelimiter( tw::STR_OBJECT_DETAIL, pOut );

    cFCODatabaseFileIter genreIter(rd);
    for (genreIter.SeekBegin(); !genreIter.Done(); genreIter.Next())
    {
        cGenreSwitcher::GetInstance()->SelectGenre( (cGenre::Genre) genreIter.GetGenre() );        
        iFCONameTranslator* pNT = iTWFactory::GetInstance()->GetNameTranslator();
        
        OutputGenreDelimiter( (cGenre::Genre) genreIter.GetGenre(), pOut );

        OutputObjectDetail( genreIter, pNT, pOut );
    }
}


void OutputObjectDetail( cFCODatabaseFileIter& genreIter, const iFCONameTranslator* pNT, TOSTREAM* pOut )
{
    const cDbDataSourceIter dbIterDetails(&genreIter.GetDb());
 
    OutputIterPeers( dbIterDetails, genreIter.GetGenreHeader().GetPropDisplayer(), pNT, pOut, true);
    (*pOut) << endl;
}


/*
static void OutputDetailHeader( const cFCODbSpecIter &dbi, int nObjects, TOSTREAM* pOut )
{
    (*pOut) << g_sz79Dashes << _T( "\n" );
    (*pOut) << TSS_GetString( cTW, tw::STR_FILE_DETAIL ) << endl;
    
    TOSTRINGSTREAM ostr2;
    ostr2 << TSS_GetString( cTW, tw::STR_RULE_NAME ) << _T(": ") << dbi.GetSpec()->GetName().c_str();
    ostr2 << _T( " (" ) << dbi.GetSpec()->GetStartPoint().AsString().c_str() << _T( ")" ) << endl;
    (*pOut) << ostr2.str().c_str();

    (*pOut) << TSS_GetString( cTW, tw::STR_TOTAL_NUM_FILES ) << _T(" ") << nObjects << endl;

    (*pOut) << g_sz79Dashes << endl << endl;
}
*/

/*
static void OutputSpecHeader( const cFCODbSpecIter &dbi, TOSTREAM* pOut )
{
    (*pOut) << g_sz79Dashes << _T( "\n" );
    
    TOSTRINGSTREAM ostr2;
    ostr2 << TSS_GetString( cTW, tw::STR_RULE_NAME ) << _T(": ") << dbi.GetSpec()->GetName().c_str();
    ostr2 << _T( " (" ) << dbi.GetSpec()->GetStartPoint().AsString().c_str() << _T( ")" ) << endl;
    (*pOut) << ostr2.str().c_str();

    (*pOut) << g_sz79Dashes << _T( "\n" );
}
*/


void DisplayFCOProps( const iFCO* const pFCO, const iFCOPropDisplayer* pPD, TOSTREAM* pOut )
{
    ASSERT( pOut != 0);
    ASSERT( pFCO != 0);
   
    const int attrNameWidth = 24;
    const int attrValueWidth = 28;

    // output header
    // TODO: make these constants into enums
    (*pOut).width( attrNameWidth );
    (*pOut) << TSS_GetString( cTW, tw::STR_ATTRIBUTES );
    (*pOut).width( attrValueWidth );
    (*pOut) << TSS_GetString( cTW, tw::STR_ATTR_VALUE );
    (*pOut) << endl;
    
    (*pOut).width( attrNameWidth );
    (*pOut) << _T("-------------");
    (*pOut).width( attrValueWidth );
    (*pOut) << _T("-----------");
    (*pOut) << endl;

    int iNumProps = pFCO->GetPropSet()->GetNumProps();
    for( int j = 0; j < iNumProps; j++ )
    {
        // output if prop is in FCO, and prop is not cFCOUndefinedProp
        if( 
            pFCO->GetPropSet()->GetValidVector().ContainsItem( j )
            &&
            pFCO->GetPropSet()->GetPropAt( j )->GetType() != cFCOUndefinedProp::GetInstance()->GetType()
          )
        {
            TSTRING strProp;

            // output property name
            (*pOut).width( attrNameWidth );
            (*pOut) << pFCO->GetPropSet()->GetPropName(j);
            (*pOut).width( attrValueWidth );
            (*pOut) << pPD->PropAsString( pFCO, j, attrNameWidth, 0 );
            (*pOut) << _T("\n");
        }
    }
}

