//
// 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.
//
// policyupdate.cpp
#include "stdtripwire.h"
#include "policyupdate.h"
#include "tw/fcoreport.h"
#include "core/errorbucketimpl.h"
#include "integritycheck.h"
#include "updatedb.h"
#include "core/usernotify.h"
#include "fco/iterproxy.h"
#include "fco/fcopropset.h"
#include "tripwireutil.h"
#include "tripwirestrings.h"
#include "fco/twfactory.h"
#include "fco/fconametranslator.h"
#include "fco/genreswitcher.h"

///////////////////////////////////////////////////////////////////////////////
// util_FCOInSpecList -- given a spec list and an fco name, return true if the 
//		name belongs in one of the specs
///////////////////////////////////////////////////////////////////////////////
static bool util_FCOInSpecList( const cFCOSpecList& specList, const cFCOName& name )
{
	cFCOSpecListCanonicalIter iter( specList );
	for( iter.SeekBegin(); ! iter.Done(); iter.Next() )
	{
		if( iter.Spec()->SpecContainsFCO( name ) )
			return true;
	}
	return false;
}


///////////////////////////////////////////////////////////////////////////////
// util_PruneExtraObjects -- checks every object in the database against the
//		provided spec set and adds them to the vector if they don't fall under any specs.
///////////////////////////////////////////////////////////////////////////////
static void util_PruneExtraObjects( cDbDataSourceIter iter, cFCOSpecListCanonicalIter specIter, 
									bool bFirst = true )
{
	if( ! bFirst )
	{
		iter.Descend();
	}


	for( iter.SeekBegin(); ! iter.Done(); /*iter.Next()*/ )
	{
		// process our children first...
		//
		if( iter.CanDescend() )
		{
			util_PruneExtraObjects( iter, specIter, false );
		}

		
		if( iter.HasFCOData() )
		{
			// figure out if this fco fits in the new policy
			//
			bool bFoundSpec = false;
			for( specIter.SeekBegin(); ! specIter.Done(); specIter.Next() )
			{
				if( specIter.Spec()->SpecContainsFCO( iter.GetName() ) )
				{
					// this doesn't belong in the new db; remove it!
					//
					bFoundSpec = true;
					break;
				}
			}
			if( ! bFoundSpec )
			{
				TW_NOTIFY_VERBOSE(	_T("--- %s%s\n"), 
									TSS_GetString( cTripwire, tripwire::STR_NOTIFY_DB_REMOVING ).c_str(),
									iTWFactory::GetInstance()->GetNameTranslator()->ToStringDisplay
										( iter.GetName() ).c_str() );
				iter.RemoveFCOData();
			}
		}
		//
		// remove the infrastructure, if appropriate...
		// this is also where the iterator incrementing occurs, so we need to 
		// be careful with the Next()s
		//
		if( ! iter.HasFCOData() )
		{
			if( iter.CanDescend() && iter.CanRemoveChildArray() )
				iter.RemoveChildArray();
			if( ! iter.CanDescend() )
				iter.RemoveFCO();
			else
				iter.Next();
		}
		else
			iter.Next();
	}
}


///////////////////////////////////////////////////////////////////////////////
// ctor
///////////////////////////////////////////////////////////////////////////////
cPolicyUpdate::cPolicyUpdate(	cGenre::Genre genreNum, const cFCOSpecList& oldPolicy, const cFCOSpecList& newPolicy,
				cHierDatabase& db, cErrorBucket* pBucket )
:	mOldPolicy	( oldPolicy ),
	mNewPolicy	( newPolicy ),
	mDb			( db ),
	mGenre		( genreNum ),
	mpBucket	( pBucket )
{

}

///////////////////////////////////////////////////////////////////////////////
// Execute
///////////////////////////////////////////////////////////////////////////////
bool cPolicyUpdate::Execute( uint32 flags )  // throw (eError)
{
	// here is my current idea for the algorithm: first, do an integrity check with the new policy on the database and
	// a special flag passed to Execute() to modify what properties are checked. Then, take the resulting
	// report print to the user its contents, minus any violations that don't make sense to report (see my EPS for
	// details on what doesn't get displayed) Finally, do a database update with the full report, making sure that 
	// properties that were from the policy are also removed from existing FCOs in the database ( I might need to 
	// add a parameter to DbUpdate to accomodate that).
	//

	// first, do the integrity check with the new database...
	//
	TW_NOTIFY_NORMAL(	TSS_GetString( cTripwire, tripwire::STR_PU_PROCESSING_GENRE ).c_str(),
						cGenreSwitcher::GetInstance()->GenreToString( cGenreSwitcher::GetInstance()->CurrentGenre(), true ) );
	TW_NOTIFY_NORMAL(	TSS_GetString( cTripwire, tripwire::STR_PU_INTEGRITY_CHECK ).c_str() );
	//
	iFCONameTranslator* pTrans = iTWFactory::GetInstance()->GetNameTranslator();
	bool bResult = true;	// begin by assuming success
	cFCOReport report;
	cIntegrityCheck ic( mGenre, mNewPolicy, mDb, report, mpBucket );
    
	//
	// set up flags for the property calculator and iterators
	//  
    uint32 icFlags = cIntegrityCheck::FLAG_COMPARE_VALID_PROPS_ONLY  | 
				     cIntegrityCheck::FLAG_INVALIDATE_EXTRA_DB_PROPS |
				     cIntegrityCheck::FLAG_SET_NEW_PROPS;

	if( flags & FLAG_ERASE_FOOTPRINTS_PU )
	{
        icFlags |= cIntegrityCheck::FLAG_ERASE_FOOTPRINTS_IC;
	}

	ic.Execute( icFlags );
		//TODO-- the second flag I just added probably makes the flag to cUpdateDb::Execute() unnecessary;
		//		I should probably remove it.


	//
	// Note that I will probably end up making two passes through the report (one for reporting to the user and one for 
	//		the db update). I don't think there is a clean way to do this in a single report pass, other than rewriting 
	//		db update, which I am not going to do.
	//
	cFCOReportSpecIter specIter( report, mGenre );
	for( specIter.SeekBegin(); ! specIter.Done(); specIter.Next() )
	{
		// notify the user of what we are doing...
		TW_NOTIFY_VERBOSE(	_T("%s%s\n"), 
							TSS_GetString( cTripwire, tripwire::STR_NOTIFY_PROCESSING ).c_str(),
							pTrans->ToStringDisplay( specIter.GetSpec()->GetStartPoint() ).c_str() );

		//
		// report objects in the added set should only be reported if they fall in a spec
		// in the old policy
		//
		iFCOSet*					pAddSet = specIter.GetAddedSet();
	    const cIterProxy<iFCOIter>	pIter	= pAddSet->GetIter();
		for( pIter->SeekBegin(); ! pIter->Done(); pIter->Next() )
		{
			if( util_FCOInSpecList( mOldPolicy, pIter->FCO()->GetName() ) )
			{
				// this is an error that should be reported to the user.
                ePolicyUpdateAddedFCO e( pTrans->ToStringDisplay( pIter->FCO()->GetName() ) );
				if( (flags & ANAL) == 0 )
					e.SetFlags( eError::NON_FATAL );
				else
					e.SetFlags( eError::SUPRESS_THIRD_MSG );
				mpBucket->AddError( e );
				bResult = false;
			}
		}
		//
		// objects in the removed set should only be reported if they fall in a spec in the
		// new policy..
		//
		iFCOSet*					pRmSet	= specIter.GetRemovedSet();
	    const cIterProxy<iFCOIter>	pRmIter	= pRmSet->GetIter();
		for( pRmIter->SeekBegin(); ! pRmIter->Done(); pRmIter->Next() )
		{
			if( util_FCOInSpecList( mNewPolicy, pRmIter->FCO()->GetName() ) )
			{
				// this is an error that should be reported to the user.
                ePolicyUpdateRemovedFCO e( pTrans->ToStringDisplay( pRmIter->FCO()->GetName() ) );

				if( (flags & ANAL) == 0 )
					e.SetFlags( eError::NON_FATAL );
				else
					e.SetFlags( eError::SUPRESS_THIRD_MSG );
				mpBucket->AddError( e );
				bResult = false;
			}
		}
		//
		// every object in the changed set should be document....
		//
		cFCOReportChangeIter changeIter( specIter );
		for( changeIter.SeekBegin(); ! changeIter.Done(); changeIter.Next() )
		{
			//----------------------------------------------------------------
			// I need to construct a string that contains all of the 
			// property names that have changed. 
			//----------------------------------------------------------------
			TSTRING badPropStr = TSS_GetString( cTripwire, tripwire::STR_PU_BAD_PROPS );
			
			badPropStr += pTrans->ToStringDisplay( changeIter.GetOld()->GetName() );
			const cFCOPropVector& changeVector = changeIter.GetChangeVector();
			for(int i=0; i < changeVector.GetSize(); i++)
			{
				if(changeVector.ContainsItem(i))
				{
					badPropStr += _T("\n> "); 
					badPropStr += changeIter.GetOld()->GetPropSet()->GetPropName(i);
				}
			}

			// add this to the error bucket
			ePolicyUpdateChangedFCO e( badPropStr );
			if( (flags & ANAL) == 0 )
				e.SetFlags( eError::NON_FATAL );
			else
				e.SetFlags( eError::SUPRESS_THIRD_MSG );
			mpBucket->AddError( e );
			bResult = false;
		}
	}
	//
	// now, we will update the database with everything in the report...
	// TODO -- don't do this if the anal flag was passed in
	//
	TW_NOTIFY_NORMAL( TSS_GetString( cTripwire, tripwire::STR_PU_UPDATE_DB ).c_str() );
	//
	cUpdateDb update( mDb, report, mpBucket );
    
    uint32 updateDBFlags = cUpdateDb::FLAG_REPLACE_PROPS;
    if( flags & FLAG_ERASE_FOOTPRINTS_PU ) 
        updateDBFlags |= cUpdateDb::FLAG_ERASE_FOOTPRINTS_UD;

	update.Execute( updateDBFlags );

	
	// the last thing that we have to do is to remove everything that is still
	// in the database that does not belong in the new database (ie -- does not fall under any
	// new rules)
	//
	// TODO -- is there any way to do this that does not involve iterating over the entire database?
	// TODO -- I should probably write a general-purpose database iterator class to do this...
	//

	TW_NOTIFY_NORMAL( TSS_GetString( cTripwire, tripwire::STR_PU_PRUNING ).c_str() );
	//
	cDbDataSourceIter i( &mDb );
    i.SetErrorBucket(mpBucket);
    
    if( flags & FLAG_ERASE_FOOTPRINTS_PU )
        i.SetIterFlags( iFCODataSourceIter::DO_NOT_MODIFY_OBJECTS );

	const cFCOSpecListCanonicalIter newPolIter( mNewPolicy );
	util_PruneExtraObjects( i, newPolIter );

	return bResult;
}

