//------------------------------------------------------------------------
//                             FAT16 Library
//
// Author: Mattias Hansson
// Copyright (c) : 2005 Mattias Hansson
// License: GNU LGPL 2
// Language: 65CM v0.4+
// Dependencies: mmc64lib, multdivlib
// ZP usage: $14-15, $3f-$41, $4b-$4c, $49-$4a
// Target: Commodore 64 with MMC64 interface
//         (possible future conversions for other platforms possible)
//
// External functions:
//   lib_fat16_mount_partition
//   lib_fat16_dirstart
//   lib_fat16_dirnext
//   lib_fat16_chdir
//   lib_fat16_fopen
//   lib_fat16_fblockread
//   lib_fat16_fblockwrite
//   lib_fat16_fclose
//   lib_fat16_seek        //future expansion
//   lib_fat16_fgetc       //future expansion
//   lib_fat16_fputc       //future expansion
//
//
// Internal function:
//   lib_fat16_calc_total_clusters
//   lib_fat16_int_read_cluster_sector
//   lib_fat16_int_write_cluster_sector
//   lib_fat16_int_get_cluster_link
//   lib_fat16_int_set_cluster_link (rw-support)
//   lib_fat16_int_search_free_clusters (rw-support)
//   lib_fat16_int_get_free_cluster (rw_support)
//   lib_fat16_int_add_dir_entry (rw-support)
//   TODO: lib_fat16_int_update_dir_entry (rw-support) //internal in fclose?
//
// External define support:
//   __LIB_FAT16_MAX_OPEN_FILES - define this to the # of open files you
//                                need in your project (default=5)
//                                If you #DEFINE this in your own code
//                                before #INCLUDE:ing the lib, the lib
//                                will "obey" your setting.
//
//  OBS: use __LIB_MMC64_READONLY instead of mocking with any R/W
//       settings in this lib. This lib is configured to obey that
//       directive also.
//
//------------------------------------------------------------------------

#IFNDEF __LIB_FAT16
#DEFINE __LIB_FAT16 = 1

//If you want to change this -> See above in the header.
#IFNDEF __LIB_FAT16_MAX_OPEN_FILES
  #DEFINE __LIB_FAT16_MAX_OPEN_FILES = 5
#IFEND

//This is the total size of a file descriptor record
//OBS! Do NOT (re)define this one from outside!!!
//NOTE: If lib is compiled for R/W this is redefined
//below.
#DEFINE __LIB_FAT16_FILE_DESCRIPTOR_SIZE = 14

#INCLUDE <multdivlib.c65>
#INCLUDE <fat16/mmc64lib.c65>

//We inherit the "write support" ([Y]/n) from settings in mmc64rwlib.c65
#IFDEF __LIB_MMC64_WRITESUPPORT
  #PRINT Write access in fat16lib.c65 is experimental!!!
  #DEFINE __LIB_FAT16_WRITESUPPORT = 1
  #UNDEF __LIB_FAT16_FILE_DESCRIPTOR_SIZE
  //note: see description of descriptor it below
  #DEFINE __LIB_FAT16_FILE_DESCRIPTOR_SIZE = 22
#IFEND

#INCLUDE <fat16/fat16lowlib.c65>

GOTO lib_fat16_skip

//Precalculated sector locations
WORD lib_fat16_partition_bootsector_lo
BYTE lib_fat16_partition_bootsector_hi

WORD lib_fat16_fat1_sector_lo
BYTE lib_fat16_fat1_sector_hi

WORD lib_fat16_rootdir_sector_lo
BYTE lib_fat16_rootdir_sector_hi

WORD lib_fat16_data_sector_lo
BYTE lib_fat16_data_sector_hi

//dir access variables
WORD lib_fat16_active_dir_startcluster  // Important if a subdir spans over several clusters. special: $0000 = root directory
WORD lib_fat16_active_dir_cluster       // special: $0000 = root directory
WORD lib_fat16_active_dir_sector        // Special if active_dir_cluster = $0000 = "root dir" this may be more than
                                        // "lib_fat16_sectors_per_cluster-1". Thats the reason that this is of type WORD.
WORD lib_fat16_active_dir_entry_offset  // address to the current directory-entry we shall examine in bytes. (inside the fat-buffer)
                                        // Starts @ lib_fat16_buffer_ptr and in incremented with 32 per entry
BYTE lib_fat16_active_dir_bitmask       // The bitmask, sent in when calling "lib_fat16_dirstart", used in "lib_fat16_dirnext"

//------------------------------------------------------------------------
//                     File descriptor table
//
// An array of records representing closed or open files.
//
// Purpose: Having a file open and currently in a read or write process
// requires the lib to hold some info about the location in the file
// etc. If we want several files open at once (and heck yes we want it
// since this is a freakin mass-storage device for our beloved C64) we
// must multiply the descriptor records for as many files we want open
// simultaniously.
//
// NOTE: To change how many files we can have open -> change the constant
// "lib_fat16_max_open_files" under the constant declaration section.
//
// Description of a record:
//
// offset    size     field_description
// --------------------------------------
//      0       2     Current cluster ($0002-$ffef) special $0000 - file not open
//      2       1     Current sector offset in the cluster (0..127)
//      3       2     Current byte position in current sector (0..511)
//      5       4     File total size in bytes (0..2147483647)
//      9       4     Current position in file, counted in bytes (0..2147483647)
//     13       1     One of the "lib_fat16_filemode_*" constants
//
// The rest of the descriptor below is only avaliable if the lib is compiled
// with R/W access enabled.
//
//     14       2     Direntry cluster          (writemode only) //($0000 = rootdir)
//     16       2     Direntry directory sector (writemode only) //used as fclose() must
//     18       2     Direntry offset in sector (writemode only) //update the entry at close
//     20       2     First cluster in file     (writemode only) //To be written to fileentry at fclose()
//
//  Total sizeof(descriptor) = 22 bytes (14 if READONLY)
//------------------------------------------------------------------------
//                   File descriptor access constants
//------------------------------------------------------------------------
BYTE CONST FAT16_DESC_CURR_CLUSTER  = 0
BYTE CONST FAT16_DESC_CURR_SECTOR   = 2
BYTE CONST FAT16_DESC_CURR_POS      = 3
BYTE CONST FAT16_DESC_FILESIZE      = 5
BYTE CONST FAT16_DESC_FILEPOS       = 9
BYTE CONST FAT16_DESC_FILEMODE      = 13
#IFDEF __LIB_FAT16_WRITESUPPORT
BYTE CONST FAT16_DESC_DIRE_CLUSTER  = 14
BYTE CONST FAT16_DESC_DIRE_SECTOR   = 16
BYTE CONST FAT16_DESC_DIRE_OFFSET   = 18
BYTE CONST FAT16_DESC_FIRST_CLUSTER = 20
#IFEND


BYTE lib_fat16_file_descriptor_size @ __LIB_FAT16_FILE_DESCRIPTOR_SIZE
ASM
lib_fat16_file_descriptors
  !fill (lib_fat16_max_open_files*lib_fat16_file_descriptor_size), $ff
ENDASM


//------------------------------------------------------------------------
//lib_fat16_calc_total_clusters (INTERNAL)
//
// purpose: To calculate the number of the last cluster in a filesystem
//
// Method:
//   ( Total sectors - ( data-area-start - start of partition) ) / clusters_per_sector = clusters_of_data_area

#IFDEF __LIB_FAT16_WRITESUPPORT
FUNC lib_fat16_calc_total_clusters
  WORD dataarea_offset_lo
  BYTE dataarea_offset_hi
  //these two represent the dataarea size in sectors.
  WORD dataarea_size_lo
  WORD dataarea_size_hi
  BYTE sectors_per_cluster

  LET sectors_per_cluster = lib_fat16_sectors_per_cluster
  //data-area-start - start of partition
  ASM
    sec
    lda lib_fat16_data_sector_lo
    sbc lib_fat16_partition_bootsector_lo
    sta |dataarea_offset_lo|
    lda lib_fat16_data_sector_lo+1
    sbc lib_fat16_partition_bootsector_lo+1
    sta |dataarea_offset_lo|+1
    lda lib_fat16_data_sector_hi
    sbc lib_fat16_partition_bootsector_hi
    sta |dataarea_offset_hi|
  ENDASM
  //Total sectors - "offset of start of dataarea in partition"
  ASM
    sec
    lda lib_fat16_total_sectors_lo
    sbc |dataarea_offset_lo|
    sta |dataarea_size_lo|
    lda lib_fat16_total_sectors_lo+1
    sbc |dataarea_offset_lo|+1
    sta |dataarea_size_lo|+1
    lda lib_fat16_total_sectors_hi
    sbc |dataarea_offset_hi|
    sta |dataarea_size_hi|
    lda lib_fat16_total_sectors_hi+1
    sbc #0
    sta |dataarea_size_hi|+1
  ENDASM
  //sectors_of_data_area / sectors_per_cluster = total_clusters
  //First bit unimportant (1 sector per cluster and we're done here!)
  ASM
    lsr |sectors_per_cluster|
  ENDASM
  WHILE sectors_per_cluster
    ASM
      clc
      ror |dataarea_size_hi|+1
      ror |dataarea_size_hi|
      ror |dataarea_size_lo|+1
      ror |dataarea_size_lo|

      lsr |sectors_per_cluster|
    ENDASM
  WEND

  LET lib_fat16_total_clusters = dataarea_size_lo

  //CALL lib_dbg_hexoutw ( lib_fat16_total_clusters )
  //CALL lib_dbg_textoutlf ( " total clusters" )

FEND
#IFEND

//------------------------------------------------------------------------
// lib_fat16_mount_partition
//
// Purpose:
//   1. To mount one of the 4 possible partitions on a MMC or SD card.
//   2. Setup all the needed FAT pointers for work.
//   3. Active directory = root-dir of the disk.
//
// Parameters:
//   lib_fat16_mp_partnr = partition nr to mount 0..3
//   lib_fat16_mp_status = returns 0 if success otherwise !0
//
// Preparation: lib_mmc64_cardinit (inherited from our usage of
//              lib_mmc64_readsector)
//
// Note: if the SD/MMC is superfloppy formatted "partition nr" is ignored
//
// Usage of lib temp variables:
//   lib_fat16_tempw
//   lib_fat16_tempw2
//   lib_fat16_tempb
//------------------------------------------------------------------------

BYTE lib_fat16_mp_partnr
BYTE lib_fat16_mp_status
FUNC lib_fat16_mount_partition ( lib_fat16_mp_partnr out:lib_fat16_mp_status )

  BYTE callstat //internal status communication with lower level routines


  LET lib_fat16_mp_status = 1 //assume a problem

  //Note to developers: comment line below if using a constant buffer reference
  POINTER lib_fat16_buffer_ptr TO lib_fat16_fatbuffer
  //!!!!DEBUG CODE!!!!: To easily look at the fat-buffer while debugging we can place it where we like it here.
  //LET lib_fat16_buffer_ptr = $c000 //comment this line when finished debugging

  //Two formats exist:
  //  1. Removable Harddrive mode (sector0 = mbr with partition table)
  //  2. Superfloppy mode USB-ZIP (sector0 = partition boot record   )
  //We must determine what this (likely) is.
  CALL lib_mmc64_readsector ( 0 0 lib_fat16_buffer_ptr callstat )
  IF 0 != callstat
    SUBEND  //error! Read of MBR failed.
  ENDIF

  //First we assume that this is a "Removable Harddrive".

  //Calculate the address (in the buffer) to the partition record caller selected
  ADD $1be + lib_fat16_buffer_ptr -> lib_fat16_tempw
  WHILE lib_fat16_mp_partnr
    ADD 16 + lib_fat16_tempw -> lib_fat16_tempw
    DEC lib_fat16_mp_partnr
  WEND

  //Is the type of the partition = FAT16 = $06 ?
  PEEK lib_fat16_tempw[4] -> lib_fat16_tempb
  IF lib_fat16_tempb == $06 //Yes
    //Now we extract the partition bootsector start and store it for
		//future usage. (needed when calculating the rest of the data areas)
		GETASWORD@ lib_fat16_tempw[8] -> lib_fat16_partition_bootsector_lo
		PEEK lib_fat16_tempw[10] -> lib_fat16_partition_bootsector_hi

		//Read the boot record of the FAT16 partition
		CALL lib_mmc64_readsector ( lib_fat16_partition_bootsector_lo lib_fat16_partition_bootsector_hi lib_fat16_buffer_ptr callstat )
		IF 0 != callstat
			SUBEND //error! Read of FAT16 partition boot record failed!
		ENDIF
  ELSE //No. Is this a "SuperFloppy"? (we take a wild guess)
    //So we set the partition boot sector to 0
    LET lib_fat16_partition_bootsector_lo = 0
    LET lib_fat16_partition_bootsector_hi = 0
  ENDIF

  //MHTAG 20051205
  //CALL lib_dbg_hexoutb ( lib_fat16_partition_bootsector_hi )
  //CALL lib_dbg_hexoutw ( lib_fat16_partition_bootsector_lo )
  //CALL lib_dbg_textoutlf ( " start of partition" )


  //Get "bytes per sector". This is a sanity check for if this might be a FAT16 PBR
  //The code highly depends on $200 bytes per sector so it's good having this covered.

  LET lib_fat16_tempw = lib_fat16_buffer_ptr
  GETASWORD@ lib_fat16_tempw[11] -> lib_fat16_tempw2
  IF lib_fat16_tempw2 != LIB_FAT16_SECTOR_SIZE
    SUBEND //error! "bytes per sector" is not $200 (512) (or this is not a FAT16 pbr!)
  ENDIF

  //Get "Sectors per cluster"
  PEEK lib_fat16_tempw[13] -> lib_fat16_sectors_per_cluster

  //Get "Reserved sectors" (including FAT boot sector)
  GETASWORD@ lib_fat16_tempw[14] -> lib_fat16_reserved_sectors
  //Get "nr of FATs"
  PEEK lib_fat16_tempw[16] -> lib_fat16_nr_of_fats
  //Get "max nr of rootdir entries"
  GETASWORD@ lib_fat16_tempw[17] -> lib_fat16_rootdir_entries
  //Get "sectors per fat"
  GETASWORD@ lib_fat16_tempw[22] -> lib_fat16_sectors_per_fat


#IFDEF __LIB_FAT16_WRITESUPPORT

  //MHTAG 20051205
  GETASWORD@ lib_fat16_tempw[34] -> lib_fat16_total_sectors_hi
  //CALL lib_dbg_hexoutw ( lib_fat16_total_sectors_hi )
  GETASWORD@ lib_fat16_tempw[32] -> lib_fat16_total_sectors_lo
  //CALL lib_dbg_hexoutw ( lib_fat16_total_sectors_lo )
  //CALL lib_dbg_textoutlf ( " total sectors" )

#IFEND


  //Now we'll do some precalculations based on the values we've read
  //so that we know the start sectors of all areas of the partition.

//known
//WORD lib_fat16_partition_bootsector_lo
//BYTE lib_fat16_partition_bootsector_hi

  //First FAT starts after the reserved sectors
  // this below means fat1_sector = partition_start + reserved_sectors
  // in assembler due to this beeing a 24-bit number
  ASM
    clc
    lda lib_fat16_reserved_sectors
    adc lib_fat16_partition_bootsector_lo
    sta lib_fat16_fat1_sector_lo
    lda lib_fat16_reserved_sectors+1
    adc lib_fat16_partition_bootsector_lo+1
    sta lib_fat16_fat1_sector_lo+1
    lda #0
    adc lib_fat16_partition_bootsector_hi
    sta lib_fat16_fat1_sector_hi
  ENDASM

  //Partition root-dir starts after the FATs
  //So first we must figure out how much space all the FATs take up together.
  LET lib_fat16_tempw = lib_fat16_nr_of_fats
  CALL lib_multdiv_mult ( lib_fat16_tempw lib_fat16_sectors_per_fat )

  //Now "total size of FATs" + "offset to the first FAT" = "root dir start"
  ASM
    clc
    lda lib_fat16_tempw
    adc lib_fat16_fat1_sector_lo
    sta lib_fat16_rootdir_sector_lo
    lda lib_fat16_tempw+1
    adc lib_fat16_fat1_sector_lo+1
    sta lib_fat16_rootdir_sector_lo+1
    lda #0
    adc lib_fat16_fat1_sector_hi
    sta lib_fat16_rootdir_sector_hi
  ENDASM

  //Partitions data-area starts after the root-dir. To calculate the total size of the data-area in sectors
  //we use this formula: ( "max root dir entries" * size of directory entry (32) ) / sector size(512)
  //To simplify 1 sector has space for 16, 32-byte directory entries.
  //So "max root dir entries" / 16 = "size of the total root-dir in sectors"
  LET lib_fat16_tempw = lib_fat16_rootdir_entries
  CALL lib_multdiv_div ( lib_fat16_tempw 16 )

  LET lib_fat16_rootdir_sectors = lib_fat16_tempw
  //Now "total size of root dir in sectors" + "root dir start sector" = "start of data area"
  ASM
    clc
    lda lib_fat16_tempw
    adc lib_fat16_rootdir_sector_lo
    sta lib_fat16_data_sector_lo
    lda lib_fat16_tempw+1
    adc lib_fat16_rootdir_sector_lo+1
    sta lib_fat16_data_sector_lo+1
    lda #0
    adc lib_fat16_rootdir_sector_hi
    sta lib_fat16_data_sector_hi
  ENDASM

  //MHTAG 20051205
  //CALL lib_dbg_hexoutb ( lib_fat16_data_sector_hi )
  //CALL lib_dbg_hexoutw ( lib_fat16_data_sector_lo )
  //CALL lib_dbg_textoutlf ( " data area start-sector" )

#IFDEF __LIB_FAT16_WRITESUPPORT
  CALL lib_fat16_calc_total_clusters
#IFEND

  //Now we've calculated all the pointers that important in this partition
  //For the more high level FAT16 access routines

  //Initialize work variables
  //LET lib_fat16_active_cluster = $ffff
  LET lib_fat16_active_dir_startcluster = $0000  // startdir = root

#IFDEF __LIB_FAT16_WRITESUPPORT
  //MHTAG 20051114 - prepare writing by finding some suitable write space when mounting
  LET lib_fat16_first_write_cluster = $ffff
  LET lib_fat16_last_write_cluster = 0
  CALL lib_fat16_int_search_free_clusters ( callstat )
  IF callstat != 0
    SUBEND
  ENDIF
#IFEND

  LET lib_fat16_mp_status = 0 //if we got this far, we've successfully mounted the partition!

FEND

//------------------------------------------------------------------------
// lib_fat16_dirnext
// lib_fat16_dirnext_le (INTERNAL)
//
// Purpose:
//   To scan active directory for the next direntry with the same bitmask
//   that was passed to the latest call to lib_fat16_dirfirst
//
// Parameters:
//   lib_fat16_ds_entry_ptr - pointer to the fat entry (check FAT docs for struct layout)
//                            NULL = 0 = not found / whole dir listed
//   lib_fat16_ds_status - result code:  0 - success
//                                       1 - internal error
//   lib_fat16_dn_firstnamechar - $00 = last entry, else not last entry
//
// Preparation: lib_fat16_dirfirst
//
// Usage of lib temp variables:
//   lib_fat16_tempw (pointer to current dir-entry in sector-buffer-memory)
//
// Note:
// When we enter this routine we're about to read the next dir
// entry. We must however first look at and adjust (if needed) the current
// cluster/sector/dir_entry_offset if we've passed a border in the previous
// call.
//
//------------------------------------------------------------------------



WORD lib_fat16_dn_entry_ptr
BYTE lib_fat16_dn_status
BYTE lib_fat16_dn_firstnamechar

#IFDEF __LIB_FAT16_WRITESUPPORT

FUNC lib_fat16_dirnext_le ( out:lib_fat16_dn_entry_ptr out:lib_fat16_dn_status out:lib_fat16_dn_firstnamechar )
  LET lib_fat16_dn_firstnamechar = $ff //when we write a new file we must be sure that a valid 0-entry terminated the dir.
#IFEND

FUNC lib_fat16_dirnext ( out:lib_fat16_dn_entry_ptr out:lib_fat16_dn_status )

  BYTE callstat
  BYTE filetype
  BYTE validentry //valid directory entry found for the caller (exit condition)

  LET lib_fat16_dn_status = 1
  LET lib_fat16_dn_entry_ptr = 0
  LET validentry = 0
  WHILE validentry = 0
    //have we passed the end of the sector (in buffer) yet?
    IF lib_fat16_active_dir_entry_offset >= LIB_FAT16_SECTOR_SIZE
      INC lib_fat16_active_dir_sector

      //if this is the root directory
      IF lib_fat16_active_dir_startcluster == 0
        IF lib_fat16_active_dir_sector >= lib_fat16_rootdir_sectors
          //(For all I know this is an error in the filesystem not to terminate the
          // directory with an entry that starts with a NULL-char). I just choose not to take action
          BREAK //read to the end of directory successfully. no more data
        ENDIF
      ELSE  //if this is in a normal cluster
        IF lib_fat16_active_dir_sector >= lib_fat16_sectors_per_cluster //we've passed the last sector of it

          //Call will update the "lib_fat16_active_dir_cluster" to the next cluster link.
          CALL lib_fat16_int_get_cluster_link ( lib_fat16_active_dir_cluster callstat )
          IF callstat != 0
            SUBEND //internal error (or end of cluster-chain == error in filesystem)
          ENDIF

          IF lib_fat16_active_dir_cluster == LIB_FAT16_CLUSTER_FREE
            SUBEND //internal error (or error in filesystem), how did we end up at an free cluster while folloing a chain?
          ENDIF

          // end of cluster-chain. we are ready with this dir-scan!
          IF lib_fat16_active_dir_cluster >= LIB_FAT16_CLUSTER_END
            //(For all I know this is an error in the filesystem not to terminate the
            // directory with an entry that starts with a NULL-char). I just choose not to take action
            BREAK //read to the end of directory successfully. no more data
          ENDIF

          //since we've gone to a new cluster we restart the internal "cluster sector offset counter"
          LET lib_fat16_active_dir_sector = 0
        ENDIF
      ENDIF

      LET lib_fat16_active_dir_entry_offset = 0 //restart "offset in sector" counter
      CALL lib_fat16_int_read_cluster_sector ( lib_fat16_active_dir_cluster lib_fat16_active_dir_sector lib_fat16_buffer_ptr callstat )
      IF callstat != 0
        SUBEND //internal error
      ENDIF

    ENDIF

    //calc the pointer address the the direntry in memory
    ADD lib_fat16_buffer_ptr + lib_fat16_active_dir_entry_offset -> lib_fat16_tempw

    PEEK lib_fat16_tempw -> lib_fat16_dn_firstnamechar

    //is it the end of the directory?
    IF lib_fat16_dn_firstnamechar == LIB_FAT16_END_OF_DIR
      //This is the correct end of an FAT16 directory. (see two "BREAK"s above)
      BREAK //read to the end of directory successfully. no more data
    ENDIF

    //is it a deleted entry?
    IF lib_fat16_dn_firstnamechar != LIB_FAT16_DELETED_FILE
      //no it exists!

      //Check the filetype of the current direntry
      PEEK lib_fat16_tempw[11] -> filetype

      IF lib_fat16_active_dir_bitmask != 0
        //calc the pointer to the filetype-bitfield in the direntry
        AND filetype WITH lib_fat16_active_dir_bitmask -> filetype
        //is it a fileentry of a type we're looking for?
        IF filetype != 0
          //found a matching entry, no more looping.
          LET lib_fat16_dn_entry_ptr = lib_fat16_tempw
          LET validentry = 1
        ENDIF
      ELSE //bitmask = 0, we want ALL file-entries!
        AND filetype WITH 8 -> filetype
        //(but not volume labels (and long filenames))
        IF filetype == 0
          //no volume label - it seams ok to return
          LET lib_fat16_dn_entry_ptr = lib_fat16_tempw
          LET validentry = 1
        ENDIF

      ENDIF
    ENDIF

    //prepare the offset to the next direntry read
    ADD LIB_FAT16_DIRENTRY_SIZE + lib_fat16_active_dir_entry_offset -> lib_fat16_active_dir_entry_offset

  WEND

  LET lib_fat16_dn_status = 0
FEND


//------------------------------------------------------------------------
// lib_fat16_dirstart
//
// Purpose:
//   To scan active directory for the first entry that maches the given bitmask
//
// Parameters:
//   lib_fat16_ds_bitmask - a bitmask that will be AND:ed with the entries
//                          of the active directory. If the result is not 0
//                          the entry is considered as a match returned to caller.
//                          (refer to FAT documentation for the meaning of the bits).
//                          special: value 0 lists all files and subdirs but not
//                          volume labels (and the LFN-support in VFAT)
//   lib_fat16_ds_entry_ptr - pointer to the fat entry (check FAT docs for struct layout)
//                            NULL = 0 = not found / whole dir listed
//   lib_fat16_ds_status - result code:  0 - success
//                                       1 - internal error
//
// Preparation: lib_mmc64_cardinit (inherited from our usage of
//              lib_mmc64_readsector)
//              lib_fat16_mount_partition
//
// Usage of lib temp variables:
//
//------------------------------------------------------------------------

BYTE lib_fat16_ds_bitmask
WORD lib_fat16_ds_entry_ptr
BYTE lib_fat16_ds_status
FUNC lib_fat16_dirstart ( lib_fat16_ds_bitmask out:lib_fat16_ds_entry_ptr out:lib_fat16_ds_status )

  BYTE callstat

  LET lib_fat16_ds_status = 1
  LET lib_fat16_active_dir_cluster = lib_fat16_active_dir_startcluster
  LET lib_fat16_active_dir_sector = 0
  LET lib_fat16_active_dir_entry_offset = 0
  LET lib_fat16_active_dir_bitmask = lib_fat16_ds_bitmask

  CALL lib_fat16_int_read_cluster_sector ( lib_fat16_active_dir_cluster lib_fat16_active_dir_sector lib_fat16_buffer_ptr callstat )
  IF callstat != 0
    SUBEND
  ENDIF

  CALL lib_fat16_dirnext ( lib_fat16_ds_entry_ptr callstat )
  IF callstat != 0
    SUBEND
  ENDIF

  LET lib_fat16_ds_status = 0 //set success if all went ok
FEND

//------------------------------------------------------------------------
// lib_fat16_chdir
//
// Purpose:
//   To change to one of the avaliable directories that exist in current
//   dir.
//
// Parameters:
//   lib_fat16_cd_dirname_ptr - pointer to a 11-char string. If this string
//                              is found to be an exact match of a directory-
//                              entry in the current dir, we will change to
//                              this new directory.
//   lib_fat16_cd_status - 0 = success, !0 = unable to change dir or internal error
//
// Preparation: lib_fat16_mount_partition
//
// Usage of lib temp variables:
//
// Note:
//   In FAT16 there are always (except in the ROOT-dir) two dirs
//   avaliable, one called "." and the other ".."
//   "." points to the current dir (which just makes you go back where you are
//   i.e. pointless =) (but there was a need for it in DOS long ago)
//   ".." points back to the parent dir.
//
//------------------------------------------------------------------------

WORD lib_fat16_cd_dirname_ptr
BYTE lib_fat16_cd_status
FUNC lib_fat16_chdir ( lib_fat16_cd_dirname_ptr out:lib_fat16_cd_status )

  WORD direntry @ $14
  BYTE callstat
  BYTE charcount
  BYTE char1
  BYTE char2
  WORD dirnamecmp_ptr

  LET lib_fat16_cd_status = 1

  //search for directories in current dir
  CALL lib_fat16_dirstart ( 16 direntry callstat )
  IF callstat != 0
    SUBEND //internal error
  ENDIF
  WHILE direntry
    //Compare the strings
    LET dirnamecmp_ptr = lib_fat16_cd_dirname_ptr
    LET charcount = 11
    WHILE charcount
      PEEK dirnamecmp_ptr -> char1
      PEEK direntry -> char2
      IF char1 != char2
        GOTO lib_fat16_cd_findnext
      ENDIF
      INC dirnamecmp_ptr
      INC direntry
      DEC charcount
    WEND
    //if we got here we have a match! So we change dir!
    //offset from start of direntry is now 11 and we want to 26
    GETASWORD@ direntry[26-11] -> lib_fat16_active_dir_startcluster
    GOTO lib_fat16_cd_exitsuccess

    LABEL lib_fat16_cd_findnext
    CALL lib_fat16_dirnext ( direntry callstat )
    IF callstat != 0
      SUBEND //internal error
    ENDIF
  WEND

  //if we came here through the loop but didn't find anything the cd has failed - exit with errorcode
  SUBEND

  LABEL lib_fat16_cd_exitsuccess
  LET lib_fat16_cd_status = 0
FEND

//------------------------------------------------------------------------
// lib_fat16_int_get_free_cluster (internal)
//
// purpose: This routine is used when the lib needs a new cluster to write
//          to, may it be for filedata or for extending a subdirectory
//          with an entry past the end of it's current last cluster
//
//          The need of this routine comes because
//          lib_fat16_int_find_unused_clusters can only find new cluster
//          holes however with any fragmentation these holes are spread
//          between chunks data on the disk. So this routine searches
//          for next cluster hole as needed.
//
//          An error is issued if diskspace is full.
//------------------------------------------------------------------------

#IFDEF __LIB_FAT16_WRITESUPPORT

WORD lib_fat16_int_cw_free_cluster_nr
BYTE lib_fat16_int_cw_status

FUNC lib_fat16_int_get_free_cluster ( out:lib_fat16_int_cw_free_cluster_nr out:lib_fat16_int_cw_status )

  BYTE callstat

  LET lib_fat16_int_cw_status = 1 //error

  //Are there any more free clusters in the previously discovered (or not discovered)
  //"cluster hole"?
  IF lib_fat16_first_write_cluster > lib_fat16_last_write_cluster
    //No, so we try to find another "cluster hole"
    CALL lib_fat16_int_search_free_clusters ( callstat )
    IF callstat != 0
      SUBEND
    ENDIF
  ENDIF

  LET lib_fat16_int_cw_free_cluster_nr = lib_fat16_last_write_cluster
  DEC lib_fat16_last_write_cluster

  LET lib_fat16_int_cw_status = 0 //success

FEND

#IFEND

//------------------------------------------------------------------------
// lib_fat16_int_add_dir_entry (INTERNAL)
//
// Purpose: when a new, previously not existing, file is opened for
//          writing, a new direntry must be added to the current
//          directory. This function does that.
//          Note: This function is only responsible for allocating a new
//          uninitialized entry, whereas the fopen function is responsible
//          to make sure that we don't add a duplicate entry in the
//          directory.
//
// Method: This function gets its input from the dirstart/dirnext done
//         by fopen() when searching, if the file for write exists from
//         before.
//         Then it copies the last entry forward one entry
//         (if possible). If all went successful it returns the pointers
//         to the newly allocated entry to the caller.
//
// Preparation: fopen() internal call
//              Run dirstart/dirnext until the active directory ends.
//              This is done in fopen() before calling this function.
//
// Parameters:
//
//   lib_fat16_ae_new_entry_filename - pointer the a 11 char long filename
//                                     for the new entry.
//   lib_fat16_ae_new_entry_cluster -  The cluster in which the new direntry
//                                     resides.
//
//
//  ZP-vars:
//     lib_fat16_tempw, lib_fat16_tempw2
//------------------------------------------------------------------------

#IFDEF __LIB_FAT16_WRITESUPPORT

WORD lib_fat16_ae_new_entry_filename
WORD lib_fat16_ae_new_entry_cluster
WORD lib_fat16_ae_new_entry_sector
WORD lib_fat16_ae_new_entry_offset
BYTE lib_fat16_ae_status

FUNC lib_fat16_int_add_dir_entry ( lib_fat16_ae_new_entry_filename out:lib_fat16_ae_new_entry_cluster out:lib_fat16_ae_new_entry_sector out:lib_fat16_ae_new_entry_offset out:lib_fat16_ae_status )

  LET lib_fat16_ae_status = 1

  WORD null_entry_cluster
  WORD null_entry_sector
  WORD null_entry_offset

  BYTE callstat


  //We assume caller has used looped to the last direntry with "dirnext"
  //NOTE!! If this is not the case we will OVERWRITE valid entries.
  LET lib_fat16_ae_new_entry_cluster = lib_fat16_active_dir_cluster
  LET lib_fat16_ae_new_entry_sector = lib_fat16_active_dir_sector
  LET lib_fat16_ae_new_entry_offset = lib_fat16_active_dir_entry_offset
  LET null_entry_cluster = lib_fat16_active_dir_cluster
  LET null_entry_sector = lib_fat16_active_dir_sector
  LET null_entry_offset = lib_fat16_active_dir_entry_offset

  //-------------------------------------------------------
  //TASK1: prepare to move the NULL-entry (end-of-dir)
  //       (leaving the old position for the new entry)

  //Is the last entry the last entry in sector also?
  IF null_entry_offset >= LIB_FAT16_SECTOR_SIZE-LIB_FAT16_DIRENTRY_SIZE
    INC null_entry_sector
    //This is the ROOT dir?
    IF null_entry_cluster == 0
      IF null_entry_sector >= lib_fat16_rootdir_sectors
        SUBEND //root directory cannot be exceeded
      ELSE
        //There are rootdir sectors left so we just take the next one.
        LET null_entry_offset = 0
      ENDIF
    ELSE //this is NOT the ROOT-dir
      //must we allocate another cluster to fit the new entry?
      IF null_entry_cluster >= lib_fat16_sectors_per_cluster
        //Yes, so we ask for another cluster.
        CALL lib_fat16_int_get_free_cluster ( null_entry_cluster callstat )
        IF callstat != 0
          SUBEND //Could not get another cluster (disk probably full)
        ENDIF
        //Now we have the number of a free cluster, yet we have to:
        //  a. Link the old last cluster to this new cluster
        //  b. mark the new cluster as the last in the chain

        //a.
        CALL lib_fat16_int_set_cluster_link ( lib_fat16_ae_new_entry_cluster null_entry_cluster callstat )
        IF callstat != 0
          SUBEND //internal error
        ENDIF

        //b.
        //Note: allthough everything above > $ffef is a "cluster chain ended", some badly written drivers only understand $ffff
        CALL lib_fat16_int_set_cluster_link ( null_entry_cluster $ffff callstat )
        IF callstat != 0
          SUBEND //internal error
        ENDIF

        //Now in the new cluster we restart the internal "offsets"
        LET null_entry_offset = 0
        LET null_entry_sector = 0

      ELSE //no, we just use another sector in the current cluster
        LET null_entry_offset = 0
      ENDIF
    ENDIF
  ELSE //last entry is not at the end of a sector.
    ADD LIB_FAT16_DIRENTRY_SIZE + null_entry_offset -> null_entry_offset
  ENDIF

  //When we've got here we have calculated all the pointers
  //to the newly created END-OF-DIR entry.

  //-------------------------------------------------------
  //TASK2 - Write a new NULL-entry.

  //Read up the sector where it resides.
  CALL lib_fat16_int_read_cluster_sector ( null_entry_cluster null_entry_sector lib_fat16_buffer_ptr callstat )
  IF callstat != 0
    SUBEND //internal error
  ENDIF
  //Adjust offset to where in memory the buffer is.
  ADD lib_fat16_buffer_ptr + null_entry_offset -> lib_fat16_tempw2
  //make this a "null-entry"
  POKE lib_fat16_tempw2 , 0
  //and write it back to disk
  CALL lib_fat16_int_write_cluster_sector ( null_entry_cluster null_entry_sector lib_fat16_buffer_ptr callstat )
  IF callstat != 0
    SUBEND //internal error
  ENDIF


  //-------------------------------------------------------
  //TASK3 - Create a new direntry (empty file)
  //Read up the sector where it resides.
  CALL lib_fat16_int_read_cluster_sector ( lib_fat16_ae_new_entry_cluster lib_fat16_ae_new_entry_sector lib_fat16_buffer_ptr callstat )
  IF callstat != 0
    SUBEND //internal error
  ENDIF
  //Adjust offset to where in memory the buffer is.
  ADD lib_fat16_buffer_ptr + lib_fat16_ae_new_entry_offset -> lib_fat16_tempw2
  //Copy in the new filename
  LET lib_fat16_tempw = lib_fat16_ae_new_entry_filename
	ASM
	  ldy #0
lib_fat16_ae_l1
	  lda (lib_fat16_tempw),y
	  sta (lib_fat16_tempw2),y
	  iny
	  cpy #11
	  bne lib_fat16_ae_l1

	  ;clear the rest of the entry (NULLs)
    lda #0
lib_fat16_ae_l2
	  sta (lib_fat16_tempw2),y
	  iny
	  cpy #32
	  bne lib_fat16_ae_l2
	ENDASM

  CALL lib_fat16_int_write_cluster_sector ( lib_fat16_ae_new_entry_cluster lib_fat16_ae_new_entry_sector lib_fat16_buffer_ptr callstat )
  IF callstat != 0
    SUBEND //internal error
  ENDIF

  LET lib_fat16_ae_status = 0

FEND
#IFEND

//------------------------------------------------------------------------
// lib_fat16_fopen
//
// Purpose:
//   To open a file in the current directory and initialize a descriptor
//   table entry.
//   A file we're opening for read must exist.
//   A file we're opening for write must NOT exist.
//
// Parameters:
//   descriptor_ptr - this is the fileptr the user needs to
//                                 remember for all further operations
//                                 on this file. If = 0 = NULL then
//                                 the function was unable to open the file.
//   lib_fat16_fo_filename_ptr - 11 char string, that must match the dir-
//                               entry exacly (or represent the new filename
//                               when writing).
//   lib_fat16_fo_filemode - use one of the filemode constants defined in
//                           the constant section. "lib_fat16_filemode_*"
//                           currently only "LIB_FAT16_FILEMODE_READ" is
//                           implemented.
//   lib_fat16_fo_status - 0 = success. !0 = file open error or internal
//                         error.
//
// Preparation: lib_fat16_mount_partition
//
// Dependencies: This function uses both "lib_fat16_dirstart" and
//               "lib_fat16_dirnext" so don't use fopen in a dir-scan
//               sequence. (it won't work at all as lib_fat16_dirstart
//               will reset the current search your are doing)
//
//
// Usage of lib temp variables:
//   (none)  ...  used lib_fat16_tempw before.
//
//------------------------------------------------------------------------

WORD descriptor_ptr
WORD lib_fat16_fo_filename_ptr
BYTE lib_fat16_fo_filemode
BYTE lib_fat16_fo_status

FUNC lib_fat16_fopen ( out:descriptor_ptr lib_fat16_fo_filename_ptr lib_fat16_fo_filemode out:lib_fat16_fo_status )

  WORD descr @ $49
  BYTE max_files
  WORD clusternr
  WORD direntry @ $14
  BYTE callstat
  BYTE charcount
  BYTE char1
  BYTE char2
  BYTE attributes
  WORD filnamecmp_ptr
  WORD wtemp
#IFDEF __LIB_FAT16_WRITESUPPORT
  BYTE lastdirentrychar
  WORD new_entry_cluster
  WORD new_entry_sector
  WORD new_entry_offset
#IFEND

  LET lib_fat16_fo_status = 1
  LET descriptor_ptr = 0

#IFNDEF __LIB_FAT16_WRITESUPPORT
  //if library is compiled for readonly support, trying to opening file for write is forbidden
  IF lib_fat16_fo_filemode != LIB_FAT16_FILEMODE_READ
    SUBEND
  ENDIF
#IFEND

  //------------------------------------------
  //TASK1 - search for an avaliable descriptor

  //max files is defined as the address of "lib_fat16_max_open_files".
  //This is a little quirk to get it into a local variable
  ASM
    lda #<lib_fat16_max_open_files
    sta |max_files|
  ENDASM
  POINTER descr TO lib_fat16_file_descriptors
  WHILE max_files
    GETASWORD@ descr -> clusternr
    IF clusternr == $ffff
      GOTO lib_fat16_fo_task2
    ENDIF
    ADD descr + __LIB_FAT16_FILE_DESCRIPTOR_SIZE -> descr
    DEC max_files
  WEND

  SUBEND //unable to find a free filedescriptor-slot, return error

  //--------------------------------------------------------
  //TASK2 - find the requested file in the current directory
  LABEL lib_fat16_fo_task2

  CALL lib_fat16_dirstart ( 0 direntry callstat )
  IF callstat != 0
    SUBEND //internal error
  ENDIF
  WHILE direntry
    //Compare the strings
    LET filnamecmp_ptr = lib_fat16_fo_filename_ptr
    LET charcount = 11
    WHILE charcount
      PEEK filnamecmp_ptr -> char1
      PEEK direntry -> char2
      IF char1 != char2
        GOTO lib_fat16_fo_findnext
      ENDIF

      INC filnamecmp_ptr
      INC direntry
      DEC charcount
    WEND

    //if we got here we have a match! Now this must be a valid file
    //so we've test the attributes of it.
    PEEK direntry -> attributes
    //if the direntry is systemfile, volume label or directory we cannot open it!
    AND attributes WITH 4+8+16 -> attributes
    IF attributes != 0
      SUBEND //error, direntry was not valid to open (see reasons above)
    ENDIF
    #IFDEF __LIB_FAT16_WRITESUPPORT
    IF lib_fat16_fo_filemode == LIB_FAT16_FILEMODE_READ
    #IFEND
      GOTO lib_fat16_fo_task4
    #IFDEF __LIB_FAT16_WRITESUPPORT
    ELSE
      SUBEND //it's write error to try to overwrite an existing file.
    ENDIF
    #IFEND



    LABEL lib_fat16_fo_findnext
#IFDEF __LIB_FAT16_WRITESUPPORT
    CALL lib_fat16_dirnext_le ( direntry callstat lastdirentrychar )
#IFEND
#IFNDEF __LIB_FAT16_WRITESUPPORT
    CALL lib_fat16_dirnext ( direntry callstat )
#IFEND
    IF callstat != 0
      SUBEND //internal error
    ENDIF
  WEND


#IFDEF __LIB_FAT16_WRITESUPPORT
  IF lib_fat16_fo_filemode == LIB_FAT16_FILEMODE_READ
#IFEND
    SUBEND // if we got here, we didn't find the file the caller requested for read access
#IFDEF __LIB_FAT16_WRITESUPPORT
  ENDIF
#IFEND


#IFDEF __LIB_FAT16_WRITESUPPORT
  //-------------------------------------------------------------------
  //TASK3 - If writemode we need to create us a new direntry

  //check verification that dirnext was on the last entry in dir
  IF lastdirentrychar != LIB_FAT16_END_OF_DIR
    SUBEND //The DIR did not end with a NULL-filename. Eighter a filesystem error or
           //it might be the rootdir that is completely filled
           //We cannot however proceed to create another file in this condition.
  ENDIF

  CALL lib_fat16_int_add_dir_entry ( lib_fat16_fo_filename_ptr new_entry_cluster new_entry_sector new_entry_offset callstat )
  IF callstat != 0
    SUBEND //internal error
  ENDIF


#IFEND

  //-------------------------------------------------------------------
  //TASK4 - Copy info from the direntry to the accuired(or created)file descriptor

  LABEL lib_fat16_fo_task4

  //clear descriptor
  ASM
    ldy #0
    tya
lib_fat16_fo_l2
    sta (|descr|),y
    iny
    cpy #lib_fat16_file_descriptor_size
    bne lib_fat16_fo_l2
  ENDASM

  //Copy the startcluster to the descriptor
  // (direntry points to position 11 in the directory-entry
  //  since we've scanned the filename before. therefore "X-11"
  GETASWORD@ direntry[26-11] -> clusternr
  PUTASWORD@ descr[FAT16_DESC_CURR_CLUSTER] VALUE clusternr

  //Copy the filesize to the descriptor. (Two words == one double word :-)
  GETASWORD@ direntry[26-11+2] -> wtemp
  PUTASWORD@ descr[FAT16_DESC_FILESIZE] VALUE wtemp
  GETASWORD@ direntry[26-11+4] -> wtemp
  PUTASWORD@ descr[FAT16_DESC_FILESIZE+2] VALUE wtemp

  //set filemode in descriptor
  POKE descr[FAT16_DESC_FILEMODE] , lib_fat16_fo_filemode

#IFDEF __LIB_FAT16_WRITESUPPORT
  IF lib_fat16_fo_filemode == LIB_FAT16_FILEMODE_WRITE
    PUTASWORD@ descr[FAT16_DESC_DIRE_CLUSTER] VALUE new_entry_cluster
    PUTASWORD@ descr[FAT16_DESC_DIRE_SECTOR]  VALUE new_entry_sector
    PUTASWORD@ descr[FAT16_DESC_DIRE_OFFSET]  VALUE new_entry_offset
  ENDIF
#IFEND

  LET descriptor_ptr = descr
  LET lib_fat16_fo_status = 0
FEND


//------------------------------------------------------------------------
// lib_fat16_fclose
// lib_fat16_fclose (returns status of operation R/W only)
//
// Purpose:
//   To close an open file specified by a descriptor pointer, the caller
//   accuired from a previous call to "lib_fat16_fopen"
//
// Parameters:
//   lib_fat16_fc_descriptor_ptr -  the file pointer.
//
// Preparation: lib_fat16_mount_partition, lib_fat16_fopen
//
// Usage of lib temp variables:
//   (none) ... used lib_fat16_tempw before
//
//------------------------------------------------------------------------

//WORD lib_fat16_fc_descriptor_ptr
WORD lib_fat16_fc_descr @ $49
#IFDEF __LIB_FAT16_WRITESUPPORT
BYTE lib_fat16_fc_stat
FUNC lib_fat16_fcloses ( lib_fat16_fc_descr out:lib_fat16_fc_stat )
  LET lib_fat16_fc_stat = 1
#IFEND
FUNC lib_fat16_fclose ( lib_fat16_fc_descr )

  //LET lib_fat16_fc_descr = lib_fat16_fc_descriptor_ptr

#IFDEF __LIB_FAT16_WRITESUPPORT
  BYTE filemode
  WORD direntry @ $14
  WORD entry_cluster
  WORD entry_sector
  WORD entry_offset
  WORD startcluster
  WORD clusternr // <- this is the last cluster!
  WORD filesize_lo
  WORD filesize_hi
  BYTE callstat

  PEEK lib_fat16_fc_descr[FAT16_DESC_FILEMODE] -> filemode
  IF filemode != LIB_FAT16_FILEMODE_READ

    //read up the direntry
    GETASWORD@ lib_fat16_fc_descr[FAT16_DESC_DIRE_CLUSTER] -> entry_cluster
    GETASWORD@ lib_fat16_fc_descr[FAT16_DESC_DIRE_SECTOR]  -> entry_sector
    GETASWORD@ lib_fat16_fc_descr[FAT16_DESC_DIRE_OFFSET]  -> entry_offset
    CALL lib_fat16_int_read_cluster_sector ( entry_cluster entry_sector lib_fat16_buffer_ptr callstat )
    IF callstat != 0
      SUBEND
    ENDIF

    ADD lib_fat16_buffer_ptr + entry_offset -> direntry

    //update it
    GETASWORD@ lib_fat16_fc_descr[FAT16_DESC_FIRST_CLUSTER] -> startcluster
    GETASWORD@ lib_fat16_fc_descr[FAT16_DESC_CURR_CLUSTER] -> clusternr
    PUTASWORD@ direntry[26] VALUE startcluster

    //Copy the filesize to the descriptor. (Two words == one double word :-)
    GETASWORD@ lib_fat16_fc_descr[FAT16_DESC_FILEPOS] -> filesize_lo
    PUTASWORD@ direntry[28] VALUE filesize_lo
    GETASWORD@ lib_fat16_fc_descr[FAT16_DESC_FILEPOS+2] -> filesize_hi
    PUTASWORD@ direntry[30] VALUE filesize_hi

    //write down the direntry
    CALL lib_fat16_int_write_cluster_sector ( entry_cluster entry_sector lib_fat16_buffer_ptr callstat )
    IF callstat != 0
      SUBEND
    ENDIF

    //mark last cluster in file as last-in-cluster-chain
    //
    //Note: allthough everything above > $ffef is a "cluster chain ended", some badly written drivers only understand $ffff
    CALL lib_fat16_int_set_cluster_link ( clusternr $ffff callstat )
    IF callstat != 0
      SUBEND
    ENDIF

  ENDIF
  LET lib_fat16_fc_stat = 0 //if we got here it's a success
#IFEND

  //we set the "current cluster" to $ffff. This
  //indicates for "lib_fat16_fopen" that this
  //descriptor location is up for grabs
  ASM
    ldy #0
    ;tya
    lda #$ff
    sta (lib_fat16_fc_descr),y
    iny
    sta (lib_fat16_fc_descr),y
  ENDASM

FEND


//------------------------------------------------------------------------
// lib_fat16_fblockread
//
// Purpose:
//   Try to read a block of 512 bytes from an file that is open for reading.
//   If the file end before we've reached 512 read bytes the reading ends.
//   (Or more correct: we don't write anymore to memory-buffer, but the
//    sector is fully read)
//
// Parameters:
//   lib_fat16_fr_descriptor_ptr -  the file pointer.
//   lib_fat16_fr_block_ptr - pointer to memory where to write the block
//                            read from the file
//   lib_fat16_fr_read_count - The number of bytes actually read from
//                             file. If this is not equal to 512
//                             it usually means we've reached the end
//                             of the file.
//
// Preparation: lib_fat16_mount_partition, lib_fat16_fopen
//
// NOTE: this routine calls "lib_fat16_int_get_cluster_link" which
// will ruin any ongoing "lib_fat16_dirstart" - "lib_fat16_dirnext"
// sequencen. Don't do it. ( both use the internal FAT-buffer )
//
// Usage of lib temp variables:
//   (none) ... used lib_fat16_tempw before
//
//------------------------------------------------------------------------

//WORD lib_fat16_fr_descriptor_ptr
WORD lib_fat16_fr_descr @ $49
WORD lib_fat16_fr_block_ptr
WORD lib_fat16_fr_read_count
BYTE lib_fat16_fr_status
FUNC lib_fat16_fblockread ( lib_fat16_fr_descr lib_fat16_fr_block_ptr out:lib_fat16_fr_read_count out:lib_fat16_fr_status )

  WORD lib_fat16_fr_current_cluster
  WORD lib_fat16_fr_sector_in_cluster //note ONLY the lowbyte in this one i used (must be word for call to next_cluster_link).
  BYTE lib_fat16_fr_current_filemode
  BYTE lib_fat16_fr_callstat
  WORD lib_fat16_fr_filesize_lo
  WORD lib_fat16_fr_filesize_hi
  WORD lib_fat16_fr_filepos_lo
  WORD lib_fat16_fr_filepos_hi
  WORD lib_fat16_fr_fileleft_lo
  WORD lib_fat16_fr_fileleft_hi

  LET lib_fat16_fr_status = 1

  //--------------------------------------
  //TASK1 - is the file valid for reading - copy data

  //LET lib_fat16_fr_descr = lib_fat16_fr_descriptor_ptr
  GETASWORD@ lib_fat16_fr_descr[FAT16_DESC_CURR_CLUSTER] -> lib_fat16_fr_current_cluster

  PEEK lib_fat16_fr_descr[FAT16_DESC_FILEMODE] -> lib_fat16_fr_current_filemode
  IF lib_fat16_fr_current_filemode != LIB_FAT16_FILEMODE_READ
    SUBEND //error. file not open for reading.
  ENDIF

  //copy descriptor data to local variables
  PEEK lib_fat16_fr_descr[FAT16_DESC_CURR_SECTOR] -> lib_fat16_fr_sector_in_cluster
	//Copy filesize och filepos from descriptor (8 bytes for two 32-bit variables)
  ASM
    ldy #5
    ldx #0
lib_fat16_fr_l1
    lda (lib_fat16_fr_descr),y
    sta |lib_fat16_fr_filesize_lo|,x
    iny
    inx
    cpx #8
    bne lib_fat16_fr_l1
  ENDASM

  IF lib_fat16_fr_current_cluster == 0
    //special case - empty (0-size) file is valid to open for reading but only 0 bytes can be read.
    IF lib_fat16_fr_filesize_lo == 0
      IF lib_fat16_fr_filesize_hi == 0
        LET lib_fat16_fr_read_count = 0
        LET lib_fat16_fr_status = 0 //exit is set to success
      ENDIF
    ENDIF
    SUBEND //error (unless special above) - file is not open
  ENDIF

  //-------------------------------------------
  //TASK2 - Adjust clusters / sectors if needed

  //Did the previous read put us past the end of the cluster?
  IF lib_fat16_fr_sector_in_cluster >= lib_fat16_sectors_per_cluster

    //Call will update the "lib_fat16_active_dir_cluster" to the next cluster link.
    CALL lib_fat16_int_get_cluster_link ( lib_fat16_fr_current_cluster lib_fat16_fr_callstat )
    IF lib_fat16_fr_callstat != 0
      SUBEND //internal error
    ENDIF
    LET lib_fat16_fr_sector_in_cluster = 0
  ENDIF

  //now if ( "File total size in bytes" - "Current position in file" ) < 512 then the sector we just read contains the EOF
  //subtracting 32 bit values is so very funny on 6502 :)
  ASM
    sec
    lda |lib_fat16_fr_filesize_lo|
    sbc |lib_fat16_fr_filepos_lo|
    sta |lib_fat16_fr_fileleft_lo|
    lda |lib_fat16_fr_filesize_lo|+1
    sbc |lib_fat16_fr_filepos_lo|+1
    sta |lib_fat16_fr_fileleft_lo|+1
    lda |lib_fat16_fr_filesize_hi|
    sbc |lib_fat16_fr_filepos_hi|
    sta |lib_fat16_fr_fileleft_hi|
    lda |lib_fat16_fr_filesize_hi|+1
    sbc |lib_fat16_fr_filepos_hi|+1
    sta |lib_fat16_fr_fileleft_hi|+1
  ENDASM

  //----------------------------------------------
  //TASK3 - Do the actual read


  LET lib_fat16_fr_read_count = LIB_FAT16_SECTOR_SIZE
  IF lib_fat16_fr_fileleft_hi == 0
    IF lib_fat16_fr_fileleft_lo < LIB_FAT16_SECTOR_SIZE
      //We've just read to the end of the file
      //of course we could know that from the link-table also. but we want to report
      //the number of bytes read to the caller.
      LET lib_fat16_fr_read_count = lib_fat16_fr_fileleft_lo

      CALL lib_fat16_int_read_cluster_sector_part ( lib_fat16_fr_current_cluster lib_fat16_fr_sector_in_cluster lib_fat16_fr_block_ptr 0 lib_fat16_fr_read_count lib_fat16_fr_callstat )
      IF lib_fat16_fr_callstat != 0
        SUBEND //internal error
      ENDIF

      GOTO lib_fat16_fr_skipelse1
    ENDIF
  ENDIF

  CALL lib_fat16_int_read_cluster_sector ( lib_fat16_fr_current_cluster lib_fat16_fr_sector_in_cluster lib_fat16_fr_block_ptr lib_fat16_fr_callstat )
  IF lib_fat16_fr_callstat != 0
    SUBEND //internal error
  ENDIF

LABEL lib_fat16_fr_skipelse1
  INC lib_fat16_fr_sector_in_cluster //we've just read it so we add the offset

  //----------------------------------------------
  //TASK4 - Recalc filepos
	//        Write back data to the file descriptor
  PUTASWORD@ lib_fat16_fr_descr[FAT16_DESC_CURR_CLUSTER] VALUE lib_fat16_fr_current_cluster
  ASM
    ldy #2
    lda |lib_fat16_fr_sector_in_cluster|
    sta (lib_fat16_fr_descr),y

    ; increment the position (32-bit add)

    ldy #9
    clc
    lda (lib_fat16_fr_descr),y
    adc lib_fat16_fr_read_count  ; lowbyte of the sector read
    sta (lib_fat16_fr_descr),y
    iny
    lda (lib_fat16_fr_descr),y
    adc lib_fat16_fr_read_count+1 ; hibyte of the sector read
    sta (lib_fat16_fr_descr),y
    iny
    lda (lib_fat16_fr_descr),y
    adc #0         ; does not apply (sector is max 512 bytes)
    sta (lib_fat16_fr_descr),y
    iny
    lda (lib_fat16_fr_descr),y
    adc #0         ; does not apply (sector is max 512 bytes)
    sta (lib_fat16_fr_descr),y
  ENDASM

  LET lib_fat16_fr_status = 0
FEND

//------------------------------------------------------------------------
// lib_fat16_fblockwrite
//
// Purpose:
//   To write a block (512) of data to a file, opened for writing.
//   If the complete size of the file is not dividable by 512, the size of
//   the last block is allowed to be less than 512 bytes.
//
// Params:
//
//
//
//------------------------------------------------------------------------

#IFDEF __LIB_FAT16_WRITESUPPORT

//WORD lib_fat16_fw_descriptor_ptr
WORD lib_fat16_fw_descr @ $49
WORD lib_fat16_fw_block_ptr
WORD lib_fat16_fw_block_size
BYTE lib_fat16_fw_status
FUNC lib_fat16_fblockwrite ( lib_fat16_fw_descr lib_fat16_fw_block_ptr lib_fat16_fw_block_size out:lib_fat16_fw_status )

  WORD lib_fat16_fw_start_cluster
  WORD lib_fat16_fw_current_cluster
  WORD lib_fat16_fw_sector_in_cluster
  WORD lib_fat16_fw_new_cluster
  BYTE lib_fat16_fw_current_filemode
  BYTE lib_fat16_fw_callstat

  LET lib_fat16_fw_status = 1 //assume error

  //LET lib_fat16_fw_descr = lib_fat16_fw_descriptor_ptr
  //----------------------------------------------
  //TASK1 - Read up vital data from filedescriptor

  PEEK lib_fat16_fw_descr[FAT16_DESC_FILEMODE] -> lib_fat16_fw_current_filemode
  IF lib_fat16_fw_current_filemode != LIB_FAT16_FILEMODE_WRITE
    SUBEND //error. file not open for writing.
  ENDIF
  GETASWORD@ lib_fat16_fw_descr[FAT16_DESC_FIRST_CLUSTER] -> lib_fat16_fw_start_cluster
  GETASWORD@ lib_fat16_fw_descr[FAT16_DESC_CURR_CLUSTER] -> lib_fat16_fw_current_cluster
  PEEK lib_fat16_fw_descr[FAT16_DESC_CURR_SECTOR] -> lib_fat16_fw_sector_in_cluster

  //This assures that only the last sector (block) written can be other than the size of a sector.
  IF lib_fat16_fw_block_size != LIB_FAT16_SECTOR_SIZE
    IF lib_fat16_fw_block_size > LIB_FAT16_SECTOR_SIZE
      SUBEND //error - not supported
    ENDIF
    //LET lib_fat16_fw_current_filemode = LIB_FAT16_FILEMODE_WRITE_ENDED
    POKE lib_fat16_fw_descr[FAT16_DESC_FILEMODE] , LIB_FAT16_FILEMODE_WRITE_ENDED
    IF lib_fat16_fw_block_size == 0 //Writing 0 bytes to a valid open write-file is OK (but nothing happens)
      LET lib_fat16_fw_status = 0
      SUBEND
    ENDIF
  ENDIF

  //----------------------------------------------
  //TASK2 - Is this the first block in the file?
  IF lib_fat16_fw_start_cluster == 0
    CALL lib_fat16_int_get_free_cluster ( lib_fat16_fw_start_cluster lib_fat16_fw_callstat )
    IF lib_fat16_fw_callstat != 0
      SUBEND //Could not get another cluster (disk probably full)
    ENDIF
    PUTASWORD@ lib_fat16_fw_descr[FAT16_DESC_FIRST_CLUSTER] VALUE lib_fat16_fw_start_cluster
    LET lib_fat16_fw_current_cluster = lib_fat16_fw_start_cluster
    //As this file is just opened sector_in_cluster is set to 0
    //LET lib_fat16_fw_sector_in_cluster = 0
  ENDIF

  //----------------------------------------------
  //TASK3 - Time to allocate another cluster?
  IF lib_fat16_fw_sector_in_cluster >= lib_fat16_sectors_per_cluster
    CALL lib_fat16_int_get_free_cluster ( lib_fat16_fw_new_cluster lib_fat16_fw_callstat )
    IF lib_fat16_fw_callstat != 0
      SUBEND //Could not get another cluster (disk probably full)
    ENDIF
    //Update the cluster link from last cluster to the new one.
    CALL lib_fat16_int_set_cluster_link ( lib_fat16_fw_current_cluster lib_fat16_fw_new_cluster lib_fat16_fw_callstat )
    IF lib_fat16_fw_callstat != 0
      SUBEND //internal error
    ENDIF
    LET lib_fat16_fw_sector_in_cluster = 0
    LET lib_fat16_fw_current_cluster = lib_fat16_fw_new_cluster
  ENDIF

  //----------------------------------------------
  //TASK4 - Write the data!
  CALL lib_fat16_int_write_cluster_sector_part ( lib_fat16_fw_current_cluster lib_fat16_fw_sector_in_cluster lib_fat16_fw_block_ptr 0 lib_fat16_fw_block_size lib_fat16_fw_callstat )
  IF lib_fat16_fw_callstat != 0
    SUBEND //internal error
  ENDIF
  INC lib_fat16_fw_sector_in_cluster //step forward for next write

  //----------------------------------------------
  //TASK5 - Update descriptor (and filepos calc)

  PUTASWORD@ lib_fat16_fw_descr[FAT16_DESC_CURR_CLUSTER] VALUE lib_fat16_fw_current_cluster
  POKE lib_fat16_fw_descr[FAT16_DESC_CURR_SECTOR] , lib_fat16_fw_sector_in_cluster

  ASM
    ; increment the position (32-bit add)

    ldy #9
    clc
    lda (lib_fat16_fw_descr),y
    adc lib_fat16_fw_block_size  ; lowbyte of the sector read
    sta (lib_fat16_fw_descr),y
    iny
    lda (lib_fat16_fw_descr),y
    adc lib_fat16_fw_block_size+1 ; hibyte of the sector read
    sta (lib_fat16_fw_descr),y
    iny
    lda (lib_fat16_fw_descr),y
    adc #0         ; does not apply (sector is max 512 bytes)
    sta (lib_fat16_fw_descr),y
    iny
    lda (lib_fat16_fw_descr),y
    adc #0         ; does not apply (sector is max 512 bytes)
    sta (lib_fat16_fw_descr),y
  ENDASM

  LET lib_fat16_fw_status = 0 //if we got here it's a success
FEND

#IFEND


LABEL lib_fat16_skip

#IFEND
