!
! Binary_File_Utility
!
! Module for utility routines for "Binary" datafiles (unformatted,
! sequential) that conform to the required format.
!
!
! CREATION HISTORY:
!       Written by:     Paul van Delst, 12-Jun-2000
!                       paul.vandelst@noaa.gov
!

MODULE Binary_File_Utility

  ! ------------------
  ! Environment set up
  ! ------------------
  ! Module use
  USE Type_Kinds,      ONLY: Long, n_Bytes_Long
  USE File_Utility,    ONLY: Get_Lun, File_Exists, File_Open
  USE Message_Handler, ONLY: SUCCESS, FAILURE, WARNING, Display_Message
  USE Endian_Utility,  ONLY: Swap_Endian
  ! Disable all implicit typing
  IMPLICIT NONE


  ! ------------
  ! Visibilities
  ! ------------
  PRIVATE
  PUBLIC :: Open_Binary_File
  PUBLIC :: WriteGAtts_Binary_File
  PUBLIC :: ReadGAtts_Binary_File
  PUBLIC :: WriteLogical_Binary_File
  PUBLIC :: ReadLogical_Binary_File


  ! -------------------
  ! Procedure overloads
  ! -------------------
  INTERFACE WriteLogical_Binary_File
    MODULE PROCEDURE WriteLogical_Scalar
    MODULE PROCEDURE WriteLogical_Rank1
  END INTERFACE WriteLogical_Binary_File

  INTERFACE ReadLogical_Binary_File
    MODULE PROCEDURE ReadLogical_Scalar
    MODULE PROCEDURE ReadLogical_Rank1
  END INTERFACE ReadLogical_Binary_File


  ! ----------
  ! Parameters
  ! ----------
  CHARACTER(*), PARAMETER :: MODULE_VERSION_ID = &
  '$Id: Binary_File_Utility.f90 99117 2017-11-27 18:37:14Z tong.zhu@noaa.gov $'
  ! Magic number header value for byte-swap checks
  INTEGER(Long), PARAMETER :: MAGIC_NUMBER = 123456789_Long
  ! Integer "logicals" for I/O
  INTEGER(Long), PARAMETER :: FALSE = 0_Long
  INTEGER(Long), PARAMETER :: TRUE  = 1_Long
  ! String lengths
  INTEGER, PARAMETER :: ML = 256   ! For messages
  INTEGER, PARAMETER :: GL = 5000  ! For local global attribute values
  ! Global attribute names
  CHARACTER(*), PARAMETER :: WRITE_MODULE_GATTNAME = 'write_module'
  CHARACTER(*), PARAMETER :: CREATED_ON_GATTNAME   = 'created_on'
  CHARACTER(*), PARAMETER :: TITLE_GATTNAME        = 'title'
  CHARACTER(*), PARAMETER :: HISTORY_GATTNAME      = 'history'
  CHARACTER(*), PARAMETER :: COMMENT_GATTNAME      = 'comment'
  

CONTAINS


!################################################################################
!################################################################################
!##                                                                            ##
!##                         ## PUBLIC MODULE ROUTINES ##                       ##
!##                                                                            ##
!################################################################################
!################################################################################

!--------------------------------------------------------------------------------
!:sdoc+:
!
! NAME:
!       Open_Binary_File
!
! PURPOSE:
!       Function to open unformatted, sequential access "Binary" files
!
! CALLING SEQUENCE:
!       Error_Status = Open_Binary_File( Filename,                &
!                                        FileID,                  &
!                                        For_Output = For_Output, &
!                                        No_Check   = No_Check    )
!
! INPUTS:
!       Filename:         Name of the Binary file to open.
!                         UNITS:      N/A
!                         TYPE:       CHARACTER(*)
!                         DIMENSION:  Scalar
!                         ATTRIBUTES: INTENT(IN)
!
! OPTIONAL INPUTS:
!       For_Output:       Set this logical argument to open a new file for
!                         writing. Default action is to open an existing file
!                         for read access. Note, if the file already exists and
!                         it is opened with this keyword set, the file is
!                         overwritten.
!                         If == .FALSE., existing file is opened for READ access (DEFAULT)
!                                        ACTION='READ', STATUS='OLD'
!                            == .TRUE. , new file is opened for WRITE access.
!                                        ACTION='WRITE', STATUS='REPLACE'
!                         UNITS:      N/A
!                         TYPE:       LOGICAL
!                         DIMENSION:  Scalar
!                         ATTRIBUTES: INTENT(IN), OPTIONAL
!
!       No_Check:         Set this logical argument to suppress the byte-order
!                         check made on an existing file by NOT reading the file
!                         header magic number.  Default action is to check the
!                         file. This argument is ignored if the FOR_OUTPUT 
!                         optional argument is set.
!                         If == .FALSE., existing file magic number is read and the
!                                        byte order is checked (DEFAULT)
!                            == .TRUE.,  magic number is *NOT* read from file and
!                                        checked for validity.
!                         UNITS:      N/A
!                         TYPE:       LOGICAL
!                         DIMENSION:  Scalar
!                         ATTRIBUTES: INTENT(IN), OPTIONAL
!
! OUTPUTS:
!       FileID:           File unit number.
!                         UNITS:      N/A
!                         TYPE:       INTEGER
!                         DIMENSION:  Scalar
!                         ATTRIBUTES: INTENT(OUT)
!
! FUNCTION RESULT:
!       Error_Status:     The return value is an integer defining the
!                         error status. The error codes are defined in
!                         the Message_Handler module. Values returned by
!                         this function are:
!                           SUCCESS == file open was successful
!                           FAILURE == an unrecoverable error occurred
!                         UNITS:      N/A
!                         TYPE:       INTEGER
!                         DIMENSION:  Scalar
!
!:sdoc-:
!--------------------------------------------------------------------------------

  FUNCTION Open_Binary_File( &
    Filename,   &  ! Input
    FileID,     &  ! Output
    For_Output, &  ! Optional input
    No_Check  ) &  ! Optional input
  RESULT( err_stat )
    ! Arguments
    CHARACTER(*),           INTENT(IN)  :: Filename
    INTEGER,                INTENT(OUT) :: FileID
    LOGICAL,      OPTIONAL, INTENT(IN)  :: For_Output
    LOGICAL,      OPTIONAL, INTENT(IN)  :: No_Check
    ! Function result
    INTEGER :: err_stat
    ! Local parameters
    CHARACTER(*), PARAMETER :: ROUTINE_NAME = 'Open_Binary_File'
    ! Local variables
    CHARACTER(ML) :: msg
    CHARACTER(ML) :: io_msg
    LOGICAL :: file_check
    LOGICAL :: file_input
    INTEGER :: io_stat
    INTEGER(Long) :: magic_number_read
    CHARACTER(7) :: file_status
    CHARACTER(5) :: file_action

    ! Set up
    err_stat = SUCCESS
    ! ...Check the For_Output argument
    file_input = .TRUE.
    IF ( PRESENT(For_Output) ) file_input = .NOT. For_Output
    ! ...Check the No_Check argument
    file_check = file_input
    IF ( PRESENT(No_Check) ) file_check = (.NOT. No_Check) .AND. file_input


    ! Branch depending on type of file I/O
    IF ( file_input ) THEN
      ! Set OPEN keywords for READING
      file_status = 'OLD'
      file_action = 'READ'
    ELSE
      ! Set OPEN keywords for WRITING
      file_status = 'REPLACE'
      file_action = 'WRITE'
    END IF


    ! Check the file byte order
    IF ( file_check ) THEN
      err_stat = Check_Binary_File( Filename )
      IF ( err_stat /= SUCCESS ) THEN
        msg = 'Error checking '//TRIM(Filename)//' file byte order'
        CALL CleanUp(); RETURN
      END IF
    END IF


    ! Get a free unit number
    FileID = Get_Lun()
    IF ( FileID < 0 ) THEN
      msg = 'Error obtaining file unit number for '//TRIM(Filename)
      CALL CleanUp(); RETURN
    END IF


    ! Open the file
    OPEN( FileID, FILE   = Filename     , &
                  STATUS = file_status  , &
                  ACTION = file_action  , &
                  ACCESS = 'SEQUENTIAL' , &
                  FORM   = 'UNFORMATTED', &
                  IOSTAT = io_stat      , &
                  IOMSG  = io_msg         )
    IF ( io_stat /= 0 ) THEN
      msg = 'Error opening '//TRIM(Filename)//' - '//TRIM(io_msg)
      CALL CleanUp(); RETURN
    END IF


    ! Skip past, or write the magic number
    IF ( File_Input ) THEN
      READ( FileID, IOSTAT=io_stat, IOMSG=io_msg ) magic_number_read
      IF ( io_stat /= 0 ) THEN
        msg = 'Error reading magic number from '//TRIM(Filename)//' - '//TRIM(io_msg)
        CALL CleanUp(); RETURN
      END IF
    ELSE
      WRITE( FileID, IOSTAT=io_stat, IOMSG=io_msg ) MAGIC_NUMBER
      IF ( io_stat /= 0 ) THEN
        msg = 'Error writing magic number to '//TRIM(Filename)//' - '//TRIM(io_msg)
        CALL CleanUp(); RETURN
      END IF
    END IF
    
  CONTAINS
   
     SUBROUTINE CleanUp()
       IF ( File_Open(Filename) ) THEN
         CLOSE( FileID, IOSTAT=io_stat, IOMSG=io_msg )
         IF ( io_stat /= 0 ) &
           msg = TRIM(msg)//'; Error closing file during error cleanup - '//TRIM(io_msg)
       END IF
       err_stat = FAILURE
       CALL Display_Message( ROUTINE_NAME, msg, err_stat )
     END SUBROUTINE CleanUp

  END FUNCTION Open_Binary_File





  ! Function to write standard global attributes to a Binary file.

  FUNCTION WriteGAtts_Binary_File( &
    fid         , &  ! Input
    Write_Module, &  ! Optional input
    Created_On  , &  ! Optional input
    Title       , &  ! Optional input
    History     , &  ! Optional input
    Comment     ) &  ! Optional input
  RESULT( err_stat )
    ! Arguments
    INTEGER     ,           INTENT(IN) :: fid
    CHARACTER(*), OPTIONAL, INTENT(IN) :: Write_Module
    CHARACTER(*), OPTIONAL, INTENT(IN) :: Created_On  
    CHARACTER(*), OPTIONAL, INTENT(IN) :: Title
    CHARACTER(*), OPTIONAL, INTENT(IN) :: History
    CHARACTER(*), OPTIONAL, INTENT(IN) :: Comment
    ! Function result
    INTEGER :: err_stat
    ! Local parameters
    CHARACTER(*), PARAMETER :: ROUTINE_NAME = 'WriteGAtts_Binary_File'
    ! Local variables
    CHARACTER(ML) :: msg
    CHARACTER(ML) :: io_msg
    CHARACTER(8)  :: cdate
    CHARACTER(10) :: ctime
    CHARACTER(5)  :: czone
    INTEGER :: io_stat

    ! Set up
    err_stat = SUCCESS
    msg = ''

    ! Software ID
    CALL WriteSingleGAtt( WRITE_MODULE_GATTNAME, gattvalue = Write_Module )
    IF ( err_stat /= SUCCESS ) RETURN
    
    ! Creation date/time
    CALL DATE_AND_TIME( cdate, ctime, czone )
    IF ( PRESENT(Created_On) ) THEN
      CALL WriteSingleGAtt( CREATED_ON_GATTNAME, gattvalue = Created_On )
    ELSE
      CALL WriteSingleGAtt( CREATED_ON_GATTNAME, &
                            gattvalue = &
                              cdate(1:4)//'/'//cdate(5:6)//'/'//cdate(7:8)//', '// &
                              ctime(1:2)//':'//ctime(3:4)//':'//ctime(5:6)//' '// &
                              czone//'UTC')
    END IF
    IF ( err_stat /= SUCCESS ) RETURN


    ! The title
    CALL WriteSingleGAtt( TITLE_GATTNAME, gattvalue = Title )
    IF ( err_stat /= SUCCESS ) RETURN


    ! The history
    CALL WriteSingleGAtt( HISTORY_GATTNAME, gattvalue = History )
    IF ( err_stat /= SUCCESS ) RETURN


    ! The comment
    CALL WriteSingleGAtt( COMMENT_GATTNAME, gattvalue = Comment )
    IF ( err_stat /= SUCCESS ) RETURN
    
  CONTAINS
  
    SUBROUTINE WriteSingleGAtt(gattname, gattvalue)
      CHARACTER(*),           INTENT(IN) :: gattname
      CHARACTER(*), OPTIONAL, INTENT(IN) :: gattvalue
      INTEGER :: gattlen
      CHARACTER(GL) :: l_gattvalue
      ! Setup
      l_gattvalue = ''
      IF ( PRESENT(gattvalue) ) THEN
        IF ( LEN_TRIM(gattvalue) /= 0 ) l_gattvalue = TRIM(gattname)//': '//TRIM(gattvalue)
      END IF
      gattlen = LEN_TRIM(l_gattvalue)
      ! Write the string length
      WRITE( fid, IOSTAT=io_stat, IOMSG=io_msg ) gattlen
      IF ( io_stat /= 0 ) THEN
        msg = 'Error writing '//TRIM(gattname)//' attribute length - '//TRIM(io_msg)
        CALL WriteGatts_Cleanup(); RETURN
      END IF
      IF ( gattlen == 0 ) RETURN
      ! Write the attribute
      WRITE( fid, IOSTAT=io_stat, IOMSG=io_msg ) TRIM(l_gattvalue)
      IF ( io_stat /= 0 ) THEN
        msg = 'Error writing '//TRIM(gattname)//' attribute - '//TRIM(io_msg)
        CALL WriteGatts_Cleanup(); RETURN
      END IF
    END SUBROUTINE WriteSingleGAtt
     
    SUBROUTINE WriteGAtts_Cleanup()
      IF ( File_Open(fid) ) THEN
        CLOSE( fid, IOSTAT=io_stat, IOMSG=io_msg )
        IF ( io_stat /= 0 ) &
          msg = TRIM(msg)//'; Error closing output file during error cleanup - '//TRIM(io_msg)
      END IF
      err_stat = FAILURE
      CALL Display_Message( ROUTINE_NAME, msg, err_stat )
    END SUBROUTINE WriteGAtts_Cleanup
    
  END FUNCTION WriteGAtts_Binary_File



  ! Function to read standard global attributes from a Binary file.

  FUNCTION ReadGAtts_Binary_File( &
    fid         , &  ! Input
    Write_Module, &  ! Optional output
    Created_On  , &  ! Optional output
    Title       , &  ! Optional output
    History     , &  ! Optional output
    Comment     ) &  ! Optional output
  RESULT( err_stat )
    ! Arguments
    INTEGER     ,           INTENT(IN)  :: fid
    CHARACTER(*), OPTIONAL, INTENT(OUT) :: Write_Module
    CHARACTER(*), OPTIONAL, INTENT(OUT) :: Created_On  
    CHARACTER(*), OPTIONAL, INTENT(OUT) :: Title
    CHARACTER(*), OPTIONAL, INTENT(OUT) :: History
    CHARACTER(*), OPTIONAL, INTENT(OUT) :: Comment
    ! Function result
    INTEGER :: err_stat
    ! Local parameters
    CHARACTER(*), PARAMETER :: ROUTINE_NAME = 'ReadGAtts_Binary_File'
    ! Local variables
    CHARACTER(ML) :: msg
    CHARACTER(ML) :: io_msg
    INTEGER :: io_stat

    ! Set up
    err_stat = SUCCESS
    msg = ''

    ! 
    ! Software ID
    CALL ReadSingleGAtt( WRITE_MODULE_GATTNAME, gattvalue = Write_Module )
    IF ( err_stat /= SUCCESS ) RETURN
    
    ! Creation date/time
    CALL ReadSingleGAtt( CREATED_ON_GATTNAME, gattvalue = Created_On )
    IF ( err_stat /= SUCCESS ) RETURN


    ! The title
    CALL ReadSingleGAtt( TITLE_GATTNAME, gattvalue = Title )
    IF ( err_stat /= SUCCESS ) RETURN


    ! The history
    CALL ReadSingleGAtt( HISTORY_GATTNAME, gattvalue = History )
    IF ( err_stat /= SUCCESS ) RETURN


    ! The comment
    CALL ReadSingleGAtt( COMMENT_GATTNAME, gattvalue = Comment )
    IF ( err_stat /= SUCCESS ) RETURN
    
  CONTAINS
  
    SUBROUTINE ReadSingleGAtt( gattname, gattvalue)
      CHARACTER(*),           INTENT(IN)  :: gattname
      CHARACTER(*), OPTIONAL, INTENT(OUT) :: gattvalue
      INTEGER :: i, gattlen
      CHARACTER(GL) :: l_gattvalue
      ! Setup
      IF ( PRESENT(gattvalue) ) gattvalue = ''
      l_gattvalue = ''
      ! Read the string length
      READ( fid, IOSTAT=io_stat, IOMSG=io_msg ) gattlen
      IF ( io_stat /= 0 ) THEN
        msg = 'Error reading '//TRIM(gattname)//' attribute length - '//TRIM(io_msg)
        CALL ReadGatts_Cleanup(); RETURN
      END IF
      IF ( gattlen == 0 ) RETURN
      ! Read the attribute
      READ( fid, IOSTAT=io_stat, IOMSG=io_msg ) l_gattvalue(1:gattlen)
      IF ( io_stat /= 0 ) THEN
        msg = 'Error reading '//TRIM(gattname)//' attribute - '//TRIM(io_msg)
        CALL ReadGatts_Cleanup(); RETURN
      END IF
      ! Strip out the attribute name
      IF ( PRESENT(gattvalue) ) THEN
        i = INDEX(l_gattvalue,': ')
        gattvalue = l_gattvalue(i+2:gattlen)
      END IF    
    END SUBROUTINE ReadSingleGAtt
     
    SUBROUTINE ReadGAtts_Cleanup()
      IF ( File_Open(fid) ) THEN
        CLOSE( fid, IOSTAT=io_stat, IOMSG=io_msg )
        IF ( io_stat /= 0 ) &
          msg = TRIM(msg)//'; Error closing input file during error cleanup - '//TRIM(io_msg)
      END IF
      err_stat = FAILURE
      CALL Display_Message( ROUTINE_NAME, msg, err_stat )
    END SUBROUTINE ReadGAtts_Cleanup
    
  END FUNCTION ReadGAtts_Binary_File




  !
  ! NAME:
  !   ReadLogical_Binary_File
  !
  ! PURPOSE:
  !   Utility function to read an integer "logical" value from file
  !

  FUNCTION ReadLogical_Scalar( &
    fid, &
    logical_value ) &
  RESULT( err_stat )
    ! Arguments
    INTEGER, INTENT(IN)  :: fid
    LOGICAL, INTENT(OUT) :: logical_value
    ! Function result
    INTEGER :: err_stat
    ! Function parameters
    CHARACTER(*), PARAMETER :: ROUTINE_NAME = 'ReadLogical_Binary_File(Scalar)'
    ! Function variables
    CHARACTER(ML) :: msg
    CHARACTER(ML) :: io_msg
    INTEGER :: io_stat
    INTEGER(Long) :: logical_integer

    ! Setup
    err_stat = SUCCESS

    ! Read the integer
    READ( fid,IOSTAT=io_stat,IOMSG=io_msg ) logical_integer
    IF ( io_stat /= 0 ) THEN
      err_stat = FAILURE
      msg = 'Error reading logical integer value - '//TRIM(io_msg)
      CALL Display_Message( ROUTINE_NAME, msg, err_stat )
      RETURN
    END IF

    ! Convert integer to a logical value
    logical_value = (logical_integer == TRUE)

  END FUNCTION ReadLogical_Scalar

  FUNCTION ReadLogical_Rank1( &
    fid, &
    logical_value ) &
  RESULT( err_stat )
    ! Arguments
    INTEGER, INTENT(IN)  :: fid
    LOGICAL, INTENT(OUT) :: logical_value(:)
    ! Function result
    INTEGER :: err_stat
    ! Function parameters
    CHARACTER(*), PARAMETER :: ROUTINE_NAME = 'ReadLogical_Binary_File(Rank-1)'
    ! Function variables
    CHARACTER(ML) :: msg
    CHARACTER(ML) :: io_msg
    INTEGER :: io_stat
    INTEGER(Long) :: logical_integer(SIZE(logical_value)) 

    ! Setup
    err_stat = SUCCESS

    ! Read the integer
    READ( fid,IOSTAT=io_stat,IOMSG=io_msg ) logical_integer
    IF ( io_stat /= 0 ) THEN
      err_stat = FAILURE
      msg = 'Error reading logical integer rank-1 array - '//TRIM(io_msg)
      CALL Display_Message( ROUTINE_NAME, msg, err_stat )
      RETURN
    END IF

    ! Convert integer to a logical value
    logical_value = (logical_integer == TRUE)

  END FUNCTION ReadLogical_Rank1


  !
  ! NAME:
  !   WriteLogical_Binary_File
  !
  ! PURPOSE:
  !   Utility function to write an integer "logical" value to file
  !

  FUNCTION WriteLogical_Scalar( &
    fid, &
    logical_value ) &
  RESULT( err_stat )
    ! Arguments
    INTEGER, INTENT(IN) :: fid
    LOGICAL, INTENT(IN) :: logical_value
    ! Function result
    INTEGER :: err_stat
    ! Function parameters
    CHARACTER(*), PARAMETER :: ROUTINE_NAME = 'WriteLogical_Binary_File(Scalar)'
    ! Function variables
    CHARACTER(ML) :: msg
    CHARACTER(ML) :: io_msg
    INTEGER :: io_stat
    INTEGER(Long) :: logical_integer

    ! Setup
    err_stat = SUCCESS


    ! Convert the logical to an integer value
    IF ( logical_value ) THEN
      logical_integer = TRUE
    ELSE
      logical_integer = FALSE
    END IF


    ! Write the integer
    WRITE( fid,IOSTAT=io_stat,IOMSG=io_msg ) logical_integer
    IF ( io_stat /= 0 ) THEN
      err_stat = FAILURE
      msg = 'Error writing logical integer - '//TRIM(io_msg)
      CALL Display_Message( ROUTINE_NAME, msg, err_stat )
      RETURN
    END IF

  END FUNCTION WriteLogical_Scalar

  FUNCTION WriteLogical_Rank1( &
    fid, &
    logical_value ) &
  RESULT( err_stat )
    ! Arguments
    INTEGER, INTENT(IN) :: fid
    LOGICAL, INTENT(IN) :: logical_value(:)
    ! Function result
    INTEGER :: err_stat
    ! Function parameters
    CHARACTER(*), PARAMETER :: ROUTINE_NAME = 'WriteLogical_Binary_File(Rank-1)'
    ! Function variables
    CHARACTER(ML) :: msg
    CHARACTER(ML) :: io_msg
    INTEGER :: io_stat
    INTEGER(Long) :: logical_integer(SIZE(logical_value)) 

    ! Setup
    err_stat = SUCCESS


    ! Convert the logical to an integer value
    WHERE ( logical_value )
      logical_integer = TRUE
    ELSEWHERE
      logical_integer = FALSE
    END WHERE


    ! Write the integer
    WRITE( fid,IOSTAT=io_stat,IOMSG=io_msg ) logical_integer
    IF ( io_stat /= 0 ) THEN
      err_stat = FAILURE
      msg = 'Error writing logical integer rank-1 array - '//TRIM(io_msg)
      CALL Display_Message( ROUTINE_NAME, msg, err_stat )
      RETURN
    END IF

  END FUNCTION WriteLogical_Rank1



!################################################################################
!################################################################################
!##                                                                            ##
!##                        ## PRIVATE MODULE ROUTINES ##                       ##
!##                                                                            ##
!################################################################################
!################################################################################

!--------------------------------------------------------------------------------
!
! NAME:
!       Check_Binary_File
!
! PURPOSE:
!       Function to determine if the unformatted Binary file is in the correct
!       byte order.
!
! CALLING SEQUENCE:
!       Error_Status = Check_Binary_File( Filename )
!
! INPUTS:
!       Filename:         Name of the Binary file to check.
!                         UNITS:      N/A
!                         TYPE:       CHARACTER(*)
!                         DIMENSION:  Scalar
!                         ATTRIBUTES: INTENT(IN)
!
! OPTIONAL INPUTS:
!       Message_Log:      Character string specifying a filename in which any
!                         Messages will be logged. If not specified, or if an
!                         error occurs opening the log file, the default action
!                         is to output Messages to the screen.
!                         UNITS:      N/A
!                         TYPE:       CHARACTER(*)
!                         DIMENSION:  Scalar
!                         ATTRIBUTES: INTENT(IN), OPTIONAL
!
! FUNCTION RESULT:
!       Error_Status:     The return value is an integer defining the
!                         error status. The error codes are defined in
!                         the Message_Handler module. Values returned by
!                         this function are:
!                           SUCCESS == file check was successful
!                           FAILURE == - error occurred reading a file record,
!                                      - 8- and/or 32-bit integers not supported.
!                         UNITS:      N/A
!                         TYPE:       INTEGER
!                         DIMENSION:  Scalar
!
!--------------------------------------------------------------------------------

  FUNCTION Check_Binary_File( Filename ) RESULT( err_stat )
    ! Arguments
    CHARACTER(*), INTENT(IN) :: Filename
    ! Function result
    INTEGER :: err_stat
    ! Local parameters
    CHARACTER(*),  PARAMETER :: ROUTINE_NAME = 'Check_Binary_File'
    ! Local variables
    CHARACTER(ML) :: msg
    CHARACTER(ML) :: io_msg
    INTEGER :: fid
    INTEGER :: io_stat
    INTEGER(Long) :: magic_number_read
    INTEGER(Long) :: magic_number_swapped

    ! Set up
    err_stat = SUCCESS


    ! Check that 4-byte integers are supported
    IF ( BIT_SIZE( 1_Long ) /= 32 ) THEN
      msg = '32-bit integers not supported. Unable to determine endian-ness'
      CALL CleanUp(); RETURN
    END IF


    ! Get a free unit number
    fid = Get_Lun()
    IF ( fid < 0 ) THEN
      msg = 'Error obtaining file unit number for '//TRIM(Filename)
      CALL CleanUp(); RETURN
    END IF


    ! Open the file as direct access
    OPEN( fid, FILE   = Filename     , &
               STATUS = 'OLD'        , &
               ACTION = 'READ'       , &
               ACCESS = 'DIRECT'     , &
               FORM   = 'UNFORMATTED', &
               RECL   = n_Bytes_Long , &
               IOSTAT = io_stat      , &
               IOMSG  = io_msg         )
    IF ( io_stat /= 0 ) THEN
      msg = 'Error opening '//TRIM(Filename)//' - '//TRIM(io_msg)
      CALL CleanUp(); RETURN
    END IF


    ! Read the magic number
    READ( fid, REC=2, IOSTAT=io_stat, IOMSG=io_msg ) magic_number_read
    IF ( io_stat /= 0 ) THEN
      msg = 'Error reading file magic number - '//TRIM(io_msg)
      CALL CleanUp(); RETURN
    END IF


    ! Close the file
    CLOSE( fid )


    ! Compare the magic numbers
    IF ( magic_number_read /= MAGIC_NUMBER ) THEN

      ! Byte swap the magic number
      magic_number_swapped = Swap_Endian( magic_number_read )
      IF ( magic_number_swapped /= MAGIC_NUMBER ) THEN
        msg = 'Unrecognised file format. Invalid magic number.'
        CALL CleanUp(); RETURN
      END IF

      ! If we get here then the data does need to be byte-swapped
      msg = 'Data file needs to be byte-swapped.'
      CALL CleanUp(); RETURN

    END IF
    
  CONTAINS
   
     SUBROUTINE CleanUp()
       IF ( File_Open(Filename) ) THEN
         CLOSE( fid, IOSTAT=io_stat, IOMSG=io_msg )
         IF ( io_stat /= 0 ) &
           msg = TRIM(msg)//'; Error closing file during error cleanup - '//TRIM(io_msg)
       END IF
       err_stat = FAILURE
       CALL Display_Message( ROUTINE_NAME, msg, err_stat )
     END SUBROUTINE CleanUp

  END FUNCTION Check_Binary_File

END MODULE Binary_File_Utility