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

#include "stdtw.h"
#include "dbexplore.h"
#include "fcodatabasefile.h"
#include "dbdatasource.h"
#include "core/debug.h"
#include "core/error.h"
#include "fco/fcopropdisplayer.h"
#include "fco/fcopropvector.h"
#include "fco/fcopropset.h"
#include "fco/fcoprop.h"
#include "fco/genreswitcher.h"
#include "fco/twfactory.h"
#include "fco/fconametranslator.h"

//
// TODO -- make an "associate with new genre" method for the db iter so we don't have to 
//		delete and create a new one when we switch genres.
//

static inline bool IsSpace( TCHAR c )
{
	return ( (c == _T(' ')) || (c == _T('\t')) || (c == _T('\r')) || (c == _T('\n')) || (c == _T('\0')) );
}

static inline bool IsEnd( TCHAR c )
{
	return ( (c == _T('\0')) );
}


///////////////////////////////////////////////////////////////////////////////
// GetNoun
//		note -- you can only call this once after the verb (it eats the rest
//			of the line!)
///////////////////////////////////////////////////////////////////////////////
static void GetNoun( TSTRING& noun )
{
	static TSTRING prevNoun;
	TCHAR buf[1024];

	TCIN.getline( buf, 1024 );
	//
	// find the end of the noun...
	//
	TCHAR* pStart	= buf;
	TCHAR* end		= &buf[1023];
	while( IsSpace(*pStart) && (! IsEnd(*pStart)) && (pStart < end) )
		pStart++;

	if( IsEnd( *pStart ) || (pStart >= end) )
	{
		// no noun!
		noun = _T("");
		return;
	}
	TCHAR* pCur		= pStart;
	bool bQuote = false;
	if( *pCur == _T('\"') )
	{
		bQuote = true;
		pCur++;
		pStart++;
	}
	while( pCur < end )
	{
		if( (! bQuote) && IsSpace(*pCur) )
			break;

		if( *pCur == _T('\"') && bQuote)
			break;

		pCur++;
	}
	noun.assign(pStart, (pCur - pStart));

	if( noun.compare( _T("!$") ) == 0 )
	{
		noun = prevNoun;
	}
	prevNoun = noun;
}

///////////////////////////////////////////////////////////////////////////////
// SeekTo -- seeks to the named noun; this takes care of interpriting special 
//		nouns. If the seek fails, then Done() is true and false is returned.
//		If noun is a special character, then it is altered to what it mapped to.
///////////////////////////////////////////////////////////////////////////////
static bool SeekTo(cDbDataSourceIter* pIter, TSTRING& noun )
{
	if( noun[0] == _T('*') )
	{
		// interprite the rest of the string as a number index to seek to...
		int index = _ttoi( &noun[1] );
		if( index < 0 )
			return false;
		pIter->SeekBegin();
		for( int i=0; ((i < index) && (! pIter->Done())); i++, pIter->Next() )
		{

		}
		if( pIter->Done() )
			return false;
		noun = pIter->GetShortName();
		return true;
	}
	else
		return ( pIter->SeekTo( noun.c_str() ) );
}

///////////////////////////////////////////////////////////////////////////////
// PrintFCO
///////////////////////////////////////////////////////////////////////////////
static void PrintFCO( const iFCO* pFCO, const iFCOPropDisplayer* pDisplayer )
{
	TCOUT.setf(std::ios::left);
	
	TCOUT	<< _T("------- ") 
			<< iTWFactory::GetInstance()->GetNameTranslator()->ToStringDisplay( pFCO->GetName() )
			<< _T(" -------") 
			<< std::endl;
	//
	// iterate over all of the properties
	//
	const iFCOPropSet* pPropSet	= pFCO->GetPropSet();
	cFCOPropVector v		= pPropSet->GetValidVector();
	for( int i=0; i < pPropSet->GetNumProps(); i++ )
	{	
		if( v.ContainsItem( i ) )
		{
			TCOUT << _T("[");
			TCOUT.width(2);
			TCOUT << i << _T("]") ;
			TCOUT.width(25);
			TCOUT << pPropSet->GetPropName(i);
			TCOUT.width(0);
			TCOUT << pDisplayer->PropAsString( pFCO, i ) << std::endl;
		}
	}
	TCOUT << _T("--------------------------------------------") << std::endl;
}

static void SplitString( const TSTRING& str, TCHAR c, std::vector<TSTRING>& vStrings )
{
	vStrings.clear();

	TSTRING::const_iterator i, prev;
	for( prev = i = str.begin(); i != str.end(); i++ )
	{
		if( *i == c )
		{
			vStrings.push_back(TSTRING());
			vStrings.back().assign( prev, i );
			prev = i+1;
		}
	}
	if( prev != i )
	{
		vStrings.push_back(TSTRING());
		vStrings.back().assign( prev, i );
	}
}


///////////////////////////////////////////////////////////////////////////////
// Execute
///////////////////////////////////////////////////////////////////////////////
void cDbExplore::Execute( cFCODatabaseFileIter& dbIter )
{
	ASSERT( ! dbIter.Done() );

	cDbDataSourceIter* pIter		= new cDbDataSourceIter( &dbIter.GetDb(), dbIter.GetGenre() );
	const iFCOPropDisplayer* pDisplayer	= dbIter.GetGenreHeader().GetPropDisplayer();

	////////////////////////////
	// the main event loop...
	////////////////////////////
	while( true )
	{
		TSTRING verb, noun;
		TCOUT << _T(">>");
		TCIN >> verb;
		//
		// ok, now we switch on the command...
		//
		//-----------------------------------------------------------------
		// quit
		//-----------------------------------------------------------------
		if( verb.compare( _T("quit") ) == 0 )
		{
			// the quit command...
			break;
		}
		//-----------------------------------------------------------------
		// print
		//-----------------------------------------------------------------
		if( verb.compare( _T("print") ) == 0 )
		{
			GetNoun(noun);
			if( SeekTo( pIter, noun ) )
			{
				if( pIter->HasFCOData() )
				{
					iFCO* pFCO = pIter->CreateFCO();
					PrintFCO( pFCO, pDisplayer );
					pFCO->Release();
				}
				else
				{
					TCOUT << _T("Object has no data associated with it.") << std::endl;
				}
			}
			else
			{
				TCOUT << _T("Unable to find object ") << noun << std::endl;
			}
		}
		//-----------------------------------------------------------------
		// pwd
		//-----------------------------------------------------------------
		else if( verb.compare( _T("pwd") ) == 0 )
		{
			TCOUT << iTWFactory::GetInstance()->GetNameTranslator()->ToStringDisplay( pIter->GetParentName() ) << std::endl;
		}
		//-----------------------------------------------------------------
		// ls
		//-----------------------------------------------------------------
		else if( verb.compare( _T("ls") ) == 0 )
		{
			int cnt = 0;
			for( pIter->SeekBegin(); ! pIter->Done(); pIter->Next(), cnt++ )
			{
				TCOUT << _T("[") << cnt ;
				if( pIter->CanDescend() )
				{
					TCOUT << _T("]*\t") ;
				}
				else
				{
					TCOUT << _T("]\t") ;
				}
				TCOUT << pIter->GetShortName() << std::endl;
			}
		}
		//-----------------------------------------------------------------
		// cd
		//-----------------------------------------------------------------
		else if( verb.compare( _T("cd") ) == 0 )
		{
			GetNoun(noun);
			std::vector<TSTRING> vDirs;
			SplitString( noun, pIter->GetParentName().GetDelimiter(), vDirs );
			for( std::vector<TSTRING>::iterator i = vDirs.begin(); i != vDirs.end(); i++ )
			{
				
				if( i->compare( _T("..") ) == 0 )
				{
					if( pIter->AtRoot() )
					{
						TCOUT << _T("Can't ascend above root.") << std::endl;
						break;
					}
					else
					{
						TCOUT << _T("Ascending...") << std::endl;
						pIter->Ascend();
					}
				}
				else
				{
					if( SeekTo( pIter, *i ) )
					{
						if( pIter->CanDescend() )
						{
							TCOUT << _T("Descending into ") << *i << std::endl;
							pIter->Descend();
						}
						else
						{
							TCOUT << *i << _T(" has no children; can't descend.") << std::endl;
							break;
						}
					}
					else
					{
						TCOUT << _T("Unable to find object ") << *i << std::endl;
						break;
					}
				}
			}
		}
		//-----------------------------------------------------------------
		// cg
		//-----------------------------------------------------------------
		else if( verb.compare( _T("cg") ) == 0 )
		{
			GetNoun(noun);

            cGenre::Genre newGenre = cGenreSwitcher::GetInstance()->StringToGenre( noun.c_str() );

            if (newGenre != cGenre::GENRE_INVALID)
            {
                dbIter.SeekToGenre( newGenre );
                if( !dbIter.Done() )
			    {
                    TCOUT << _T("Changing to Genre ") << noun << std::endl;
					//
					// create a new db iter for the new genre (db iters can only be
					// assocaited with a single genre :-( )
					//
					delete pIter;
					pIter = new cDbDataSourceIter( &dbIter.GetDb(), newGenre );
				}
			    else
			    {
				    TCOUT << _T("Unable to find Genre ") << noun << std::endl;
			    }
            }
            else
            {
                TCOUT << _T("Invalid Genre ") << noun << std::endl;
            }
        }
		//-----------------------------------------------------------------
		// pwg
		//-----------------------------------------------------------------
		else if( verb.compare( _T("pwg") ) == 0 )
		{
			TCOUT << _T("Current Genre: ") << cGenreSwitcher::GetInstance()->GenreToString( (cGenre::Genre)dbIter.GetGenre(), true ) << std::endl;
		}

		//-----------------------------------------------------------------
		// help
		//-----------------------------------------------------------------
		if( verb.compare( _T("help") ) == 0 )
		{
			TCOUT	<< _T("Commands: ")				<< std::endl
				    << _T("	cd <dir_name>")			<< std::endl
				    << _T("	pwd ")					<< std::endl
				    << _T("	ls ")					<< std::endl
				    << _T("	print <object_name>")	<< std::endl
				    << _T("	cg (FS | NTFS | NTREG)")<< std::endl
				    << _T("	pwg")					<< std::endl
					<< _T("	quit")					<< std::endl;
		}

		// make sure the file is still valid...
		//
	/*
#ifdef _BLOCKFILE_DEBUG
		db.AssertAllBlocksValid() ;
#endif
	*/
	}

	delete pIter;
	TCOUT << _T("Exiting...") << std::endl;

}

///////////////////////////////////////////////////////////////////////////////
//
// old stuff
//
///////////////////////////////////////////////////////////////////////////////
/*
	
		//-----------------------------------------------------------------
		// mkdir
		//-----------------------------------------------------------------
		else if( verb.compare( _T("mkdir") ) == 0 )
		{
			GetNoun(noun);
			TCOUT << "Making a child of " << noun << std::endl;
			if( iter.SeekTo( noun.c_str() ) )
			{
				iter.AddChildArray();
			}
			else
			{
				TCOUT << "Unable to find object " << noun << std::endl;
			}
		}
		//-----------------------------------------------------------------
		// mk
		//-----------------------------------------------------------------
		else if( verb.compare( _T("mk") ) == 0 )
		{
			GetNoun(noun);
			TCOUT << "Making object " << noun << std::endl;
			if( iter.SeekTo( noun.c_str() ) )
			{
				TCOUT << "Error: object already exists!" << std::endl;
			}
			else
			{
				iter.AddFCO( noun, 0 );	// add a null fco for now
			}
		}
		//-----------------------------------------------------------------
		// rmdir
		//-----------------------------------------------------------------
		// TODO -- still needs to be implemented in the iterator class!
		//
		else if( verb.compare( _T("rmdir") ) == 0 )
		{
			GetNoun(noun);
			TCOUT << "Removing the child of " << noun << std::endl;
			if( iter.SeekTo( noun.c_str() ) )
			{
				//TODO -- check that it has an empty child
				iter.DeleteChildArray();
			}
			else
			{
				TCOUT << "Unable to find object " << noun << std::endl;
			}
		}
		//-----------------------------------------------------------------
		// rm
		//-----------------------------------------------------------------
		else if( verb.compare( _T("rm") ) == 0 )
		{
			GetNoun(noun);
			TCOUT << "Removing object " << noun << std::endl;
			if( iter.SeekTo( noun.c_str() ) )
			{
				if( iter.CanDescend() )
				{
					TCOUT << "Can't delete object; it still has children." << std::endl;
				}
				else
				{
					iter.RemoveFCO();
				}
			}
			else
			{
				TCOUT << "Unable to find object " << noun << std::endl;
			}
		}

*/
