/*
 * $Id$
 */

/*
 * Harbour Project source code:
 * FFILE1.PRG Filebase class for hbdoc
 *
 * Copyright 2000 Luiz Rafael Culik <culik@sl.conex.net>
 * www - http://www.harbour-project.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA (or visit the web site http://www.gnu.org/).
 *
 * As a special exception, the Harbour Project gives permission for
 * additional uses of the text contained in its release of Harbour.
 *
 * The exception is that, if you link the Harbour libraries with other
 * files to produce an executable, this does not by itself cause the
 * resulting executable to be covered by the GNU General Public License.
 * Your use of that executable is in no way restricted on account of
 * linking the Harbour library code into it.
 *
 * This exception does not however invalidate any other reasons why
 * the executable file might be covered by the GNU General Public License.
 *
 * This exception applies only to the code released by the Harbour
 * Project under the name Harbour.  If you copy code from other
 * Harbour Project or Free Software Foundation releases into a copy of
 * Harbour, as the General Public License permits, the exception does
 * not apply to the code that you add in this way.  To avoid misleading
 * anyone as to the status of such modified files, you must delete
 * this exception notice from them.
 *
 * If you write modifications of your own for Harbour, it is your choice
 * whether to permit this exception to apply to your modifications.
 * If you do not wish that, delete this exception notice.
 *
 */

#include "hbclass.ch"
#include 'common.ch'
#include 'hbdocdef.ch'

*+北北北北北北北北北北北北北北北北北北北北北北北北北北北北北北北北北北
*+
*+    Class HBMake_FileBase
*+
*+北北北北北北北北北北北北北北北北北北北北北北北北北北北北北北北北北北
*+
CLASS HBMake_FileBase FROM FileMan

   DATA nOpenMode   // Holds the value to use when opening the file
   DATA nCreateMode // Holds the value to use when creating the file
   DATA nDosHandle  // Holds the DOS file handle for this

   DATA nEndOfFile  // Holds the last byte value in the file
   DATA nSkipLength // This holds the default skpping length of 1
   DATA cName       // This holds the name of the file being worked on
   DATA nPosition   // This holds the position in the file at
   DATA lAtBottom   // This is a value to show if at bottom of file
   DATA lAtTop      // This is a value to show if at top of file

   METHOD new( cname )                  // This is the constructor for the file
   METHOD FOPEN()   // This opens the specified file
   METHOD closefile()                   // This closes the specified file
   METHOD fskip( n )                    // Moves the byte pointer within the file
   METHOD FWRITE( c )                   // Write passed data to file w/ len() == nSkipLenght
   METHOD retrieve()                    // Returns the contents of the file at current pos
   METHOD fgoTop()  // Move the byte pointer to the top of the file
   METHOD fgoBottom()                   // Move the byte pointer to the bottom of the file
   METHOD fgoto()   // Moves the byte pointer to a specific location
   METHOD create()
   message fappend METHOD fappendByte( cByte )
   message BuffGet METHOD BufferGet( lDirection )
   METHOD SKIP( nRecord )               // Moves the byte pointer within the file
   METHOD WRITE( cChar )                // Write passed data to file w/ len() == nSkipLenght
   METHOD goTop()   // Move the byte pointer to the top of the file
   METHOD goBottom()                    // Move the byte pointer to the bottom of the file
   METHOD GOTO( nValue )                // Moves the byte pointer to a specific location
   METHOD OPEN()
   message append METHOD appendLine( cline )

ENDCLASS

   /* Method:  Init/New
   Params:  N/A
   Returns: Self
   Purpose: Constructor
*/
METHOD new( cName ) CLASS HBMake_FileBase

   ::super:new()
   // first thing to do is check to see if there is a valid file

   ::nSkipLength := 1
   ::nOpenMode   := 2                   // Mode for which to open the file
   ::nCreateMode := 0                   // Mode for which to create the file

   ::cName := cName

   RETURN ( self )

   /* Method:  skip( <nRecords> )
   Params:  <nRecords>
   Returns: Self
   Purpose: This method moves the file's byte pointer <nRecords> position
            from the current location.  The actualy movement is determined
            on the value of ::nSkipLength which holds the skipping base.
            This class's purpose is to do one byte movements.
*/
METHOD fskip( nRecords ) CLASS HBMake_FileBase

   DEFAULT nRecords TO 1

   IF ::noDosError() .AND. ::nDosHandle > 0
      FSEEK( ::nDosHandle, ( ::nSkipLength * nRecords ), 1 )
      ::nLastDosMessage := FERROR()
      ::nPosition       := FSEEK( ::nDosHandle, 0, 1 )
      DO CASE
         CASE ::nPosition == ::nEndOfFile
            ::lAtBottom := pTRUE
            ::lAtTop    := pFALSE
         CASE ::nPosition <= 1
            ::lAtTop    := pTRUE
            ::lAtBottom := pFALSE
         OTHERWISE
            ::lAtBottom := ::lAtTop := pFALSE
      ENDCASE
   ENDIF

   RETURN ( self )

   /* Method:  gotop()
   Params:  N/A
   Returns: Self
   Purpose: Move the byte pointer to the top of the file
*/
METHOD fgotop() CLASS HBMake_FileBase

   IF ::noDosError() .AND. ::nDosHandle > 0
      ::nPosition       := FSEEK( ::nDosHandle, 0, 0 )
      ::nLastDosMessage := FERROR()
      ::lAtTop          := pTRUE
      ::lAtBottom       := pFALSE
   ENDIF

   RETURN ( self )

   /* Method:  gobottom()
   Params:  N/A
   Returns: Self
   Purpose: Move hte byte pointer of the file to tbe bottom.
*/
METHOD fgoBottom() CLASS HBMake_FileBase

   IF ::noDosError() .AND. ::nDosHandle > 0
      ::nPosition       := FSEEK( ::nDosHandle, 0, 2 )
      ::nLastDosMessage := FERROR()
      ::lAtTop          := pFALSE
      ::lAtBottom       := pTRUE
   ENDIF

   RETURN ( self )

   /* Method:  close()
   Params:  N/A
   Returns: Self
   Purpose: To close the file
*/
METHOD closefile() CLASS HBMake_FileBase

   IF ::noDosError() .AND. ::nDosHandle > 0
      FCLOSE( ::nDosHandle )
      ::nLastDosMessage := FERROR()
      ::delItem( ::nDosHandle )
      ::lAtTop    := ::lAtBottom := pFALSE
      ::nPosition := 0
   ENDIF

   RETURN ( self )

   /* Method:  retrieve
   Params:  N/A
   Returns: <cChar>
   Purpose: To return the contents of the file at the current position based
            on the length of ::nSkipLength.
*/
METHOD retrieve() CLASS HBMake_FileBase

   LOCAL cReturn       // as char
   LOCAL nMoved        // as int

   IF ::noDosError() .AND. ::nDosHandle > 0
      cReturn           := SPACE( ::nSkipLength )
      nMoved            := FREAD( ::nDosHandle, @cReturn, ::nSkipLength )
      ::nLastDosMessage := FERROR()
      FSEEK( ::nDosHandle, - ( nMoved ), 1 )                // Re-position the pointer
   ENDIF

   RETURN ( cReturn )

   /* Method:  write(<cChar>)
   Params:  <cChar>
   Returns: Self
   Purpose: To write out to the contents of the file the value in the
            parameter <cChar>.
*/
METHOD FWRITE( cChar ) CLASS HBMake_FileBase

   IF ::noDosError() .AND. ::nDosHandle > 0
      IF cChar IS pCHARACTER
         FWRITE( ::nDosHandle, cChar, 1 )
         ::nLastDosMessage := FERROR()
         IF ::noDosError()
            FSEEK( ::nDosHandle, ::nPosition, 0 )           // Re-position the pointer
         ENDIF
      ENDIF
   ENDIF

   RETURN ( self )

   /* Method:  goto(<nRecord>)
   Params:  <nRecord>       The record byte to move to
   Returns: Self
   Purpose: This method moves the byte marker to the <nRecord> position
            within the file.  It is also based on the value stored to the
            ::nSkipLength instance variable
*/
METHOD fgoto( nValue ) CLASS HBMake_FileBase

   IF ::noDosError() .AND. ::nDosHandle > 0
      IF nValue IS pNUMERIC
         IF nValue > 0 .AND. ;
                    ( nValue * ::nSkipLength ) <= ::nEndOfFile
            FSEEK( ::nDosHandle, ( nValue * ::nSkipLength ), 0 )
            ::nLastDosMessage := FERROR()
            ::nPosition       := FSEEK( ::nDosHandle, 0, 1 )
            DO CASE
               CASE ::nPosition == ::nEndOfFile
                  ::lAtBottom := pTRUE
                  ::lAtTop    := pFALSE
               CASE ::nPosition <= 1
                  ::lAtTop    := pTRUE
                  ::lAtBottom := pFALSE
               OTHERWISE
                  ::lAtBottom := ::lAtTop := pFALSE
            ENDCASE
         ENDIF
      ENDIF
   ENDIF

   RETURN ( ::nPosition )

   /* Method:  create()
   Params:  N/A
   Returns: Self
   Purpose: Creates the specified file with the proper access code
*/
METHOD Create() CLASS HBMake_FileBase

   LOCAL nFile         // as int

   IF ::noDosError()
      nFile             := FCREATE( ::cName, ::nCreateMode )
      ::nLastDosMessage := FERROR()
      IF ::noDosError()                 // No Error
         FCLOSE( nFile )                // Close the file
         ::fopen()  // Re-open the file
      ENDIF
   ENDIF

   RETURN ( self )

   /* Method:  open()
   Params:  N/A
   Returns: Self
   Purpose: Opens the file with the proper access code
*/
METHOD FOPEN() CLASS HBMake_FileBase

   IF ::noDosError()
      ::nDosHandle :=::openfile( ::cName, ::nOpenMode )
      ::nEndOfFile := FSEEK( ::nDosHandle, 0, 2 )
      ::nPosition  := FSEEK( ::nDosHandle, 0, 0 )
      ::lAtTop     := pTRUE
      ::lAtBottom  := pFALSE
   ENDIF

RETURN ( self )

METHOD fappendByte( cByte ) CLASS HBMake_FileBase

   DEFAULT cByte TO ""

   IF !EMPTY( cByte )                   // Valid line
      IF ::noDosError() .AND. ::nDosHandle > 0              // No error
         FSEEK( ::nDosHandle, 0, 2 )
         FWRITE( ::nDosHandle, cByte, 1 )
         ::nEndOfFile  := FSEEK( ::nDosHandle, 0, 2 )
         ::nPosition   := FSEEK( ::nDosHandle, - ( LEN( cByte ) ), 2 )
         ::nSkipLength := LEN( cByte )
         ::lAtBottom   := ::lAtTop := pFALSE
      ENDIF
   ENDIF

RETURN ( self )

// End of File: FFile1.prg
METHOD OPEN() CLASS HBMake_FileBase

   Self:nDosHandle := Self:openfile( ::cName, ::nOpenMode )
   ::nEndOfFile    := FSEEK( Self:nDosHandle, 0, 2 )
   FSEEK( Self:nDosHandle, 0, 0 )
   ::nSkipLength := Self:Buffget()
   ::lAtTop      := pTRUE
   ::lAtBottom   := pFALSE
   ::nHan        := Self:nDosHandle
   RETURN ( self )

   /* Method:  gotop()
   Params:  N/A
   Returns: Self
   Purpose: Move the byte pointer to the top of the file
*/
METHOD gotop() CLASS HBMake_FileBase

   IF Self:noDosError() .AND. Self:nDosHandle > 0
      ::fgotop()
      ::nSkipLength := Self:Buffget()
   ENDIF

   RETURN ( self )

   /* Method:  gobottom()
   Params:  N/A
   Returns: Self
   Purpose: Move hte byte pointer of the file to tbe bottom.
*/
METHOD goBottom() CLASS HBMake_FileBase

   LOCAL cBuffer       // as char
   LOCAL lWithCRLF := pFALSE               // as logical

   IF Self:noDosError() .AND. Self:nDosHandle > 0
      ::fgobottom()
      // Now, back off from the end one line length and set the marker
      cBuffer := SPACE( pBUFFER_LENGTH )
      FSEEK( Self:nDosHandle, - ( pBUFFER_LENGTH ), 2 )
      FREAD( Self:nDosHandle, @cBuffer, pBUFFER_LENGTH )
      IF RIGHT( cBuffer, 2 ) == pCRLF   // We need to remove this extra one!
         cBuffer   := LEFT( cBuffer, LEN( cBuffer ) - 2 )
         lWithCRLF := pTRUE
      ENDIF
      cBuffer       := SUBSTR( cBuffer, RAT( pCRLF, cBuffer ) + 2 )
      ::nSkipLength := LEN( cBuffer ) + IF( lWithCRLF, 2, 0 )
      ::nposition   := FSEEK( Self:nDosHandle, - ( LEN( cBuffer ) ), 2 )
      IF lWithCRLF
         ::nposition := FSEEK( Self:nDosHandle, - 2, 1 )
      ENDIF
   ENDIF

   RETURN ( self )

   /* Method:  close()
   Params:  N/A
   Returns: Self
   Purpose: To close the file
*/

/* Should not any more exist here ... Not sure so I just keep it remed */
/* JFL in all case it is not anymore declared from the Class */
/*
METHOD FCLOSE() CLASS HBMake_FileBase

   IF Self:noDosError() .AND. Self:nDosHandle > 0
      FCLOSE( Self:nDosHandle )
      Self:nLastDosMessage := FERROR()
      Self:delItem( Self:nDosHandle )
      Self:lAtTop    := Self:lAtBottom := pFALSE
      Self:nPosition := 0
   ENDIF

   RETURN ( self )
*/
   /* Method:  write(<cChar>)
   Params:  <cChar>
   Returns: Self
   Purpose: To write out to the contents of the file the value in the
            parameter <cChar>.
*/
METHOD WRITE( cChar ) CLASS HBMake_FileBase

   IF Self:noDosError() .AND. Self:nDosHandle > 0
      IF cChar IS pCHARACTER
         IF cChar > Self:nSkipLength    // we are going to truncate for now...
            FWRITE( Self:nDosHandle, cChar, Self:nSkipLength )
         ELSE
            FWRITE( Self:nDosHandle, cChar, LEN( cChar ) )
         ENDIF
         FSEEK( Self:nDosHandle, ::nposition, 0 )
         Self:nLastDosMessage := FERROR()
         IF Self:noDosError()
            FSEEK( Self:nDosHandle, Self:nPosition, 0 )     // Re-position the pointer
         ENDIF
      ENDIF
   ENDIF

   RETURN ( self )

   /* Method:  getBuffer( <lDirection> )
   Params:  <lDirection>    Logical toggle for direction
   Returns: <nBytes>
   Purpose: To return the number of bytes either forward or backward from
            the present file pointer position in which the next CRLF char
            appears.  If <lDirection> is a logical false (.F.) value, them
            the operation will go in reverse order; otherwise, it will go
            in a forward direction.  The default value is a logical true
            (.T.) value.
*/


/* Should not any more exist here ... Not sure so I just keep it remed */
/* JFL in all case it is not anymore declared from the Class */
/*

/* 
METHOD Buffget( lForward ) CLASS HBMake_FileBase

   LOCAL cBuffer       // as char
   LOCAL nLocation     // as int
   LOCAL nRead         // as int
   LOCAL lWithCRLF := pFALSE               // as logical

   DEFAULT lForward TO pTRUE

   IF !lForward

      nRead := FSEEK( Self:nDosHandle, ;
                      - ( IF( ::nposition < pBUFFER_LENGTH, ;
                      ::nposition, ;
                      pBUFFER_LENGTH ) ), ;
                      1 )               // rewind backwards

      cBuffer := SPACE( ::nposition - nRead )
      FREAD( Self:nDosHandle, @cBuffer, ( ::nposition - nRead ) )

      IF RIGHT( cBuffer, 2 ) == pCRLF   // with line already
         cBuffer   := LEFT( cBuffer, LEN( cBuffer ) - 2 )
         lWithCRLF := pTRUE
      ENDIF
      nLocation := LEN( cBuffer ) - ( RAT( pCRLF, cBuffer ) )

   ELSE
      cBuffer := SPACE( pBUFFER_LENGTH )
      nRead   := FREAD( Self:nDosHandle, @cBuffer, pBUFFER_LENGTH )
      FSEEK( Self:nDosHandle, - ( IF( nRead < pBUFFER_LENGTH, nRead, ;
             pBUFFER_LENGTH ) ), 1 )    // Rewind

      // Now, parse the string. and file

      nLocation := AT( pCRLF, cBuffer )

      // Now, if there is NO CRLF in the buffer and if the value of the
      // number of bytes read is less than the buffer length, then we
      // have an end of file condition.
      IF nLocation == 0 .AND. ( nRead < pBUFFER_LENGTH )
         // If so, then set the appropriate flags accordingly.
         ::lAtBottom := pTRUE
         ::lAtTop    := pFALSE
      ENDIF
   ENDIF

   RETURN ( nLocation )
*/
   /* Method:  appendLine( <cLine )
   Params:  <cLine>         Character line to append
   Returns: Self
   Purpose: To append a blank CRLF delimited line at the end of the file.
            If <cLine> is not passed or if it an empty line with 0 bytes
            in length, the function will not operate.
*/
METHOD appendLine( cLine ) CLASS HBMake_FileBase

   DEFAULT cLine TO ""

   IF LEN( cLine ) == 0                 // Valid line
      IF Self:noDosError() .AND. Self:nDosHandle > 0        // No error
         IF !( pCRLF $ cLine )          // No CRLF, so add
            cLIne += pCRLF
         ENDIF
         FSEEK( Self:nDosHandle, 0, 2 )
         FWRITE( Self:nDosHandle, cLine )
         ::nEndOfFile  := FSEEK( Self:nDosHandle, 0, 2 )
         ::nposition   := FSEEK( Self:nDosHandle, - ( LEN( cLine ) ), 2 )
         ::nSkipLength := LEN( cLine )
         ::lAtBottom   := ::lAtTop := pFALSE
      ENDIF
   ENDIF

   RETURN ( self )

   /* Method:  skip( <nRecords> )
   Params:  <nRecords>
   Returns: Self
   Purpose: This method moves the file's byte pointer <nRecords> position
            from the current location.  The actualy movement is determined
            on the value of Self:nSkipLength which holds the skipping base.
            This class's purpose is to do one byte movements.
*/

METHOD SKIP( nRecords ) CLASS HBMake_FileBase

   LOCAL nCount := 0   // as int

   DEFAULT nRecords TO 1

   // Here, we have to start looking for CHR(13)+CHR(10) character
   // combinations.  Once found, then we have to set the super class
   // values appropriately

   IF Self:noDosError() .AND. Self:nDosHandle > 0
      DO CASE
         CASE nRecords > 0              // It's positive movement
            WHILE nCount ++ != nRecords
               ::fskip()
               ::nSkipLength := Self:Buffget()
            ENDDO

         CASE nRecords < 0              // It's negative movement
            WHILE nCount -- != nRecords
               ::nSkipLength := Self:Buffget( pFALSE )
               ::fskip( - 1 )
            ENDDO

      ENDCASE
   ENDIF

   RETURN ( self )

   /* Method:  goto(<nRecord>)
   Params:  <nRecord>       The record byte to move to
   Returns: Self
   Purpose: This method moves the byte marker to the <nRecord> position
            within the file.  It is also based on the value stored to the
            Self:nSkipLength instance variable
*/
METHOD GOTO( nValue ) CLASS HBMake_FileBase

   LOCAL cLine     := ""                   // as char
   LOCAL nCount    := 0                    // as int
   LOCAL lContinue := pTRUE                // as logical
   LOCAL cBuffer       // as char

   DEFAULT nValue TO 0

   IF Self:noDosError() .AND. Self:nDosHandle > 0
      IF nValue IS pNUMERIC
         IF nValue > 0                  // o.k. so far
            FSEEK( Self:nDosHandle, 0, 0 )                  // start at the top
            WHILE lContinue
               cBuffer   := SPACE( pBUFFER_LENGTH )
               lContinue := ( FREAD( Self:nDosHandle, @cBuffer, pBUFFER_LENGTH ) == ;
                              pBUFFER_LENGTH )
               cBuffer := cLine + cBuffer
               WHILE pCRLF $ cBuffer
                  IF ++ nCount == nValue
                     lContinue := pFALSE
                     EXIT
                  ENDIF
                  cBuffer := SUBSTR( cBuffer, AT( pCRLF, cBuffer ) + 2 )
               ENDDO
               cLine := cBuffer
            ENDDO
            IF nCount == nValue         // We have a match
               FSEEK( Self:nDosHandle, - ( pBUFFER_LENGTH ), 1 )                // Back off from here
               ::nposition := FSEEK( Self:nDosHandle, ;
                                     ( pBUFFER_LENGTH - LEN( cBuffer ) ), ;
                                     1 )                    // Move
               ::nSkipLength := Self:Buffget()
            ELSE
               FSEEK( Self:nDosHandle, ::nposition, 0 )
               nCount := 0
            ENDIF
         ENDIF
      ENDIF
   ENDIF

RETURN ( nCount )

// End of File: FFile2.prg

METHOD BufferGet( lForward ) CLASS HBMake_FileBase

   LOCAL cBuffer       // as char
   LOCAL nLocation     // as int
   LOCAL nRead         // as int
   LOCAL lWithCRLF := pFALSE               // as logical

   DEFAULT lForward TO pTRUE

   IF !lForward

      nRead := FSEEK( Self:nDosHandle, ;
                      - ( IF( ::nposition < pBUFFER_LENGTH, ;
                      ::nposition, ;
                      pBUFFER_LENGTH ) ), ;
                      1 )               // rewind backwards

      cBuffer := SPACE( ::nposition - nRead )
      FREAD( Self:nDosHandle, @cBuffer, ( ::nposition - nRead ) )

      IF RIGHT( cBuffer, 2 ) == pCRLF   // with line already
         cBuffer   := LEFT( cBuffer, LEN( cBuffer ) - 2 )
         lWithCRLF := pTRUE
      ENDIF
      nLocation := LEN( cBuffer ) - ( RAT( pCRLF, cBuffer ) )

   ELSE
      cBuffer := SPACE( pBUFFER_LENGTH )
      nRead   := FREAD( Self:nDosHandle, @cBuffer, pBUFFER_LENGTH )
      FSEEK( Self:nDosHandle, - ( IF( nRead < pBUFFER_LENGTH, nRead, ;
             pBUFFER_LENGTH ) ), 1 )    // Rewind

      // Now, parse the string. and file

      nLocation := AT( pCRLF, cBuffer )

      // Now, if there is NO CRLF in the buffer and if the value of the
      // number of bytes read is less than the buffer length, then we
      // have an end of file condition.
      IF nLocation == 0 .AND. ( nRead < pBUFFER_LENGTH )
         // If so, then set the appropriate flags accordingly.
         ::lAtBottom := pTRUE
         ::lAtTop    := pFALSE
      ENDIF
   ENDIF

RETURN ( nLocation )

*+ EOF: FFILE1.PRG
