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

#include "stdtw.h"
#include "fcodatabasefile.h"
#include "core/archive.h"
#include "core/serializer.h"
#include "core/serializerutil.h"
#include "fco/fcogenre.h"
#include "fco/genreswitcher.h"
#include "fco/twfactory.h"
#include "fco/fconameinfo.h"
#include "core/fileheader.h"
#include "core/fsservices.h"
#include "db/blockrecordfile.h"


// TODO: May localizable strings need to be moved to string table

IMPLEMENT_TYPEDSERIALIZABLE(cFCODatabaseFile, _T("cFCODatabaseFile"), 0, 1)


cFCODatabaseFile::tEntry::tEntry( cGenre::Genre genre )
//TODO -- ugh, this sucks! I need to add another interface to the database!
:	mDb (	cGenreSwitcher::GetInstance()->GetFactoryForGenre((cGenre::Genre)genre)->GetNameInfo()->IsCaseSensitive(),
			cGenreSwitcher::GetInstance()->GetFactoryForGenre((cGenre::Genre)genre)->GetNameInfo()->GetDelimitingChar()
		),
	mGenre( genre )
{
}

cFCODatabaseFile::cFCODatabaseFile()
#ifdef _DEBUG
:	mFileName( _T("Unknown file name") )
#else
:	mFileName( _T("") )  // If we don't know the filename, lets just not have one in release mode.
#endif
{
}

cFCODatabaseFile::~cFCODatabaseFile()
{
	for( DbList::iterator i = mDbList.begin(); i != mDbList.end(); i++ )
	{
		delete *i;
	}
	mDbList.clear();
}

///////////////////////////////////////////////////////////////////////////////
// Read
///////////////////////////////////////////////////////////////////////////////
void cFCODatabaseFile::Read(iSerializer* pSerializer, int32 version)
{
	if (version > Version())
		ThrowAndAssert(eSerializerVersionMismatch(_T("Database Read")));
    
	//
	// read the db header..
	//
    pSerializer->ReadObject( &mHeader );

	int32 numGenre;
	pSerializer->ReadInt32( numGenre );
	for( int i=0; i < numGenre; i++ )
	{
		// read the genre number and throw if it is incorrect
		//
		int32 iGenre;
		pSerializer->ReadInt32( iGenre );
        cGenre::Genre genre = (cGenre::Genre) iGenre;

        if( ! cGenreSwitcher::GetInstance()->IsGenreRegistered( genre ) )
		{
	        throw eSerializerInputStreamFmt(_T("Encountered unknown genre.  Can not read database on this platform."), mFileName, eSerializer::TY_FILE);
		}

		mDbList.push_back( new tEntry( genre ) );
		tEntry& entry = *(mDbList.back());
		//
		// read the db genre header..
		//
		pSerializer->ReadObject( &entry.mGenreHeader );
		//
		// get the spec list
		//
		pSerializer->ReadObject( &entry.mSpecList );
		//
		// get the database data
		//
		int32 fileSize;
		pSerializer->ReadInt32( fileSize );
		//
		// write the hier database into a temp file...
		//
		cLockedTemporaryFileArchive* pArch = new cLockedTemporaryFileArchive();
		pArch->OpenReadWrite();

		cSerializerUtil::Copy( pArch, pSerializer, fileSize );
		//
		// associate the database with this file...
		//
		entry.mDb.Open( pArch );
	}
}

///////////////////////////////////////////////////////////////////////////////
// Write
///////////////////////////////////////////////////////////////////////////////
void cFCODatabaseFile::Write(iSerializer* pSerializer) const //throw( eFCODbFileTooBig )
{    
	//
	// write the db header..
	//
    pSerializer->WriteObject( &mHeader );

	// iterate through the genres, writing each one...
	//
	pSerializer->WriteInt32( mDbList.size() );
	//
	// TODO -- the database is not really const-correct; therefore I have the sick casts below...
	//
	for( DbList::iterator i = const_cast<DbList*>(&mDbList)->begin(); i != const_cast<DbList*>(&mDbList)->end(); i++ )
	{
		pSerializer->WriteInt32		( (*i)->mGenre );
		pSerializer->WriteObject	( &(*i)->mGenreHeader );
		pSerializer->WriteObject	( &(*i)->mSpecList );
		//
		// copy the database's data into the archive...
		//
		(*i)->mDb.Flush();
		cBidirArchive* pDbArch = (*i)->mDb.GetArchive();
		pDbArch->Seek( 0, cBidirArchive::BEGINNING );

        // TODO:BAM -- eventually we should write it as a 64 bit,
        // but that would change the db format
        if( pDbArch->Length() > TSS_INT32_MAX )
            throw eFCODbFileTooBig();

        pSerializer->WriteInt32( static_cast<int32>( pDbArch->Length() ) );
		cSerializerUtil::Copy( pSerializer, pDbArch, pDbArch->Length() );
	}
}

///////////////////////////////////////////////////////////////////////////////
// GetFileHeaderID()
///////////////////////////////////////////////////////////////////////////////
static cFileHeaderID gFCODatabaseFileHeaderID(_T("cFCODatabaseFile"));

const cFileHeaderID& cFCODatabaseFile::GetFileHeaderID()
{
    return gFCODatabaseFileHeaderID;
}


///////////////////////////////////////////////////////////////////////////////
// AddGenre
///////////////////////////////////////////////////////////////////////////////
void cFCODatabaseFile::AddGenre( cGenre::Genre genreId, cFCODatabaseFileIter* pIter )
{
	//
	// first, lets make sure this genre doesn't exist...
	//
	for( DbList::iterator i = mDbList.begin(); i != mDbList.end(); i++ )
	{
		if( (*i)->mGenre == genreId )
		{
			ASSERT( false );
			return;
		}
	}
	// 
	// just add it to the front; I don't care about order
	//
	mDbList.push_back( new tEntry( genreId ) );
	if( pIter )
	{
		pIter->mIter = mDbList.end()-1;
	}
	//
	// create a new prop displayer for the header.
	//
	mDbList.back()->mGenreHeader.SetPropDisplayer
		(
			cGenreSwitcher::GetInstance()->GetFactoryForGenre((cGenre::Genre)mDbList.back()->mGenre)->CreatePropDisplayer()
		);
	//
	// open the database
	//
    //
	// the hier database will own destroying this...
	//
	cLockedTemporaryFileArchive* pArch = new cLockedTemporaryFileArchive();
	pArch->OpenReadWrite();
	mDbList.back()->mDb.Open( pArch );

}

 void cFCODatabaseFile::SetFileName( const TSTRING& name )
{
	mFileName = name;
}

 TSTRING	cFCODatabaseFile::GetFileName() const
{
	return mFileName;
}	

//-----------------------------------------------------------------------------
// cFCODatabaseFileIter
//-----------------------------------------------------------------------------
cFCODatabaseFileIter::cFCODatabaseFileIter( cFCODatabaseFile& dbFile ) 
:	mDbFile( dbFile ) 
{ 	
	SeekBegin(); 
}
 
void cFCODatabaseFileIter::SeekBegin()
{
	mIter = mDbFile.mDbList.begin();
}

void cFCODatabaseFileIter::Next()
{
	mIter++;
}

bool cFCODatabaseFileIter::Done() const
{
	return ( mIter == mDbFile.mDbList.end() );
}

void cFCODatabaseFileIter::SeekToGenre(cGenre::Genre genreId)
{
	for( SeekBegin(); ! Done(); Next() )
	{
		if( GetGenre() == genreId )
			return;
	}
}

void cFCODatabaseFileIter::Remove()
{
	ASSERT( ! Done() );
	if( ! Done() )
	{
		delete *mIter;
		mIter = mDbFile.mDbList.erase( mIter );
	}
}

cGenre::Genre cFCODatabaseFileIter::GetGenre()	const
{
	ASSERT( ! Done() );
	return (*mIter)->mGenre;
}

cHierDatabase&	cFCODatabaseFileIter::GetDb() 
{
	ASSERT( ! Done() );
	return (*mIter)->mDb;
}

const cHierDatabase& cFCODatabaseFileIter::GetDb() const
{
	ASSERT( ! Done() );
	return (*mIter)->mDb;

}

cFCOSpecList& cFCODatabaseFileIter::GetSpecList()
{
	ASSERT( ! Done() );
	return (*mIter)->mSpecList;
}

const cFCOSpecList& cFCODatabaseFileIter::GetSpecList()	const
{
	ASSERT( ! Done() );
	return (*mIter)->mSpecList;
}

const cFCODbGenreHeader& cFCODatabaseFileIter::GetGenreHeader() const
{
	ASSERT( ! Done() );
	return (*mIter)->mGenreHeader;
}

cFCODbGenreHeader& cFCODatabaseFileIter::GetGenreHeader()
{
	ASSERT( ! Done() );
	return (*mIter)->mGenreHeader;
}


