#ifndef __HARBOUR__
#include "clipper.ch"
#endif

#include "directry.ch"
#include "ord.ch"

#define MAX_TEST_RECS   100
#define INDEX_KEY_CHAR  CHAR + Str( NUM ) + DToS( DATE )
#define INDEX_KEY_NUM   NUM
#define INDEX_KEY_DATE  DATE
#define INDEX_KEY_LOG   LOG

#ifdef WITH_ADS
#include "ads.ch"
EXTERNAL _ADS
#endif

EXTERNAL DBFNTX
EXTERNAL DBFCDX

PROCEDURE Main( cRDDType, cAdsMode )

   LOCAL cRDD := "", aStruct, xTemp, bMemoText

   FIELD CHAR, NUM, DATE, LOG

   bMemoText := {|| "This is memo #" + hb_ntos( RecNo() ) + "." + hb_eol() + ;
      hb_eol() + ;
      "This is a very long string. " + ;
      "This may seem silly however strings like this are still " + ;
      "used. Not by good programmers though, but I've seen " + ;
      "stuff like this used for Copyright messages and other " + ;
      "long text. What is the point to all of this you'd say. " + ;
      "Well I am coming to the point right now, the constant " + ;
      "string is limited to 256 characters and this string is " + ;
      "a lot bigger. Do you get my drift ? If there is somebody " + ;
      "who has read this line upto the very end: Esto es un " + ;
      "sombrero grande ridiculo." + hb_eol() + "/" + hb_eol() + "[;-)" + hb_eol() + "\" }

   DO CASE
   CASE Empty( cRDDType )

      NotifyUser( "Usage: TESTRDD2 RDDTYPE [ADSMODE]" + hb_eol() + ;
         hb_eol() + ;
         "RDDTYPE = DBFNTX, DBFCDX, ADSCDX, ADSNTX or ADSADT" + hb_eol() + ;
         hb_eol() + ;
         "ADSMODE = LOCAL or SERVER (only applies to ADSCDX, ADSNTX and ADSADT)" + hb_eol() + ;
         "(If specify SERVER, must be run from a drive suported by ADS server)", .T. )

#ifdef WITH_ADS

   CASE Left( cRDDType := Upper( AllTrim( cRDDType ) ), 3 ) == "ADS"

      rddRegister( "ADS", 1 )
      rddSetDefault( "ADS" )

      IF Empty( cADSMode )
         NotifyUser( "Missing ADS mode" )
      ENDIF

      cADSMode := Upper( AllTrim( cADSMode ) )

      DO CASE
      CASE cADSMode == "LOCAL"  ; AdsSetServerType( ADS_LOCAL_SERVER )
      CASE cADSMode == "SERVER" ; AdsSetServerType( ADS_REMOTE_SERVER )
      OTHERWISE                 ; NotifyUser( "Bad ADS mode" )
      ENDCASE

      DO CASE
      CASE cRDDType == "ADSNTX" ; AdsSetFileType( ADS_NTX ) ; cRDD := "ADSNTX"
      CASE cRDDType == "ADSADT" ; AdsSetFileType( ADS_ADT ) ; cRDD := "ADSADT"
      CASE cRDDType == "ADSCDX" ; AdsSetFileType( ADS_CDX ) ; cRDD := "ADSCDX"
      OTHERWISE                 ; NotifyUser( "Bad ADS flavor" )
      ENDCASE

      AdsLocking( .T. )
      AdsRightsCheck( .T. )

      AdsSetDefault( "" )
      AdsSetSearchPath( "" )

#endif

   CASE cRDDType == "DBFCDX" .OR. ;
        cRDDType == "DBFNTX"

      rddSetDefault( cRDD := cRDDType )

   OTHERWISE

      NotifyUser( "Bad DBF flavor" )

   ENDCASE

   // Delete test_?.* since may be changing RDD flavors (avoid conflicts)
   AEval( Directory( "test_?.*" ), {| a | hb_dbDrop( a[ F_NAME ] ) } )

   IF hb_dbExists( "test_2.dbf" )
      NotifyUser( "Cannot delete test_2.dbf" )
   ENDIF

   // TEST: dbCreate()

   dbCreate( "test_2.dbf", aStruct := { ;
      { "CHAR", "C", 30, 0 }, ;
      { "NUM",  "N", 15, 3 }, ;
      { "DATE", "D",  8, 0 }, ;
      { "LOG",  "L",  1, 0 }, ;
      { "MEMO", "M", 10, 0 } } )

   IF ! hb_dbExists( "test_2.dbf" )
      NotifyUser( "Failed to create test_2.dbf" )
   ENDIF

   // TEST: dbUseArea()/USE

   USE test_2.dbf NEW SHARED ALIAS mytest

   IF ! Alias() == "MYTEST"
      NotifyUser( "Failed to open test_2.dbf" )
   ENDIF

   // TEST: rddName()

   IF ! rddName() == cRDD
      NotifyUser( "Failed to set RDD to " + cRDD )
   ENDIF

   // TEST: dbStruct()

   IF ! CompareArray( aStruct, dbStruct() )
      NotifyUser( "Resulting table structure is not what we asked for" )
   ENDIF

   // TEST: Header()

   IF ! Header() == 194
      NotifyUser( "Header() returned wrong size (" + hb_ntos( Header() ) + " bytes)" )
   ENDIF

   // Add a mix of data to table

   DO WHILE LastRec() < MAX_TEST_RECS

      // TEST: dbAppend()/APPEND BLANK

      APPEND BLANK

      // TEST: REPLACE

      REPLACE CHAR WITH Chr( Asc( "A" ) + Val( SubStr( hb_ntos( RecNo() ), 2, 1 ) ) ) + ;
         " RECORD " + hb_ntos( RecNo() )

      // TEST: Direct field assigment

      MYTEST->NUM  := ( iif( RecNo() % 2 > 0, - 1, 1 ) * RecNo() ) + ( RecNo() / 1000 )
      MYTEST->DATE := Date() + Int( FIELD->NUM )
      MYTEST->LOG  := ( FIELD->NUM < 0 )
      MYTEST->MEMO := Eval( bMemoText )

   ENDDO

   // TEST: LastRec()

   IF ! LastRec() == MAX_TEST_RECS
      NotifyUser( "dbAppend() and/or LastRec() failed" )
   ENDIF

   // TEST: dbGoBottom()/GO BOTTOM

   GO BOTTOM

   IF ! RecNo() == MAX_TEST_RECS
      NotifyUser( "dbGoBottom() failed" )
   ENDIF

   // TEST: dbGoTop()/GO TOP

   GO TOP

   IF ! RecNo() == 1
      NotifyUser( "dbGoTop() failed" )
   ENDIF

   // Now check each and every record for accuracy

   DO WHILE ! Eof()

      // TEST: Field access

      IF ! RTrim( FIELD->CHAR ) == Chr( Asc( "A" ) + Val( SubStr( hb_ntos( RecNo() ), 2, 1 ) ) ) + ;
            " RECORD " + hb_ntos( RecNo() ) .OR. ;
            ! FIELD->NUM == ( iif( RecNo() % 2 > 0, - 1, 1 ) * RecNo() ) + ( RecNo() / 1000 ) .OR. ;
            ! FIELD->DATE == Date() + Int( FIELD->NUM ) .OR. ;
            ! FIELD->LOG == ( FIELD->NUM < 0 ) .OR. ;
            ! FIELD->MEMO == Eval( bMemoText )

         NotifyUser( "Data in table is incorrect" )

      ENDIF

      SKIP

   ENDDO

   // TEST: Index creation

   INDEX ON INDEX_KEY_CHAR TO test_c.idx
   INDEX ON INDEX_KEY_NUM  TO test_n.idx ADDITIVE
   INDEX ON INDEX_KEY_DATE TO test_d.idx ADDITIVE
   INDEX ON INDEX_KEY_LOG  TO test_l.idx ADDITIVE

   // TEST: IndexOrd()

   IF ! IndexOrd() == 4
      NotifyUser( "Bad IndexOrd()" )
   ENDIF

   // TEST: DBOI_KEYCOUNT

   SET ORDER TO 1
   IF ! dbOrderInfo( DBOI_KEYCOUNT ) == MAX_TEST_RECS
      NotifyUser( "Bad DBOI_KEYCOUNT/1" )
   ENDIF

   SET ORDER TO 2
   IF ! dbOrderInfo( DBOI_KEYCOUNT ) == MAX_TEST_RECS
      NotifyUser( "Bad DBOI_KEYCOUNT/2" )
   ENDIF

   SET ORDER TO 3
   IF ! dbOrderInfo( DBOI_KEYCOUNT ) == MAX_TEST_RECS
      NotifyUser( "Bad DBOI_KEYCOUNT/3" )
   ENDIF

   SET ORDER TO 4
   IF ! dbOrderInfo( DBOI_KEYCOUNT ) == MAX_TEST_RECS
      NotifyUser( "Bad DBOI_KEYCOUNT/4" )
   ENDIF

   // TEST: Character index
   SET ORDER TO 1
   GO TOP
   IF ! dbOrderInfo( DBOI_KEYVAL ) == INDEX_KEY_CHAR
      NotifyUser( "Bad DBOI_KEYVAL (CHAR)" )
   ENDIF

   // TEST: Positive index key
   SET ORDER TO 2
   LOCATE for FIELD->NUM > 0
   IF ! dbOrderInfo( DBOI_KEYVAL ) == INDEX_KEY_NUM
      NotifyUser( "Bad DBOI_KEYVAL (NUM)" )
   ENDIF

   // TEST: Negative index key
   SET ORDER TO 2
   LOCATE for FIELD->NUM < 0
   IF ! dbOrderInfo( DBOI_KEYVAL ) == INDEX_KEY_NUM
      NotifyUser( "Bad DBOI_KEYVAL (NUM)" )
   ENDIF

   // TEST: Date index
   SET ORDER TO 3
   GO BOTTOM
   IF ! dbOrderInfo( DBOI_KEYVAL ) == INDEX_KEY_DATE
      NotifyUser( "Bad DBOI_KEYVAL (DATE)" )
   ENDIF

   // TEST: Logical index
   SET ORDER TO 4
   GO TOP
   IF ! dbOrderInfo( DBOI_KEYVAL ) == INDEX_KEY_LOG
      NotifyUser( "Bad DBOI_KEYVAL (LOG/1)" )
   ENDIF
   GO BOTTOM
   IF ! dbOrderInfo( DBOI_KEYVAL ) == INDEX_KEY_LOG
      NotifyUser( "Bad DBOI_KEYVAL (LOG/2)" )
   ENDIF

   // TEST: EXACT with a locate

   SET ORDER TO 0

   SET EXACT ON
   LOCATE for FIELD->CHAR = "J RECORD"
   IF ! Eof()
      NotifyUser( "LOCATE with EXACT ON failed" )
   ENDIF

   SET EXACT OFF
   LOCATE for FIELD->CHAR = "J RECORD"
   IF Eof()
      NotifyUser( "LOCATE with EXACT OFF failed" )
   ENDIF

   // TEST: EXACT with an index (also tests COUNT)

   SET EXACT ON
   SET ORDER TO 0
   COUNT FOR RTrim( FIELD->CHAR ) = "A RECORD 1" TO xTemp  // Get proper count
   INDEX ON CHAR TO test_e.idx FOR RTrim( FIELD->CHAR ) = "A RECORD 1" ADDITIVE
   IF ! dbOrderInfo( DBOI_KEYCOUNT ) == xTemp
      NotifyUser( "Bad conditional index count with EXACT ON" )
   ENDIF

   SET EXACT OFF
   SET ORDER TO 0
   COUNT FOR RTrim( FIELD->CHAR ) = "A RECORD 1" TO xTemp  // Get proper count
   INDEX ON CHAR TO test_e.idx FOR RTrim( FIELD->CHAR ) = "A RECORD 1" ADDITIVE
   IF ! dbOrderInfo( DBOI_KEYCOUNT ) == xTemp
      NotifyUser( "Bad conditional index count with EXACT OFF" )
   ENDIF

   //
   // PUT MORE RDD TESTS HERE
   //

   // TEST: dbCloseArea()

   MYTEST->( dbCloseArea() )

   IF Select( "MYTEST" ) > 0
      NotifyUser( "Failed to close table" )
   ENDIF

   NotifyUser( "Test PASSED! :-)", .T. )

   RETURN

PROCEDURE ErrorSys()

   ErrorBlock( {| e | MyError( e ) } )

   RETURN

STATIC PROCEDURE MyError( e )

   LOCAL i := 1 /* Start are "real" error */
   LOCAL cErr

   cErr := "Runtime error" + hb_eol() + ;
      hb_eol() + ;
      "Gencode: " + hb_ntos( e:GenCode ) + hb_eol() + ;
      "Desc: " + e:Description +  + hb_eol() + ;
      "Sub-system: " + hb_ntos( e:SubCode ) + hb_eol() + ;
      hb_eol() + ;
      "Call trace:" + hb_eol() + ;
      hb_eol()

   DO WHILE ! Empty( ProcName( ++i ) )
      cErr += RTrim( ProcName( i ) ) + "(" + hb_ntos( ProcLine( i ) ) + ")" + hb_eol()
   ENDDO

   NotifyUser( cErr )  // Calls quit

   RETURN

STATIC FUNCTION CompareArray( a1, a2 )

   LOCAL i, j

   IF ! Len( a1 ) == Len( a2 )
      RETURN .F.
   ENDIF

   FOR i := 1 TO Len( a1 )

      FOR j := 1 TO Len( a1[ i ] )

         IF ! a1[ i, j ] == a2[ i, j ]
            RETURN .F.
         ENDIF

      NEXT

   NEXT

   RETURN .T.

STATIC PROCEDURE NotifyUser( cErr, lNotError )

   HB_SYMBOL_UNUSED( lNotError )

   ? cErr

   dbCloseAll()

   hb_dbDrop( "test_2" )
   hb_dbDrop( "test_e.idx" )
   hb_dbDrop( "test_c.idx" )
   hb_dbDrop( "test_n.idx" )
   hb_dbDrop( "test_d.idx" )
   hb_dbDrop( "test_l.idx" )

   QUIT

   RETURN
