//
// 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.
//
///////////////////////////////////////////////////////////////////////////////
// dbdatasource.cpp
//

#include "stdtw.h"
#include "dbdatasource.h"
#include "fco/twfactory.h"
#include "fco/fconameinfo.h"
#include "core/archive.h"
#include "core/serializerimpl.h"
#include "fco/fcopropset.h"
#include "fco/fco.h"
#include "fco/fcodatasourceiter.h"

// TODO -- get rid of this include somehow!
#include "fco/genreswitcher.h"

///////////////////////////////////////////////////////////////////////////////
// ctor
///////////////////////////////////////////////////////////////////////////////
cDbDataSourceIter::cDbDataSourceIter(cHierDatabase* pDb, int genreNum )
:	iFCODataSourceIter	(),
	mDbIter				(pDb),
    mFlags              (0),
    mpErrorBucket       (0)
{
	//
	// remember the fco creation function...
	// TODO -- Note that this couples this file with cGenreSwitcher; perhaps this should take the fco creation 
	//		function instead...
	//
	if( genreNum == -1 )
		genreNum   = cGenreSwitcher::GetInstance()->CurrentGenre();
	mFCOCreateFunc = cGenreSwitcher::GetInstance()->GetFactoryForGenre( (cGenre::Genre)genreNum )->GetCreateFunc();

#ifdef _DEBUG
	//
	// make some assertions about the current genre's name info
	//
	iFCONameInfo* pNameInfo = cGenreSwitcher::GetInstance()->GetFactoryForGenre( (cGenre::Genre)genreNum )->GetNameInfo();
	ASSERT( pDb->IsCaseSensitive()   == pNameInfo->IsCaseSensitive()   );
	ASSERT( pDb->GetDelimitingChar() == pNameInfo->GetDelimitingChar() );
#endif //#ifdef _DEBUG
}

///////////////////////////////////////////////////////////////////////////////
// cDbDataSourceIter
///////////////////////////////////////////////////////////////////////////////
cDbDataSourceIter::cDbDataSourceIter( const cDbDataSourceIter& rhs )
:	mDbIter			( rhs.mDbIter ),
	mFCOCreateFunc	( rhs.mFCOCreateFunc ),
    mFlags          ( rhs.mFlags ),
    mpErrorBucket   ( rhs.mpErrorBucket )
{
	
}

///////////////////////////////////////////////////////////////////////////////
// operator=
///////////////////////////////////////////////////////////////////////////////
cDbDataSourceIter& cDbDataSourceIter::operator=( const cDbDataSourceIter& rhs )
{
	mDbIter			= rhs.mDbIter ;
	mFCOCreateFunc	= rhs.mFCOCreateFunc ;
    mFlags          = rhs.mFlags;
    mpErrorBucket   = rhs.mpErrorBucket;
	return (*this);
}


///////////////////////////////////////////////////////////////////////////////
// dtor
///////////////////////////////////////////////////////////////////////////////
cDbDataSourceIter::~cDbDataSourceIter()	
{

}

///////////////////////////////////////////////////////////////////////////////
// CreateCopy
///////////////////////////////////////////////////////////////////////////////
iFCODataSourceIter* cDbDataSourceIter::CreateCopy() const 
{
	//TODO -- implement this with a memory pool?
	return new cDbDataSourceIter(*this);
}


///////////////////////////////////////////////////////////////////////////////
// GetName
///////////////////////////////////////////////////////////////////////////////
cFCOName cDbDataSourceIter::GetName() const 
{
	ASSERT( ! Done() );

	cFCOName rtn( mDbIter.GetCwd() );
	rtn.Push( mDbIter.GetName() );
	return rtn;
}


///////////////////////////////////////////////////////////////////////////////
// CreateFCO
//
// note that cHierDatabase::GetData() will throw if we are Done() or the node
//		doesn't have data
///////////////////////////////////////////////////////////////////////////////
iFCO* cDbDataSourceIter::CreateFCO() //throw (eError)
{
	ASSERT( ! Done() );
	//
	// ok, first, we will create the new fco...
	//
	iFCO* pFCO = static_cast<iFCO*>( (*mFCOCreateFunc)() );
	pFCO->SetName( GetName() );
	try
	{
		ASSERT( mDbIter.HasData() );
		int32 length;
		int8* pData = mDbIter.GetData(length);
		//
		// associate a serializer with this memory and read in the property set...
		//
		cFixedMemArchive	arch( pData, length );
		cSerializerImpl		ser	( arch, cSerializerImpl::S_READ );
		ser.Init();
		ser.ReadObject( pFCO->GetPropSet() );
		ser.Finit();
	}
	catch(...)
	{
		pFCO->Release();
		throw;
	}

	return pFCO;
}

///////////////////////////////////////////////////////////////////////////////
// HasFCOData
///////////////////////////////////////////////////////////////////////////////
bool cDbDataSourceIter::HasFCOData() const
{
	ASSERT( ! Done() );
	if( Done() )
		return false;

	return ( mDbIter.HasData() );
}


///////////////////////////////////////////////////////////////////////////////
// SeekToFCO
///////////////////////////////////////////////////////////////////////////////
void cDbDataSourceIter::SeekToFCO(const cFCOName& name, bool bCreatePeers) //throw(eFCODataSourceIter) 
{
	ASSERT( name.GetSize() > 0 );
	cFCOName parentName = name;
	parentName.Pop();
	if(! SeekToDirectory( parentName, false ) )
	{
		// make myself Done() and return...
		//
		while( ! Done() ) Next();
		return;
	}
	//
	// note that this is Done() if it fails, so we are good...
	//
	mDbIter.SeekTo( name.GetShortName() ) ;
}

///////////////////////////////////////////////////////////////////////////////
// AddFCO
///////////////////////////////////////////////////////////////////////////////
void cDbDataSourceIter::AddFCO( const TSTRING& shortName, const iFCO* pFCO ) //throw (eFCODataSourceIter, eError)
{
	mDbIter.CreateEntry( shortName );
	//
	// we are now pointing at the entry we just created, so now let's add the data...
	//
	SetFCOData( pFCO );
}

///////////////////////////////////////////////////////////////////////////////
// RemoveFCO
///////////////////////////////////////////////////////////////////////////////
void cDbDataSourceIter::RemoveFCO() //throw (eError)
{
	if( (! Done()) && (! HasFCOData()) )
	{
		mDbIter.DeleteEntry();
	}
	else
	{
		// this was called in inappropriate circumastances
		ASSERT( false );
	}
}

///////////////////////////////////////////////////////////////////////////////
// RemoveFCOData
///////////////////////////////////////////////////////////////////////////////
void cDbDataSourceIter::RemoveFCOData() //throw (eError)
{
	ASSERT( ! Done() );
	ASSERT( HasFCOData() );
	if( HasFCOData() )
	{
		mDbIter.RemoveData();
	}
}

///////////////////////////////////////////////////////////////////////////////
// SetFCOData
///////////////////////////////////////////////////////////////////////////////
void cDbDataSourceIter::SetFCOData( const iFCO* pFCO ) //throw (eError)
{
	ASSERT( ! Done() );
	if( Done() )
	{
		throw eHierDatabase( _T("Attempt to set FCO data when the iterator is done.") );
	}
	// TODO -- assert and throw if the fco's type is not the same as our creation function
	//		There is no way to do this through the serializable interface, but when there is,
	//		we should do the above assertion.
	//
	// if data already exists here, we first remove it; 
	//
	if( mDbIter.HasData() )
	{
		mDbIter.RemoveData();
	}
	//
	// write the fco's property set to a memory archive...
	//
	// TODO -- does this need to be static?
	static cMemoryArchive arch;
	arch.Seek			( 0, cBidirArchive::BEGINNING );
	cSerializerImpl ser	(arch, cSerializerImpl::S_WRITE);
	ser.Init();
	ser.WriteObject		( pFCO->GetPropSet() );
	ser.Finit();
	//
	// write this to the archive...
	//
	mDbIter.SetData( arch.GetMemory(), arch.CurrentPos() );
}

///////////////////////////////////////////////////////////////////////////////
// AddChildArray
///////////////////////////////////////////////////////////////////////////////
void cDbDataSourceIter::AddChildArray() //throw (eError)
{
	ASSERT( ! Done() );
	ASSERT( ! CanDescend() );

	mDbIter.CreateChildArray();
}

///////////////////////////////////////////////////////////////////////////////
// RemoveChildArray
///////////////////////////////////////////////////////////////////////////////
void cDbDataSourceIter::RemoveChildArray()  //throw (eError)
{
	//NOTE -- the hier db iter does all of the proper asserting...
	//
	mDbIter.DeleteChildArray();
}

///////////////////////////////////////////////////////////////////////////////
// CreatePath
///////////////////////////////////////////////////////////////////////////////
void cDbDataSourceIter::CreatePath( const cFCOName& name ) //throw (eError)
{
	ASSERT( name.GetSize() > 0 );

	cFCOName parentName = name;
	parentName.Pop();
	SeekToDirectory( parentName, true );
	if( ! mDbIter.SeekTo( name.GetShortName() ) )
	{
		mDbIter.CreateEntry( name.GetShortName() );
	}
}


///////////////////////////////////////////////////////////////////////////////
// SeekToDirectory
///////////////////////////////////////////////////////////////////////////////
bool cDbDataSourceIter::SeekToDirectory( const cFCOName& parentName, bool bCreate )
{
	cDebug d( "cDbDataSourceIter::SeekToDirectory" );
	//
	// the first task is to ascend until we are in a directory that we can descend into to 
	// reach parentName...
	//
	cFCOName curParent = GetParentName();
	d.TraceDebug( _T("Entering... Seeking to %s (cwd = %s)\n"), parentName.AsString().c_str(), curParent.AsString().c_str() );
	int ascendCount ;
	switch( curParent.GetRelationship( parentName ) )
	{
		case cFCOName::REL_BELOW:
			//
			// we must ascend...
			//
			ascendCount = curParent.GetSize() - parentName.GetSize();
			d.TraceDetail( _T("\tAscending %d times...\n"), ascendCount );
			ASSERT( ascendCount > 0 );
			for( ; ascendCount > 0; ascendCount-- )
				Ascend();
			break;
		case cFCOName::REL_ABOVE:
			//
			// we are above the needed directory; nothing else to do here...
			//
			d.TraceDetail( _T("\tAbove; not ascending...\n") );
			break;
		case cFCOName::REL_EQUAL:
			//
			// we need to do nothing else here...
			//
			d.TraceDetail( _T("\tEqual; doing nothing...\n") );
			SeekBegin();
			return true;
		case cFCOName::REL_UNRELATED:
			//
			// we have to go all the way to the root...
			//
			d.TraceDetail( _T("\tUnrelated; seeking to root...\n") );
			SeekToRoot();
			break;
		default:
			// unreachable
			ASSERT( false );
			return false;
	}

	curParent = GetParentName();
	if( parentName.GetSize() == curParent.GetSize() )
		return true;
	//
	// now we will descend to the parent directory we are interested in...
	//
	cFCOName::iterator i(parentName);
	i.SeekTo( curParent.GetSize() );
	for(; (! i.Done()); i.Next() )
	{
		if( ! mDbIter.SeekTo( i.GetName() ) )
		{
			// this needs to be created!
			if( bCreate )
				mDbIter.CreateEntry( i.GetName() );
			else
				return false;
		}
		//
		// create the child array and descend
		//
		if( ! mDbIter.CanDescend() )
		{
			if( bCreate )
				mDbIter.CreateChildArray();
			else
				return false;
		}
		mDbIter.Descend();
	}
	return true;
}


