/////////////////////////////////////////////////////////////////////////////
// Name:        wxDbListCtrl
// Purpose:     
// Author:      Aleksandras Gluchovas
// Modified by:
// Created:     2000/07/27
// RCS-ID:      $Id: dblist.cpp,v 1.3 2001/04/21 11:48:18 georgetasker Exp $
// Copyright:   (c) 2000 PM-Soft
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

#ifdef __GNUG__
    #pragma implementation "boundlist.cpp"
    #pragma interface "boundlist.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 "wx/wspp/dblist.h"
#include "wx/tokenzr.h"

#include "wx/log.h"

/***** Implementation for class DataCursorBase *****/

void DataCursorBase::ODBCSetup( const wxString& dsn,
                const wxString& user,
                const wxString& passwd
                )
{
  mDsn = dsn;
  mUser = user;
  mPasswd = passwd;
}

void DataCursorBase::SetSortOrder( const wxString& col, bool accendingFlag )
{
  wxStringList cols; wxArrayInt flags;
  cols.Add( col );
  flags.Add( accendingFlag );

  SetSortOrder( cols, flags );
}

bool DataCursorBase::SetCurrentRowPos( int pos )
{
  return FALSE;
}

int  DataCursorBase::GetCurrentRowPos()
{
  return -1;
}

void DataCursorBase::InsertCacheRowAt( int row, wxStringList& values  )
{
  int avoidCompilerWarning = 0;
  wxASSERT( avoidCompilerWarning ); // DBG::
}

void DataCursorBase::RemoveCacheRow( int row )
{
  int avoidCompilerWarning = 0;
  wxASSERT( avoidCompilerWarning ); // DBG::
}

void DataCursorBase::UpdateCacheRow( int row, wxStringList& values )
{
  int avoidCompilerWarning = 0;
  wxASSERT( avoidCompilerWarning ); // DBG::
}

int DataCursorBase::FindInsertionPos( int col, const wxString& insValue, bool isAscending )
{
  int avoidCompilerWarning = 0;
  wxASSERT( avoidCompilerWarning ); // DBG::

  return -1;
}

/***** Implementation for class SampleDataCursor *****/

bool SampleDataCursor::ExecuteQuery( const wxString& query )
{
  mRow = -1;
  return TRUE;
}

bool SampleDataCursor::CloseQuery()
{
  return TRUE;
}

bool SampleDataCursor::FetchFirst()
{
  mRow = 0;

  return mRow < mCount;
}

bool SampleDataCursor::FetchNext()
{
  ++mRow;

  return mRow < mCount;
}

bool SampleDataCursor::FetchPrevious()
{
  --mRow;

  if ( mRow < 0 ) 
    
    mRow = -1000;

  return mRow >= 0;
}

void SampleDataCursor::ClearEof()
{
  if ( mRow == -1000 ) mRow = -1;
  else
  if ( mRow >= mCount ) mRow = mCount - 1;
}

void SampleDataCursor::GetColumnNames( wxStringList& names )
{
  // sample..

  names.Add( "First" );
  names.Add( "Second" );
  names.Add( "Third" );
}

wxString SampleDataCursor::GetValue( int colIndex )
{
  mStr.Printf( "%d-%d", mRow, colIndex );

  return mStr;
}

void SampleDataCursor::SetSortOrder( wxStringList& cols, wxArrayInt& accendingFlags )
{
	int avoidCompilerWarning = 0;
	wxASSERT_MSG(avoidCompilerWarning, "SampleDataCursor::SetSortOrder() has no code in it.");
}

bool SampleDataCursor::IsEof()
{
  return mRow >= mCount || mRow <= -1000;
}

/***** Implementation for class ODBCDataCursor *****/

#if USE_WX_ODBC

#include "wx/db.h"
#include "browsedb.h"

BrowserDB* ODBCDataCursor::mpBrowser = NULL;

ODBCDataCursor::ODBCDataCursor()

  : mpTableInfo( NULL ),
    mVerbose( FALSE ),
    mIsEof( FALSE )
{}

ODBCDataCursor::~ODBCDataCursor()
{
  /*
  if ( mpBrowser )
  {
    mpBrowser->OnCloseDB( !mVerbose );
    delete mpBrowser;
    mpBrowser = NULL;
  } */
}

bool ODBCDataCursor::ExecuteQuery( const wxString& query )
{
  bool firstTime = mpBrowser == NULL;

  if ( !mpBrowser ) 
    
    mpBrowser = new BrowserDB();

  mpBrowser->ODBCSource = mDsn;
  mpBrowser->UserName = mUser;
  mpBrowser->Password = mPasswd;

  if ( firstTime )
  {
    if ( !mpBrowser->OnStartDB( !mVerbose, FALSE ) )

      return FALSE;

    if ( firstTime && !mpBrowser->OnGetCatalog( !mVerbose ) )

      return FALSE;
  }

  mpTableInfo = NULL;

  wxStringTokenizer tok( query );
  wxString table;

  while( tok.HasMoreTokens() )
  {
    if ( tok.GetNextToken().Upper() == "FROM" )
    {
      table = tok.GetNextToken();
      break;
    }
  }

  for( int i = 0; i != mpBrowser->ct_BrowserDB->numTables; ++i )
  {
    wxString name = mpBrowser->ct_BrowserDB->pTableInf[i].tableName;

    if ( table == mpBrowser->ct_BrowserDB->pTableInf[i].tableName )
    {
      mpTableInfo = &mpBrowser->ct_BrowserDB->pTableInf[i]; 
      break;
    }
  }

  if ( !mpBrowser->cl_BrowserDB ) 
    
    if ( !mpBrowser->OnGetColumns( (char*)table.c_str(), mpTableInfo->numCols, !mVerbose ) )

        return FALSE;

  // start query and position cursor before the first element


  //if ( !mpBrowser->OnExecSql( query + mOrderSuffix, !mVerbose ) )   

  //  return FALSE;

  HSTMT hstmt = mpBrowser->db_BrowserDB->GetHSTMT();

    if ( SQLFreeStmt( hstmt, SQL_CLOSE) != SQL_SUCCESS )
    return FALSE;

  // NOTE:: setting up SQL_CURSOR_DYNAMIC cursor-type instead of 
  //        SQL_CURSOR_FORWARD_ONLY.which was default

  if ( SQLSetStmtOption( hstmt, SQL_CURSOR_TYPE, SQL_CURSOR_DYNAMIC ) != SQL_SUCCESS )
    return FALSE;

//  if ( SQLSetStmtOption( hstmt, SQL_ROWSET_SIZE, 200 ) != SQL_SUCCESS )
//    return FALSE;

  wxString qStr = query + mOrderSuffix;

    if (SQLExecDirect( hstmt, (UCHAR FAR *) (const char*)qStr, SQL_NTS) == SQL_SUCCESS)
       return(TRUE);

  //if ( !mpBrowser->OnExecSql( query + mOrderSuffix, !mVerbose ) )   
  //  return FALSE;

  return TRUE;
}

bool ODBCDataCursor::CloseQuery()
{
  // TBD::

  return TRUE;
}

bool ODBCDataCursor::FetchFirst()
{
  return TRUE;
}

bool ODBCDataCursor::FetchNext()
{
  bool ok = mpBrowser->OnGetNext( mpTableInfo->numCols, !mVerbose );

  mIsEof = !ok;

  return ok;
}

bool ODBCDataCursor::FetchPrevious()
{
  bool ok = mpBrowser->OnGetNext( mpTableInfo->numCols, !mVerbose, FALSE );

  mIsEof = !ok;

  return ok;
}

void ODBCDataCursor::SetSortOrder( wxStringList& cols, wxArrayInt& accendingFlags )
{
  mOrderSuffix = "";

  if ( cols.GetCount() != 0 ) 
  {
    mOrderSuffix = " ORDER BY ";

    for( size_t i = 0; i != cols.GetCount(); ++i )
    {
      if ( i > 0 ) mOrderSuffix += ", ";

      mOrderSuffix += cols[ i ];

      if ( accendingFlags[i] == 1 ) mOrderSuffix += " ASC";
                     else mOrderSuffix += " DESC";
    }
  }
}

void ODBCDataCursor::GetColumnNames( wxStringList& names )
{
  wxASSERT( mpTableInfo ); // DBG::

  for( int n = 0; n != mpTableInfo->numCols; ++n )
  
    names.Add( mpBrowser->cl_BrowserDB[n].colName );
}

wxString ODBCDataCursor::GetValue( int colIndex )
{
  return mpBrowser->cl_BrowserDB[colIndex].tableName;
}

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

void ODBCDataCursor::ClearEof()
{
  if ( mIsEof )
  {
    // TBD:: go back one row
  }

  mIsEof = FALSE;
}

#endif // USE_ODBC_PLUS

/***** Implementation for class ODBCPlusCursor *****/

#if USE_ODBC_PLUS

#include "odbc++/statement.h"
#include "odbc++/resultset.h"
#include "odbc++/drivermanager.h"
#include "odbc++/databasemetadata.h"
#include "odbc++/resultsetmetadata.h"

ODBCPlusCursor::ODBCPlusCursor()
  : mpCon( NULL ),
    mpRSet( NULL ),
    mpStmt( NULL )
{}

ODBCPlusCursor::~ODBCPlusCursor()
{
  if ( mpStmt ) delete mpStmt;
  if ( mpCon ) delete mpCon;
}

// overriden methods of DataCursorBase

bool ODBCPlusCursor::ExecuteQuery( const wxString& query )
{
  if ( !mpCon )
  {
    mpCon = odbc::DriverManager::getConnection( (const char*)mDsn, 
                          (const char*)mUser, 
                          (const char*)mPasswd );

    if ( !mpCon ) return FALSE;
  }

  int rstype = 0, rsconc = 0;

  odbc::DatabaseMetaData* md= mpCon->getMetaData();

  if(md->supportsResultSetType( odbc::ResultSet::TYPE_SCROLL_INSENSITIVE ) ) 

    rstype = odbc::ResultSet::TYPE_SCROLL_INSENSITIVE;

  else 
  if(md->supportsResultSetType( odbc::ResultSet::TYPE_SCROLL_SENSITIVE )) 

    rstype = odbc::ResultSet::TYPE_SCROLL_SENSITIVE;
  else 
  {
    //cout << "warning: data source does not support scrollable cursors" 
    return FALSE;
  }

  if ( mpStmt ) CloseQuery();

  mpStmt = mpCon->createStatement(rstype,rsconc);

  if ( !mpStmt ) return FALSE;

  mpRSet = mpStmt->executeQuery( (const char*)(query + mOrderSuffix) );

  mpRSet->setFetchSize( 200 );

  return mpRSet != NULL;
}

bool ODBCPlusCursor::CloseQuery()
{
  if ( mpRSet ) delete mpRSet;

  mpRSet = NULL;

  return TRUE;
}

bool ODBCPlusCursor::FetchFirst()
{
  return mpRSet->first();
}

bool ODBCPlusCursor::FetchNext()
{
  return mpRSet->next();
}

bool ODBCPlusCursor::FetchPrevious()
{
  return mpRSet->previous();
}

void ODBCPlusCursor::SetSortOrder( wxStringList& cols, wxArrayInt& accendingFlags )
{
  mOrderSuffix = "";

  if ( cols.GetCount() != 0 ) 
  {
    mOrderSuffix = " ORDER BY ";

    for( size_t i = 0; i != cols.GetCount(); ++i )
    {
      if ( i > 0 ) mOrderSuffix += ", ";

      mOrderSuffix += cols[ i ];

      if ( accendingFlags[i] == 1 ) mOrderSuffix += " ASC";
                     else mOrderSuffix += " DESC";
    }
  }
}

void ODBCPlusCursor::GetColumnNames( wxStringList& names )
{
  wxASSERT( mpRSet ); // DBG::

  odbc::ResultSetMetaData* pData = mpRSet->getMetaData();

  for( size_t i = 0; i != pData->getColumnCount(); ++i )

    names.Add( pData->getColumnName( i + 1 ).c_str() );
}

wxString ODBCPlusCursor::GetValue( int colIndex )
{
  return mpRSet->getString( colIndex + 1 ).c_str();
}

bool ODBCPlusCursor::IsEof()
{
  return mpRSet->isAfterLast() || mpRSet->isBeforeFirst();
}

void ODBCPlusCursor::ClearEof()
{
  if ( mpRSet->isAfterLast() )

    mpRSet->previous();
  else
  if ( mpRSet->isBeforeFirst() )

    mpRSet->next();
}

#endif // USE_ODBC_PLUS

/***** Implementation for class MyODBCCursor *****/

#if USE_MY_ODBC

#include "wx/wspp/myodbc.h"

MyODBCCursor::MyODBCCursor()
  : mpCon( NULL ),
    mpRSet( NULL ),
    mpStmt( NULL )
{}

MyODBCCursor::~MyODBCCursor()
{
  CloseQuery();
  if ( mpStmt ) delete mpStmt;
  if ( mpCon )  delete mpCon;
}

// overriden methods of DataCursorBase

bool MyODBCCursor::ExecuteQuery( const wxString& query )
{
  if ( !mpCon )
  {
    mpCon = MyDriverManager::GetConnection( mDsn, mUser, mPasswd );

    if ( !mpCon ) return FALSE;
  }

  if ( mpRSet ) CloseQuery();

  if ( !mpStmt )

    mpStmt = mpCon->CreateStatement( MyResultSet::RS_TYPE_SCROLL_SENSITIVE  );

  if ( !mpStmt ) return FALSE;

  mpRSet = mpStmt->ExecuteQuery( (const char*)(query + mOrderSuffix) );

#if USE_ODBC_PLUS 
  mpRSet->SetFetchSize( 200 );
#endif

  return mpRSet != NULL;
}

bool MyODBCCursor::CloseQuery()
{
  if ( mpRSet ) delete mpRSet;

  mpRSet = NULL;

  return TRUE;
}

bool MyODBCCursor::FetchFirst()
{
  return mpRSet->First();
}

bool MyODBCCursor::FetchNext()
{
  return mpRSet->Next();
}

bool MyODBCCursor::FetchPrevious()
{
  return mpRSet->Previous();
}

void MyODBCCursor::SetSortOrder( wxStringList& cols, wxArrayInt& accendingFlags )
{
  mOrderSuffix = "";

  if ( cols.GetCount() != 0 ) 
  {
    mOrderSuffix = " ORDER BY ";

    for( size_t i = 0; i != cols.GetCount(); ++i )
    {
      if ( i > 0 ) mOrderSuffix += ", ";

      mOrderSuffix += cols[ i ];

      if ( accendingFlags[i] == 1 ) mOrderSuffix += " ASC";
                     else mOrderSuffix += " DESC";
    }
  }
}

void MyODBCCursor::GetColumnNames( wxStringList& names )
{
  //wxASSERT( mpRSet ); // DBG::

  MyResultSetMetaData* pData = mpRSet->GetMetaData();

  for( int i = 0; i != pData->GetColumnCount(); ++i )

    names.Add( pData->GetColumnName( i + 1 ) );
}

wxString MyODBCCursor::GetValue( int colIndex )
{
  if ( !mpRSet ) return wxEmptyString;

  return mpRSet->GetString( colIndex + 1 );
}

bool MyODBCCursor::IsEof()
{
  return mpRSet->IsEof();
}

void MyODBCCursor::ClearEof()
{
  // TBD::
}

bool MyODBCCursor::SetCurrentRowPos( int pos )
{
  if ( !mpRSet ) return 0;

  return mpRSet->SetCurrentRowPos( pos );
}

int MyODBCCursor::GetCurrentRowPos()
{
  if ( !mpRSet ) return 0;

  return mpRSet->GetCurrentRowPos();
}

void MyODBCCursor::InsertCacheRowAt( int row, wxStringList& values  )
{
  mpRSet->InsertCacheRows( row, 1 );
  UpdateCacheRow( row, values );
}

void MyODBCCursor::RemoveCacheRow( int row )
{
  mpRSet->RemoveCacheRows( row, 1 );
}

void MyODBCCursor::UpdateCacheRow( int row, wxStringList& values )
{
  for( size_t i = 0; i != values.GetCount(); ++i )
  
    mpRSet->SetCacheRowValue( row, (int)i, values[i] );
}

int MyODBCCursor::FindInsertionPos( int col, const wxString& insValue, bool isAscending )
{
  return mpRSet->FindInsertionPos( col, insValue, isAscending );
}

int MyODBCCursor::GetCacheUsed()
{
  return mpRSet->GetCacheUsed();
}

MyConnection* MyODBCCursor::GetConnection()
{
	return mpCon;
}

#endif // USE_MY_ODBC

/***** Implementation for class wxDbListCtrl *****/

IMPLEMENT_DYNAMIC_CLASS( wxDbListCtrl, wxGenericListCtrl )

BEGIN_EVENT_TABLE( wxDbListCtrl, wxGenericListCtrl )
	
	EVT_ACTIVATE( wxDbListCtrl::OnActivate )

END_EVENT_TABLE()

wxDbListCtrl::wxDbListCtrl()

	: wxGenericListCtrl(),
	  mUseInternalCache( TRUE ),
      mpCursor( NULL ),
      mMaxCacheSize( 100000000 ), // FOR NOW:: inf. large 
      mMaxPrefetchRows( 0 ),
      mFirstCached( 0 ),
	  mQueryFailed( FALSE )
{
	SetModel( this );
}

wxDbListCtrl::wxDbListCtrl( wxWindow *parent, wxWindowID id,
			                const wxPoint& pos, const wxSize& size,
							long style, const wxString& name )

  : wxGenericListCtrl( parent, id, pos, size, style, name ),
    mUseInternalCache( TRUE ),
    mpCursor( NULL ),
    mMaxCacheSize( 100000000 ), // FOR NOW:: inf. large 
    mMaxPrefetchRows( 0 ),
    mFirstCached( 0 ),
	mQueryFailed( FALSE )
{
  SetModel( this );
}

wxDbListCtrl::~wxDbListCtrl()
{
  ClearCache();
  if ( mpCursor ) delete mpCursor;

  mpModel = NULL;
}

// overriden methods of wxListModel

const wxString wxDbListCtrl::GetValue( int row, int col )
{
  if ( mUseInternalCache )
  {
    if ( row < mFirstCached ) 
      
      return wxEmptyString;

    if ( row >= mFirstCached + (int)mCachedData.Count() ) 
      
      return wxEmptyString;

    wxString* dataRow = (wxString*)mCachedData[ row - mFirstCached ];

    return dataRow[col];
  }
  else
  {
    int pos = mpCursor->GetCurrentRowPos();

    mpCursor->SetCurrentRowPos( row );

    wxString s = mpCursor->GetValue( col );

    mpCursor->SetCurrentRowPos( pos );

    return s;
  }
}

int wxDbListCtrl::GetRowCount()
{
  // FOR NOW:: very dumb paging-flip logic

  if ( mMaxRows != -1 ) return mMaxRows;

  if ( mMaxScrolled < 40 ) return 80;

  return ( mMaxScrolled / 3 ) * 4;
}

int wxDbListCtrl::GetFinalRowCount()
{
  // scroll to "EOF"

  while( mMaxRows == -1 )
  {
    FetchMoreRows( 500 );
  }

  return mMaxRows;
}

void wxDbListCtrl::Sort( const wxString& byColumn, bool acending )
{
  if ( mCurQueryString.Length() == 0 )
  {
    mSortReqColName = byColumn;
    mSortReqFlag = acending;

    return;
  }

  mpCursor->SetSortOrder( byColumn, acending );

  RunQuery( mCurQueryString );
}

void wxDbListCtrl::Sort( size_t byColumn, bool acending )
{
  // FIXME:: old stuff

  wxStringList cols; wxArrayInt flags;

  wxStringList colNames;

  mpCursor->GetColumnNames( colNames );

  cols.Add( colNames[ byColumn ] );

  flags.Add( (int)acending );

  mpCursor->SetSortOrder( cols, flags );

  RunQuery( mCurQueryString );
}

void wxDbListCtrl::DrawPageContent( wxDC& dc )
{
  int firstRow = 0, rowsVisible = 0;
  GetPageInfo( firstRow, rowsVisible );

  if ( !mQueryFailed )
  {
	  int tillRow = wxMin( mpModel->GetRowCount(), firstRow + rowsVisible );

	  if ( !IsCached( firstRow ) ||
		 !IsCached( tillRow - 1 ) )
	  {
		mReqFromRow = firstRow;
		mReqRowCcount = tillRow - firstRow;

		HandleFetchRuest();
	  }
  }

  if ( ( mMaxRows > 0 && firstRow < mMaxRows ) ||
     mMaxRows < 0 )

    wxGenericListCtrl::DrawPageContent( dc );
  else
    FillEmptyAreas( dc );


  // FIMXE:: cleaner...

  UpdateScrollBars( -1, GetFirstVisibleRow() / mRowsPerUnit );
}

void wxDbListCtrl::ClearCache()
{
  for( size_t i = 0; i != mCachedData.Count(); ++i )

    delete [] (wxString*) mCachedData[i];

  mCachedData.Clear();

  mFirstCached = 0;
}

void wxDbListCtrl::OnRowChanged( ENUM_ROW_CHANGE_TYPE changeType, 
                    const wxString& PKValue, int PKColIndex,
                    wxStringList& newValues,
                    bool refreshNow )
{
  bool refreshNeeded = FALSE;

#if USE_MY_ODBC
  if ( !mUseInternalCache )
  {
    MyODBCCursor& cursor = *(MyODBCCursor*)mpCursor;

    int actualCursorPos = cursor.GetCurrentRowPos();

    if ( changeType == REMOVED )
    {
      // remove corresponding cache row by PK

      for( int pos = 0; pos != cursor.GetCacheUsed(); ++pos )
      {
        cursor.SetCurrentRowPos( pos );

        if ( cursor.GetValue( PKColIndex ) == PKValue )
        {
          cursor.RemoveCacheRow( pos );
          refreshNeeded = TRUE;
		  --mMaxRows;
          break;
        }
      }
    }
    else
    if ( changeType == MODIFIED )
    {
      for( int pos = 0; pos != cursor.GetCacheUsed(); ++pos )
      {
        cursor.SetCurrentRowPos( pos );

        if ( cursor.GetValue( PKColIndex ) == PKValue )
        {
          cursor.UpdateCacheRow( pos, newValues );
          refreshNeeded = TRUE;
          break;
        }
      }
    }
    else
    if ( changeType == INSERTED )
    {
      wxASSERT( mLastSortedCol != -1 ); // DBG::

      bool isAscending = mColSortOptions[ mLastSortedCol ] == 1;

      wxString insValue = newValues[ mLastSortedCol ];

      int insPos = mpCursor->FindInsertionPos( mLastSortedCol, insValue, isAscending );

      if ( insPos != - 1 )
      {
        cursor.InsertCacheRowAt( insPos, newValues );

        if ( actualCursorPos > (int)insPos )

          ++actualCursorPos;

        refreshNeeded = TRUE;
		++mMaxRows;
      }
    }

    cursor.SetCurrentRowPos( actualCursorPos );
  }
#endif

  if ( mUseInternalCache )
  {
    wxASSERT( mFirstCached == 0 ); // DBG::

    if ( changeType == REMOVED )
    {
      // remove corresponding cache row by PK

      for( size_t i = 0; i != mCachedData.Count(); ++i )
      {
        wxString* pRow = (wxString*) mCachedData[i];

        if ( PKValue == pRow[ PKColIndex ] )
        {
          delete pRow;
          mCachedData.Remove( i );

          if ( mCursorPos > (int)i )

            --mCursorPos;

          refreshNeeded = TRUE;
		  --mMaxRows;

          break;
        }
      }
    }
    else
    if ( changeType == MODIFIED )
    {
      for( size_t i = 0; i != mCachedData.Count(); ++i )
      {
        wxString* pRow = (wxString*) mCachedData[i];

        if ( PKValue == pRow[ PKColIndex ] )
        {
          // apply new values to the cache-row

          for( size_t k = 0; k != mQueryColumnNames.GetCount() &&
							 k < newValues.GetCount(); ++k )
          {
            pRow[k] = newValues[k];
          }

          refreshNeeded = TRUE;

          break;
        }
      }
    }
    else
    if ( changeType == INSERTED )
    {
      wxASSERT( mLastSortedCol != -1 ); // DBG::

      bool isAscending = mColSortOptions[ mLastSortedCol ] == 1;

      wxString insValue = newValues[ mLastSortedCol ];

      // find out if inserted row goes somewhere inside the
      // cached row-set

      for( size_t i = 0; i != mCachedData.Count(); ++i )
      {
        wxString* pRow = (wxString*) mCachedData[i];

        bool hits = FALSE;

        if ( isAscending && pRow[ mLastSortedCol ].CmpNoCase( insValue ) > 0 )

          hits = TRUE;

        if ( !isAscending && pRow[ mLastSortedCol ].CmpNoCase( insValue ) < 0 )

          hits = TRUE;

        if ( hits )
        {
          // insert new row into guessed-position inside the cache

          pRow = new wxString[ mQueryColumnNames.GetCount() ];

          for( size_t k = 0; k != mQueryColumnNames.GetCount() &&
			                 k < newValues.GetCount(); ++k )

            pRow[k] = newValues[k];

          mCachedData.Insert( pRow, i );

          if ( mCursorPos > (int)i )

            ++mCursorPos;

          refreshNeeded = TRUE;
		  ++mMaxRows;

          break;
        }
      }
    }
  }

  if ( refreshNeeded && refreshNow )
  {
	UpdateScrollBars();
    Refresh();
  }
}

bool wxDbListCtrl::FindRow( const wxString& pattern, wxStringList& inColumns,
                   bool startOver, bool exactMatch, 
                   bool ignoreCase, bool matchBegining,
                   bool forward )
{
  wxASSERT( mFirstCached == 0 ); // DBG::

  int row = startOver ? 0 : mFocusedRow + 1;

  if ( !startOver && !forward )
  {
    row = mFocusedRow - 1;

    if ( mFocusedRow == 0 ) 

      return FALSE;
  }

  int* indecies = new int[ inColumns.GetCount() ];

  size_t i = 0;
  for( ; i != inColumns.GetCount(); ++i )
  {
    int idx = GetColumnIndex( inColumns[i] );

    wxASSERT( idx != -1 ); // DBG::

    indecies[ i ] = idx;
  }

  size_t colCount = inColumns.GetCount();

  bool matched = FALSE;

  while( mMaxRows == -1 || row < mMaxRows )
  {
    if ( row > mCursorPos )
    {
      FetchMoreRows( 100 );

      if ( row > mCursorPos )

        break;
    }

    bool isInCache = row < (int)mCachedData.Count();

    if ( !mUseInternalCache )
    {
      if ( mMaxRows != -1 )
    
        isInCache = row <= mMaxRows;
      else
        isInCache = row <= mpCursor->GetCurrentRowPos();
    }
    
    if ( isInCache )
    {
      for( i = 0; i != colCount; ++i )
      {
        wxString value;

        if ( mUseInternalCache )
        {
          wxString* pRow = (wxString*)mCachedData[ row ];
          value = pRow[ indecies[i] ];
        }
        else
          value = GetValue( row, indecies[i] );

        if ( exactMatch )
        {
          if ( ( !ignoreCase && value == pattern ) || 
             ( ignoreCase && value.CmpNoCase( pattern ) == 0 ) ) 
          {
            matched = TRUE; 
            break;
          }
        }
        else
        {
          int pos = -1;

          // there is no wxString::Find which ignores char-case,
          // so we write our own

          char* val = (char*)(const char*)value;
          char* ptn = (char*)(const char*)pattern;
          char* start = val;

          for(; *val ;)
          {
            if ( toupper( *val ) == toupper( *ptn ) )
            {
              char* v = val+1; char* p = ptn+1;

              while( toupper( *v ) == toupper( *p ) && *v && *p )
              {
                ++v; ++p;
              }

              if ( !*p )
              {
                pos = (int)(val - start);
                break;
              }
            }

            ++val;
          }

          if ( pos != -1 )
          {
            if ( ( matchBegining && pos == 0 ) || !matchBegining ) 
            {
              matched = TRUE;
              break;
            }
          }
        }
      }

    } // end of for(...)

    if ( !forward && row == 0 )

      break;

    if ( matched )

      break;

    if ( forward )

      ++row;
    else
      --row;

  } // end of while(...)
      
  if ( matched )

    SelectRow( row );

  return matched;
}

void wxDbListCtrl::FetchMoreRows( int count )
{
  mpCursor->ClearEof();

  int tillRow = mCachedData.Count() + count;

  if ( !mUseInternalCache )
  {
    if ( mpCursor->GetCurrentRowPos() < 0 )

      mpCursor->FetchNext();

    mCursorPos = mpCursor->GetCurrentRowPos();

    tillRow = mpCursor->GetCurrentRowPos() + count;
  }

  while( mCursorPos < tillRow )
  { 
    if ( !DoFetchNext( TRUE ) )

      break;
  }

  if ( mpCursor->IsEof() )
  {
    mMaxRows = mCursorPos + 1;
  }
}

void wxDbListCtrl::OnActivate( wxActivateEvent& event )
{
	wxMessageBox( "OnActivate!" );
}

void wxDbListCtrl::SetDataCursor( DataCursorBase* pCursor )
{
  ClearCache();
  mpCursor = pCursor;

  mCursorPos = -1;
  mMaxScrolled = -1;
  mMaxRows = -1;
}

DataCursorBase* wxDbListCtrl::GetDataCursor()
{
  if ( !mpCursor )
  {
#if USE_ODBC_PLUS
    mpCursor = new ODBCPlusCursor();
#elif USE_WX_ODBC
    mpCursor = new ODBCDataCursor();
#elif USE_MY_ODBC
    mpCursor = new MyODBCCursor();

    mUseInternalCache = FALSE; // MyODBCCursor's impl. caches automatically...
#else
  #error "Data-cursor implementation is not #define'd "
#endif

  }

  return mpCursor;
}

void wxDbListCtrl::SetODBCDataSource( const wxString& dsn,
                       const wxString& user,
                       const wxString& passwd )
                       
{
  DataCursorBase* pCursor = GetDataCursor();

  pCursor->ODBCSetup( dsn, user, passwd );
}

bool wxDbListCtrl::RunQuery( const wxString& query )
{
  /*
  // check if the sort-option is specified, if none,
  // setup default, because the resultset should be sorted in
  // order to react notifications of external changes
  // properly

  if ( GetLastSortedColumn() == -1 )
  {
    wxArrayInt cols; wxArrayInt flags;

    bool isAscending = TRUE;
    int col = 0;

    cols.Add( col );
    flags.Add( (int)isAscending );

    mpCursor->SetSortOrder( cols, flags );
  }
  */

  ClearCache();

  mCursorPos = -1;
  mMaxScrolled = -1;
  mMaxRows = -1;

  if ( mCurQueryString.Length() )
  
    GetDataCursor()->CloseQuery();

  if ( mSortReqColName.Length() )
  
    mpCursor->SetSortOrder( mSortReqColName, mSortReqFlag );

  mCurQueryString = query;

  mFocusedRow = 0;
  mSelection.Clear();
  mSelection.Add( 0 );

  mQueryFailed = FALSE;

  bool ok = GetDataCursor()->ExecuteQuery( query );

  if ( !ok )
  {
	mQueryFailed = TRUE;
    wxLogError( "\nERROR:: (wxDbListCtrl) execution of query \"" + query + "\" failed!" );
	return FALSE;
  }

  if ( mQueryColumnNames.GetCount() == 0 )
  {
    RemoveAllColumns();

    mpCursor->GetColumnNames( mQueryColumnNames );

    int reqCol = -1;

    for( int i = 0; i != (int)mQueryColumnNames.GetCount(); ++i )
    {
	  wxString col = wxString( mQueryColumnNames[ i ] ).Upper();

      if ( col == mSortReqColName.Upper() )

        reqCol = i;

      this->AddColumn( mQueryColumnNames[ i ] );
    }
    
    LayoutHeadingsByWidths();

    if ( reqCol != -1 )
    {
      mLastSortedCol = reqCol;
      mColSortOptions[ reqCol ] = (int)mSortReqFlag;
      mSortReqColName = "";
    }
  }

  Refresh();

  return TRUE;
}

wxStringList& wxDbListCtrl::GetQueryColumnNames()
{
  return mQueryColumnNames;
}

/*** protected methods ***/

void wxDbListCtrl::CacheCursorRow()
{
  // assertain that cursor-movement was gradual (by +/-1 incraments)

  wxASSERT( mCursorPos >= 0 ); // DBG::
  wxASSERT( mCursorPos - mFirstCached >= -1 ); // DBG::

  int u = mCursorPos - ( mFirstCached + mCachedData.Count() );
  wxASSERT( u <= 0 ); // DBG::

  if ( mCursorPos >= mFirstCached && mCursorPos < mFirstCached + (int)mCachedData.Count() )

    return; // hits already cached row
    
  int nCols = GetColumnCount();
  wxString* dataRow = new wxString[ nCols ];

  if ( mCursorPos >= mFirstCached )
  {
    mCachedData.Add( dataRow ); // at the end

    // control size of the cache while growing forward

    if ( (int)mCachedData.Count() > mMaxCacheSize )
    {
      delete [] (wxString*)mCachedData[ 0 ];
      mCachedData.Remove( (size_t)0 );

      ++mFirstCached;
    }
  }
  else
  {
    mCachedData.Insert( dataRow, 0 ); // at the begining
    --mFirstCached;

    // control size of the cache while growing backward

    if ( (int)mCachedData.Count() > mMaxCacheSize )
    {
      delete [] (wxString*)mCachedData[ mCachedData.Count() - 1 ];
      mCachedData.Remove( mCachedData.Count() - 1 );
    }
  }

  for( int i = 0; i != nCols; ++i )
  
    dataRow[i] = mpCursor->GetValue( i );
}

bool wxDbListCtrl::IsCached( int row )
{
  return row >= mFirstCached && row < mFirstCached + (int)mCachedData.Count();
}

void wxDbListCtrl::HandleFetchRuest()
{
	if ( !mpCursor )

		return;

    bool isForward = TRUE;
    int  tillRow   = mReqFromRow + mReqRowCcount;
    int  inc = 1;

    if ( mReqFromRow < mFirstCached )
    {
      isForward = FALSE;
      tillRow = mReqFromRow - 1;
      inc = -1;
    }

    // first try to fetch as may rows as requested

    mpCursor->ClearEof();

    while( mCursorPos + inc != tillRow )
    {
      if  ( !DoFetchNext( isForward ) )

        break;
    }

    if ( isForward && mpCursor->IsEof() )
    {
      mMaxRows = mCursorPos + 1;
    }

    // notify requesting thread

    // request sutisffied, now do prefetching if possible

    if ( mMaxPrefetchRows > 0 && 
       isForward &&  // FOR NOW:: prefetching only works forward!
       mCursorPos + inc == tillRow && !mpCursor->IsEof() )
    {
      int nPrefetched = 0;

      // FIXME:: this lock migth block the released GUI-thread for the
      //       time of prefetching :(

      while( nPrefetched < mMaxPrefetchRows )
      {
        if ( !DoFetchNext( isForward ) )

          break;

        ++nPrefetched;
      }

      if ( isForward && mpCursor->IsEof() )
      {
        mMaxRows = mCursorPos + 1;
      }
    }

	mReqFromRow = 0;
	mReqRowCcount = 0;
}

bool wxDbListCtrl::DoFetchNext( bool forward )
{
  bool ok = FALSE;

  if ( forward ) 
  {
    ok = mpCursor->FetchNext();
    if ( ok ) ++mCursorPos;
  }
  else 
  {
    ok = mpCursor->FetchPrevious();
    if ( ok ) --mCursorPos;
  }

  if ( ok ) 
  {
    if ( mUseInternalCache )

      CacheCursorRow();

    mMaxScrolled = wxMax( mMaxScrolled, mCursorPos );
  }

  return ok;
}
