/////////////////////////////////////////////////////////////////////////////
// Name:        No names yet.
// Purpose:     Contrib. demo
// Author:      Aleksandras Gluchovas
// Modified by:
// Created:     2000/02/08
// RCS-ID:      $Id: txtrecset.cpp,v 1.12 2001/04/21 11:46:10 georgetasker Exp $
// Copyright:   (c) Aleksandras Gluchovas
// Licence:   	GPL (version 2.0 or later)
/////////////////////////////////////////////////////////////////////////////

#ifdef __GNUG__
    #pragma implementation "txtrecset.cpp"
    #pragma interface "txtrecset.h"
#endif

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <memory.h>

#include <time.h>

#include "txtrecset.h"

/***** Implementation for class FieldBase *****/

FieldBase::FieldBase()
{}

FieldBase::~FieldBase()
{}

wxString& FieldBase::GetName()
{
	return mName;
}

void FieldBase::SetName( const wxString& name )
{
	mName = name;
}

/***** Implementation for class StringValue *****/

StringValue::StringValue()
{}

StringValue::StringValue( const wxString& name, const wxString& value )
	: mValue( value )
{
	mName = name;
}

StringValue::StringValue( const wxString& value )
	: mValue( value )
{
	mName = "";
}

int StringValue::GetType()
{
	return FT_VALUE;
}

const wxString& StringValue::GetValue()
{
	return mValue;
}

FieldBase* StringValue::Clone()
{
	return (FieldBase*)new StringValue( mName, mValue );
}

void StringValue::SetValue( const wxString& value )
{
	mValue = value;
}

void StringValue::Dump()
{
	printf( mName + " = \"" + mValue + "\"\n" );
}

/***** Implementation for class StringListValue *****/

void StringListValue::SetValue( const wxString& value )
{
	mValue = value;

	mValues.Clear();

	ExtractConcatenatedValues( value, mValues, '|' );
}

StrListT& StringListValue::GetValues()
{
	return mValues;
}


/***** Implementation for class Record *****/

wxString Record::m_sEmptyValue = "";

Record::Record()
{
}

Record::Record( const wxString& name )
{
	mName = name;
}

Record::Record( const wxString& name, const wxString& strValue )
{
	mName = name;
	mStrValue = strValue;
}

Record::~Record()
{
	RemoveAllFields();
}

int Record::GetType()
{
	return FT_RECORD;
}

wxString& Record::GetStringValue()
{
	return mStrValue;
}

const wxString& Record::GetValue()
{
	return mStrValue;
}

void Record::SetStringValue( const wxString& value )
{
	mStrValue = value;
}

// inline'ed text-database parsing helpers

static inline bool get_next_word( char*& cur, char* end )
{
    for(;;)
    {
        while( cur < end && 
              ( *cur == ' ' || *cur == '\n' || *cur == '\r' || *cur == '\t' ) ) ++cur;

        // skip comment-lines

        if ( *cur == '/' && *(cur+1) == '/' )
        {
            while ( cur < end && *cur != '\n' ) ++cur;

            continue;
        }
        else break;

    } 
        
    return ( cur < end );
}

static inline bool skip_word( char*& cur, char* end )
{
	// handle quoted strings and "blobs" enclosed within "[[ ]]" double-brackets

	if ( *cur == '=' ) 
	{
		++cur;
		return TRUE;
	}
	else
	if ( *cur == '"' )
	{
		++cur;

		while( cur < end && ( *cur != '\"' || 
			                  ( *(cur-1) == '\\' && *cur == '\"' )
						    )
		     ) 
			
			++cur;

		if ( cur >= end ) return FALSE;

		++cur;

		return TRUE;
	}
	else
	if ( *cur == '[' &&  ( cur + 1 ) < end && *(cur+1) == '[' )
	{
		cur += 2;
		 
		while( cur < end )
		{
			if ( *cur == ']' )
			{
				if ( *(cur+1) == ']' ) break;
			}

			++cur;
		}

		if ( cur >= end ) return FALSE;

		cur += 2;

		return TRUE;
	}
	     

    while( cur < end && *cur != '\n' && *cur != '\r' && *cur != '=' &&
           *cur != ' ' && *cur !='\t' ) ++cur;

    return ( cur < end );
}

static inline bool skip_xml_word( char*& cur, char* end )
{
	// handle quoted strings and "blobs" enclosed within "[[ ]]" double-brackets

	if ( *cur == '"' )
	{
		++cur;

		while( cur < end && ( *cur != '\"' || 
			                  ( *(cur-1) == '\\' && *cur == '\"' )
						    )
		     ) 
			
			++cur;

		if ( cur >= end ) return FALSE;

		++cur;

		return TRUE;
	}
	else

	if ( *cur == '=' ||
		 *cur == '<' ||
		 *cur == '>' ||
		 *cur == '/' )
	{
		 ++cur;

		 return ( cur < end );
	}

    while( cur < end && *cur != '\n' && *cur != '\r' &&
           *cur != ' ' && *cur !='\t' &&

		   *cur != '=' &&
		   *cur != '<' &&
		   *cur != '>' &&
		   *cur != '/' 
		 )

		   ++cur;

    return ( cur < end );
}


static inline bool matches_word( char* cur, char* end, const char* word )
{
    while ( cur < end && *word && *cur == *word ) 
    {
        ++cur;
        ++word;
    }

    // FIXME:: handle the case when word is sub-wxString of bigger one at `cur'

    return ( *word == '\0' );
}

static wxString _gTmpStForWord = wxString( 'x', 1024 );

static wxString& get_word( char* cur, char* end )
{

	char* start = cur;

	skip_word( cur, end );

	// strip quotes and double brackets ("[[ ]]")

	if ( *start == '"' )
	{
		++start; --cur;
	}
	else
	if ( *start == '[' && *(start+1) == '[' )
	{
		start += 2; cur -= 2;
	}

	// for performance use global wxString-object (cut out ref-count stuff)

#if 0
	return wxString( start, size_t( cur - start ) );
#else
	//_gTmpStForWord.Truncate( 0 );

	_gTmpStForWord = "";

	ConcatString( _gTmpStForWord, start, size_t( cur - start ) );

	return _gTmpStForWord;
#endif
}

static wxString& get_xml_word( char* cur, char* end )
{
	char* start = cur;

	skip_xml_word( cur, end );

	// strip quotes and double brackets ("[[ ]]")

	if ( *start == '"' )
	{
		++start; --cur;
	}
	else
	if ( *start == '[' && *(start+1) == '[' )
	{
		start += 2; cur -= 2;
	}

	// for performance use global wxString-object (cut out ref-count stuff)

	_gTmpStForWord = "";

	ConcatString( _gTmpStForWord, start, size_t( cur - start ) );

	return _gTmpStForWord;
}

static void print_line_at( char* cur, char* end )
{
	if ( cur >= end ) return;

	int maxBack = 80;

	char* till = cur;

    int i;
	for( i = 0; i != 2; ++i )
	{
		int maxBack = 80;

		while( *cur != 13 && *cur != 10 && --maxBack ) --cur;

		while( *cur == 13 || *cur == 10 ) --cur;
	}

	++cur;

	while( till < end && *till != 13 && *till != 10 ) ++till;

	char tmp = *till;

	*till = '\0';

	printf( " At fragment:\n%s\n", cur );

	*till = tmp;
}

#define RETURN_ERROR { printf( "ERROR while parsing record!" ); print_line_at( cur, end );return FALSE; }

// parses record recursivelly out of given text-stream

bool Record::LoadFromText( char*& cur, char* end )
{
	if ( !get_next_word( cur, end ) ) return FALSE;

	if ( *cur != '{' ) 
	{
		SetName( get_word( cur, end ) );

		skip_word( cur, end );

		if ( !get_next_word( cur, end ) ) RETURN_ERROR

		if ( *cur != '=' ) RETURN_ERROR

		skip_word( cur, end );

		if ( !get_next_word( cur, end ) ) RETURN_ERROR

		if ( *cur != '{' ) RETURN_ERROR
	}

	skip_word( cur, end );

	for(;;)
	{
		if ( !get_next_word( cur, end ) ) RETURN_ERROR

		if ( *cur == '{' ) // unnamed record: "{ ... }"
		{
			Record* nested = new Record();

			nested->LoadFromText( cur, end );

			nested->SetName( "" );

			mFields.Add( nested );

			continue;
		}

		if ( *cur == '"' ||
			 ( *cur == '[' || *(cur+1) == '[' ) )
		{
			// single nameless wxString value... like " ... "BOX" ... "

			mFields.Add( new StringValue( get_word( cur, end ) ) );

			skip_word( cur, end );

			continue;
		}

		wxString name = get_word( cur, end ); // name of a property (or value, if unnamed)

		skip_word( cur, end );

		if ( name == "}" )
		{
			skip_word( cur, end );
			return TRUE;
		}

		if ( !get_next_word( cur, end ) ) RETURN_ERROR

		if ( *cur == '=' )
		{
			skip_word( cur, end );

			if ( !get_next_word( cur, end ) ) RETURN_ERROR

			if ( *cur == '{' )
			{
				Record* nested = new Record();

				nested->LoadFromText( cur, end );

				nested->SetName( name );

				mFields.Add( nested );
			}
			else
			{
				mFields.Add( new StringValue( name, get_word( cur, end ) ) );
	
				skip_word( cur, end );
			}
		}
		else
			mFields.Add( new StringValue( "", name ) ); // "name" is actually a value here
	}

//	return TRUE; // never reached
}

bool Record::LoadFromString( const wxString& text )
{
	char* start = (char*)(const char*)text;
	char* end   = start + text.length();

	return LoadFromText( start, end );
}

wxString Record::GetTextContent()
{
	wxString text = "";

	if ( GetName().length() )

		text = GetName() + " = ";

	text += "{\n";

    size_t i;
	for( i = 0; i != mFields.Count(); ++i )
	{
		FieldBase* pField = mFields[i];

		if ( pField->GetType() == FT_RECORD )
		
			text += ((Record*)pField)->GetTextContent();
		else
		{
			StringValue* pSValue = (StringValue*)pField;

			if ( pSValue->GetName().length() )

				text += pSValue->GetName() + " = \"" + pSValue->GetValue() + "\"";
			else
				text += "\"" + pSValue->GetValue() + "\"";
		}

		text += "\n";
	}


	text += "}\n";

	return text;
}

bool Record::LoadFromXMLString( const wxString& XMLtext )
{
	char* cur = (char*)(const char*)XMLtext.c_str();
	char* end = cur + XMLtext.Length();

	return LoadFromXML( cur, end );
}

bool Record::LoadFromXML( char*& cur, char* end )
{
	for(;;) // skip all leading comment and version tags
	{
		if ( !get_next_word( cur, end ) ) RETURN_ERROR;

		if ( *cur != '<' )

			RETURN_ERROR;


		++cur; // skip '<';

		if ( !get_next_word( cur, end ) ) RETURN_ERROR;

		if ( !( *cur == '!' && *(cur+1) == '-' ) && 
			 !( *cur == '?' )
			) 
			
			break;

		while( cur < end && *cur != '>' ) 

			++cur;

		++cur; // skip '>'
	}

	mName = get_xml_word( cur, end ); // name of a property (or value, if unnamed)

	skip_xml_word( cur, end );

	for(;;) // retrieve atomic properties of this record
	{
		if ( !get_next_word( cur, end ) ) RETURN_ERROR;

		if ( *cur == '>' ) break;

		if ( *cur == '/' )
		{
			while( cur < end && *cur != '>' ) ++cur;

			++cur; // skip '>' 

			// this is the end

			return TRUE;
		}

		wxString propName = get_xml_word( cur, end );

		skip_xml_word( cur, end );

		if ( !get_next_word( cur, end ) ) RETURN_ERROR;

		if ( *cur != '=' ) RETURN_ERROR; 
		
		++cur; // skip '=';

		if ( !get_next_word( cur, end ) ) RETURN_ERROR;

		wxString propValue = get_xml_word( cur, end );

		skip_xml_word( cur, end );

		this->AddStringField( propName, propValue );
	}

	++cur; // skip '>'

	for(;;) // obtain all nested records
	{
		if ( !get_next_word( cur, end ) ) RETURN_ERROR;

		if ( *cur == '<' && *(cur+1) == '!' && *(cur+2) == '[' )
		{
			// handle <![CDATA[...]]>

			while( cur < end && *cur != '[' ) ++cur;

			++cur;

			while( cur < end && *cur != '[' ) ++cur;

			++cur;

			char* start = cur;

			while( cur < end && *cur != ']' ) ++cur;

			wxString val;
			ConcatString( val, start, size_t( cur - start ) );

			SetStringValue( val ); 

			while( cur < end && *cur != '>' ) ++cur;

			++cur;
			
			continue;
		}
		else
		if ( *cur != '<' )
		{
			char* start = cur;

			while( cur < end && *cur != '<' ) ++cur;

			wxString val;
			ConcatString( val, start, size_t( cur - start ) );

			SetStringValue( val ); 

			continue;
		}
		else
		if ( *cur == '<' && *(cur+1) == '/' )

			break;

		Record* pNested = new Record();

		pNested->LoadFromXML( cur, end );

		mFields.Add( pNested );
	}

	// FIXME:: curde... should ceck if the closing tag matches the opening one

	while( cur < end && *cur != '>' ) ++cur; // skip </tag>

	++cur; // skip '>'

	return TRUE;
}

bool ContainsReservedXMLChars( const wxString& s )
{
	const char* cur = (const char*)s;

	while( *cur )
	{
		if ( *cur == '>' || *cur == '/' || *cur == '<' || *cur == '=' )

			return TRUE;

		++cur;
	}

	return FALSE;
}

wxString Record::GetXMLTextContent( const wxString& lmargin )
{
	wxString text = "";

	text = "\n" + lmargin + "<" + GetName();

	size_t nProps = 0;

	size_t i;
	for( i = 0; i != mFields.Count(); ++i )
	{
		FieldBase* pField = mFields[i];

		if ( pField->GetType() == FT_VALUE )
		{
			StringValue* pSValue = (StringValue*)pField;

			if ( text.Length() >= 60 )
				
				text += "\n" + lmargin + "\t";
			else
				text += " ";

			text += pSValue->GetName() + "=\"" + pSValue->GetValue() + "\"";

			++nProps;
		}
	}

	text += ">";

	nProps = 0;

	for( i = 0; i != mFields.Count(); ++i )
	{
		FieldBase* pField = mFields[i];

		if ( pField->GetType() == FT_RECORD )
		{
			wxString margin = lmargin + "\t"; // increase margin

			text += ((Record*)pField)->GetXMLTextContent( margin );

			++nProps;
		}
	}


	wxString val = GetStringValue();

	if ( ContainsReservedXMLChars( val ) )
	{
		text += "\n" + lmargin + "\t" + "<![CDATA[" + val + "]]>";
		nProps = 1;
	}
	else
		text += val;

	if ( nProps )

		text += "\n" + lmargin;

	text += "</" + GetName() + ">";

	return text;
}

bool Record::EnsureExists( const wxString& name, int fieldType )
{
	FieldBase* pFld = NULL;

	if ( ( pFld = FindField( name )) != NULL ) 
	{
		if ( pFld->GetType() != fieldType )
		{
			if ( fieldType == FT_VALUE )

				pFld = new StringValue();
			else
				pFld = new Record();

			ReplaceFieldByName( name, pFld );
		}

		return FALSE;
	}
	
	if ( fieldType == FT_RECORD )
		
		pFld = new Record();
	else
		pFld = new StringValue();

	pFld->SetName( name );

	mFields.Add( pFld );

	return TRUE;
}

bool Record::SetValueByIndex( size_t idx, const wxString& value )
{
	((StringValue*)mFields[idx])->SetValue( value );

	return TRUE;
}

bool Record::SetValueByName( const wxString& name, const wxString& value )
{
	// FOR NOW:: wxString field is assumed!

	StringValue* pVal = (StringValue*)FindField( name );

	if ( pVal == NULL )
	{
		printf( "FATAL ERROR: field " + name + " was not found in the record!" );

		pVal = new StringValue( name, value );

		mFields.Add( pVal );

		return TRUE;
	}

	pVal->SetValue( value );

	return TRUE;
}

bool Record::SetFieldByName( const wxString& name, FieldBase* pField )
{
    size_t i;
	for( i = 0; i != mFields.Count(); ++i )

		if ( mFields[i]->GetName() == name )
		{
			delete mFields[i];
			mFields[i] = pField;

			return TRUE;
		}

	mFields.Add( pField );

	pField->SetName( name );

	return FALSE;
}

const wxString& Record::GetValueByName( const wxString& name )
{
	FieldBase* pField = FindField( name );

	if ( pField )

		return ((StringValue*)pField)->GetValue();
	else
		return m_sEmptyValue;
}

const wxString& Record::GetValueByIndex( size_t idx )
{
	return ((StringValue*)mFields[ idx ])->GetValue();
}

bool Record::HasValue( const wxString& name )
{
	return FindField( name ) != NULL;
}

void Record::CopyFieldsFrom( Record& other )
{
	this->RemoveAllFields();

	FieldListT& lst = other.GetFields();

    size_t i;
	for( i = 0; i != lst.Count(); ++i )
	
		mFields.Add( lst[i]->Clone() );

	this->SetName( other.GetName() );
}

void Record::AddFieldsByPrefix( const wxString& prefix, Record& other )
{
	FieldListT& lst = other.GetFields();
	size_t prefLen = prefix.Length();

    size_t i;
	for( i = 0; i != lst.Count(); ++i )
	{
		wxString& name = lst[i]->GetName();

		if ( name.length() >= prefLen &&
			 name[0u] == prefix[0u] )
		{
			size_t n;
			for( n = 1; n != prefLen; ++n )
			
				if ( name[n] != prefix[n] ) break;

			if ( n == prefLen )
			
				mFields.Add( lst[i]->Clone() );
		}
	}
}

void Record::AssignFieldsToTemplate( ScriptTemplate& templ, 
									 int escapeType )
{
    size_t i;
	for( i = 0; i != mFields.Count(); ++i )
	
		// FOR NOW:: assuming that only wxString values are assigned!

		if ( mFields[i]->GetType() == FT_VALUE ) 
		{	
			wxString val = ((StringValue*)mFields[i])->GetValue();

			if ( escapeType == ESCAPE_ENCODE )
				
				val = ::GetEscapedString( val );
			else
			if ( escapeType == ESCAPE_DECODE )

				val = ::GetUnescapedString( val );

			templ.SetVar( mFields[i]->GetName(), MakeTmpString( val)  );
		}
}

void Record::AssignValuesFrom( Record& other, int escapeType )
{
	FieldListT& lst = other.GetFields();

    size_t i;
	for( i = 0; i != lst.Count(); ++i )
	{
		FieldBase* pFld = FindField( lst[i]->GetName() );

		// FOR NOW:: assuming that only wxString values are assigned!
	
		if ( pFld && pFld->GetType() == FT_VALUE )
		{
			wxString val = ((StringValue*)lst[i])->GetValue();

			if ( escapeType == ESCAPE_ENCODE )
				
				val = ::GetEscapedString( val );
			else
			if ( escapeType == ESCAPE_DECODE )

				val = ::GetUnescapedString( val );

			((StringValue*)pFld)->SetValue( val );
		}
	}
}

void Record::CopyStringValues( StrListT& toLst )
{
    size_t i;
	for( i = 0; i != mFields.Count(); ++i )

		toLst.Add( (*this)[i] );
}

void Record::AddStringValues( StrListT& fromLst )
{
    size_t i;
	for( i = 0; i != (unsigned int)fromLst.Count(); ++i )

		mFields.Add( new StringValue( "", fromLst[i] ) );
}

void Record::AddStringField( const wxString& name, const wxString& value )
{
	mFields.Add( new StringValue( name, value ) );
}

void Record::AddIntField( const wxString& name, int value )
{
	mFields.Add( new StringValue( name, IntToStr( value ) ) );
}

void Record::AddRecord( const wxString& name, const wxString& stringValue )
{
	Record* pRec = new Record( name );
	pRec->mStrValue = stringValue;

	mFields.Add( pRec );
}

int Record::GetIntValueByName( const wxString& name )
{
	return StrToInt( GetValueByName( name ) );
}

void Record::SetIntValueByName( const wxString& name, int value )
{
	((StringValue*)FindField( name ))->SetValue( IntToStr( value ) );
}

FieldBase& Record::GetFieldByName( const wxString& name )
{
	return *FindField( name );
}

FieldBase& Record::GetFieldByIndex( size_t idx )
{
	return *mFields[ idx ];
}

FieldBase& Record::FieldAt( size_t idx )
{
	return *mFields[ idx ];
}

bool Record::ReplaceFieldByName( const wxString& name, FieldBase* pNewField )
{
    size_t i;
	for( i = 0; i != mFields.Count(); ++i )

		if ( mFields[i]->GetName() == name )
		{
			delete mFields[i];
			mFields[i] = pNewField;
			pNewField->SetName( name );

			return TRUE;
		}

	return FALSE;
}

Record& Record::GetRecordByName( const wxString& name )
{
	return *((Record*)FindField( name ));
}

Record& Record::GetRecordByIndex( size_t idx )
{
	return *((Record*)mFields[ idx ]);
}

size_t Record::GetFieldCount()
{
	return mFields.Count();
}

void Record::AddField( FieldBase* pField )
{
	mFields.Add( pField );
}

void Record::RemoveFieldByIndex( size_t idx )
{
	delete mFields[idx];

	mFields.Remove( idx );
}

void Record::RemoveField( FieldBase* pField )
{
	delete pField;

	mFields.Remove( pField );
}

void Record::RemoveAllFields()
{
	WX_CLEAR_ARRAY( mFields );
}

const wxString& Record::operator[]( size_t idx )
{
	FieldBase& fld = *mFields[idx];

	if ( fld.GetType() == FT_RECORD )

		return ((Record*)&fld)->GetStringValue();
	else
		return ((StringValue*)&fld)->GetValue();
}

static wxString _gDummyEmptyStr;

const wxString& Record::operator[]( const wxString& name )
{
	FieldBase& fld = *FindField( name );

	if ( &fld == NULL ) return _gDummyEmptyStr;

	StringValue* pVal = (StringValue*)FindField( name );

	if ( fld.GetType() == FT_RECORD )

		return ((Record*)&fld)->GetStringValue();
	else
		return ((StringValue*)&fld)->GetValue();
}

FieldBase* Record::Clone()
{
	Record* pCopy = new Record();

	pCopy->mName = mName;
	pCopy->mStrValue = mStrValue;

    size_t i;
	for( i = 0; i != mFields.Count(); ++i )

		pCopy->mFields.Add( mFields[i]->Clone() );

	return (FieldBase*)pCopy;
}

void Record::Dump()
{
	printf( "Number of fields = %d ", mFields.Count() );

	if ( GetName().length() )

		printf( GetName() + " = " );

	printf( "{\n" );

    size_t i;
	for( i = 0; i != mFields.Count(); ++i )
	{
		printf( "%d. ", i );

		mFields[i]->Dump();
	}

	printf( "}\n" );
}
/***** Implementation for class RecordSetBase *****/

RecordSetBase::RecordSetBase()
{}

RecordSetBase::~RecordSetBase()
{
	CloseSet();
}

void RecordSetBase::CloseSet()
{
	// derived classes will handle this hook
}

/***** Implementation for class InMemoryXMLRecordSet *****/

InMemoryXMLRecordSet::InMemoryXMLRecordSet()
{}

InMemoryXMLRecordSet::InMemoryXMLRecordSet( const wxString& source )

	: InMemoryRecordSet( source )
{}

void InMemoryXMLRecordSet::Open( const wxString& source )
{
	RemoveAll();
	
	mAppended = FALSE;
	mModified = FALSE;
	mRemoved  = FALSE;
	mCurRecNo = 0;      // automaticly move to first

	char* cur = (char*)(const char*)source;
	char* end = cur + source.length();

	Record* pRec = new Record();

	while( pRec->LoadFromXML( cur, end ) )
	{
		//if ( pRec->FindField( "ID" ) )
		//{
		//	printf( "*ID:\t%s\t%d\n", ((*pRec)["ID"]).c_str(), pRec->GetFieldCount() );
		//}

		//printf( "Section %s parsed OK...\n", pRec->GetName().c_str() );
		mRecList.Add( pRec );

		pRec = new Record();

	}

	delete pRec; // because was not loaded completely

	mIsEof = mRecList.Count() == 0;
}

/***** Implementation for class InMemoryRecordSet *****/

InMemoryRecordSet::InMemoryRecordSet()

	: mAppended( FALSE ),
	  mModified( FALSE ),
	  mRemoved ( FALSE ),
	  mCurRecNo( 0 ),
	  mIsEof( TRUE )
{}

InMemoryRecordSet::InMemoryRecordSet( const wxString& source )

	: mAppended( FALSE ),
	  mModified( FALSE ),
	  mRemoved ( FALSE ),
	  mCurRecNo( 0 )
{
	Open( source );
}

InMemoryRecordSet::~InMemoryRecordSet()
{
	RemoveAll();
}

void InMemoryRecordSet::Open( const wxString& source )
{
	RemoveAll();
	
	mAppended = FALSE;
	mModified = FALSE;
	mRemoved  = FALSE;
	mCurRecNo = 0;      // automaticly move to first

	char* cur = (char*)(const char*)source;
	char* end = cur + source.length();

	Record* pRec = new Record();

	while( pRec->LoadFromText( cur, end ) )
	{
		//if ( pRec->FindField( "ID" ) )
		//{
		//	printf( "*ID:\t%s\t%d\n", ((*pRec)["ID"]).c_str(), pRec->GetFieldCount() );
		//}

		//printf( "Section %s parsed OK...\n", pRec->GetName().c_str() );
		mRecList.Add( pRec );

		pRec = new Record();

	}

	delete pRec; // because was not loaded completely

	mIsEof = mRecList.Count() == 0;
}

void InMemoryRecordSet::MoveFirst()
{
	mCurRecNo = 0;

	mIsEof = mRecList.Count() == 0;
}

void InMemoryRecordSet::MoveLast()
{
	mCurRecNo = mRecList.Count() - 1;

	mIsEof = mRecList.Count() == 0;
}

void InMemoryRecordSet::MoveNext()
{
	++mCurRecNo;

	mIsEof = mCurRecNo == mRecList.Count();
}

void InMemoryRecordSet::MovePrev()
{
	mIsEof = mCurRecNo == 0;

	--mCurRecNo;
}

bool InMemoryRecordSet::IsEof()
{
	return mIsEof;
}

int  InMemoryRecordSet::GetRecordCount()
{
	return mRecList.Count();
}

void InMemoryRecordSet::ModifyRecord()
{
	mModified = TRUE;
}

void InMemoryRecordSet::AppendRecord( Record* pRecord )
{
	mAppended = mAppended + 1;

	mRecList.Add( pRecord );
}

void InMemoryRecordSet::AppendRecordsFrom( RecordSetBase& other )
{
	other.MoveFirst();

	while( !other.IsEof() )
	{
		mRecList.Add( other.GetRecord().Clone() );

		other.MoveNext();

		++mAppended;
	}
}

void InMemoryRecordSet::RemoveAll()
{
	WX_CLEAR_ARRAY( mRecList );

	mRemoved = TRUE;
}

void InMemoryRecordSet::RemoveRecord()
{
	wxASSERT( !mIsEof );

	delete mRecList[ mCurRecNo ];

	mRecList.Remove( mCurRecNo );

	mRemoved = TRUE;
}

void InMemoryRecordSet::UseIndex( const wxString& idxName )
{
	// not relevant to in-memory r.sets
}

int InMemoryRecordSet::GetRecordPos()
{
	return mCurRecNo;
}

bool InMemoryRecordSet::SetRecordPos( unsigned int pos )
{
	mCurRecNo = pos;

	mIsEof = (pos >= mRecList.Count());

	return !mIsEof;
}

bool InMemoryRecordSet::FindFirst( const wxString& name, const wxString& value )
{
    size_t i;
	for( i = 0; i != mRecList.Count(); ++i )
	
		if ( ((Record*)mRecList[i])->GetValueByName( name ) == value )
		{
			mCurRecNo = i;
			mIsEof	  = FALSE;

			return TRUE;
		}

	mIsEof = TRUE;

	return FALSE;
}

bool InMemoryRecordSet::FindFirst( const wxString& name1, const wxString& value1,
								   const wxString& name2, const wxString& value2 )
{
    size_t i;
	for( i = 0; i != mRecList.Count(); ++i )
	
		if ( ((Record*)mRecList[i])->GetValueByName( name1 ) == value1 &&
		     ((Record*)mRecList[i])->GetValueByName( name2 ) == value2
		   )
		{
			mCurRecNo = i;
			mIsEof	  = FALSE;

			return TRUE;
		}

	mIsEof = TRUE;

	return FALSE;
}

wxString InMemoryRecordSet::GetTextContent()
{
	wxString txt;

    size_t i;
	for( i = 0; i != mRecList.Count(); ++i )
	{
		if ( i != mRecList.Count() - 1 )

			txt += '\n';

		txt += ((Record*)mRecList[i])->GetTextContent();
	}

	return txt;
}

void InMemoryRecordSet::Flush()
{
	// not relevant to in-memory r.sets
}

void InMemoryRecordSet::CloseSet()
{
	RemoveAll();
	// not relevant to in-memory r.sets
}

Record& InMemoryRecordSet::GetRecord()
{
	return *((Record*)mRecList[mCurRecNo]);
}

/***** Implementation for class TextFileRecordSet *****/

TextFileRecordSet::TextFileRecordSet()
{}

TextFileRecordSet::~TextFileRecordSet()
{
	Flush();
}

TextFileRecordSet::TextFileRecordSet( const wxString& fname )
{
	DoOpen( fname );
}

// overriden methods of InMemoryRecordSet class

void TextFileRecordSet::Open( const wxString& source )
{
	DoOpen( source );
}

void TextFileRecordSet::Flush()
{
	if ( mFName == "" ) return;

	if ( mModified || mRemoved )
	
		WriteFileContent( mFName, GetTextContent() );
	else
	if ( mAppended && mRecList.Count() )
	{
		wxString text = "";

        size_t i;
		for( i = 0; i != (size_t)mAppended; ++ i )
		
			text += ((Record*)mRecList[ mRecList.Count() - mAppended + i ] )->
							GetTextContent();
		
		AppendFileContent( mFName, text );
	}

	mAppended = mModified = mRemoved = FALSE;
}

void TextFileRecordSet::CloseSet()
{
	Flush();
}

void TextFileRecordSet::DoOpen( const wxString& fname )
{
	mFName = fname;

	wxString text = LoadFileContent( fname );

	printf( "Parsing records from file %s...", fname.c_str() );
	InMemoryRecordSet::Open( text );
	printf( "done.\n" );
}

