; File I/O routines for the VM target
;
; NOTE: not all is implemented.
; NOTE: some calls are slightly different from the "official" diskio library because for example,
;       here we cannot deal with multiple return values.

%import textio
%import syslib

diskio {
    %option no_symbol_prefixing, ignore_unused

    sub directory() -> bool {
        ; -- Prints the directory contents to the screen. Returns success.
        %ir {{
            loadm.w r65534,diskio.load.filenameptr
            loadm.w r65535,diskio.load.address_override
            syscall 45 (): r0.b
            returnr.b r0
        }}
    }

    sub list_filenames(uword pattern_ptr, uword filenames_buffer, uword filenames_buf_size) -> ubyte {
        ; -- fill the provided buffer with the names of the files on the disk (until buffer is full).
        ;    Files in the buffer are separated by a 0 byte. You can provide an optional pattern to match against.
        ;    After the last filename one additional 0 byte is placed to indicate the end of the list.
        ;    Returns number of files (it skips 'dir' entries i.e. subdirectories).
        ;    Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
        txt.print("@TODO: list_filenames\n")
        sys.clear_carry()
        return 0
    }

    ; ----- iterative file lister functions (uses the read io channel) -----

    sub lf_start_list(uword pattern_ptr) -> bool {
        ; -- start an iterative file listing with optional pattern matching.
        ;    note: only a single iteration loop can be active at a time!
        txt.print("@TODO: lf_start_list\n")
        return false
    }

    sub lf_next_entry() -> bool {
        ; -- retrieve the next entry from an iterative file listing session.
        ;    results will be found in list_blocks, list_filename, and list_filetype.
        ;    if it returns false though, there are no more entries (or an error occurred).
        txt.print("@TODO: lf_next_entry\n")
        return false
    }

    sub lf_end_list() {
        txt.print("@TODO: lf_end_list\n")
    }


    ; ----- iterative file loader functions (uses the input io channel) -----

    sub f_open(uword filenameptr) -> bool {
        ; -- open a file for iterative reading with f_read
        ;    note: only a single iteration loop can be active at a time!
        ;    Returns true if the file is successfully opened and readable.
        ;    No need to check status(), unlike f_open_w() !
        ;    NOTE: the default input isn't yet set to this logical file, you must use reset_read_channel() to do this,
        ;          if you're going to read from it yourself instead of using f_read()!

        %ir {{
            loadm.w r65535,diskio.f_open.filenameptr
            syscall 52 (r65535.w): r0.b
            returnr.b r0
        }}
    }

    sub f_read(uword bufferpointer, uword num_bytes) -> uword {
        ; -- read from the currently open file, up to the given number of bytes.
        ;    returns the actual number of bytes read.  (checks for End-of-file and error conditions)
        uword actual
        repeat num_bytes {
            %ir {{
                syscall 54 (): r0.w
                storem.w r0,$ff02
            }}
            if cx16.r0H==0
                return actual
            @(bufferpointer) = cx16.r0L
            bufferpointer++
            actual++
        }
        return actual
    }

    sub f_read_all(uword bufferpointer) -> uword {
        ; -- read the full contents of the file, returns number of bytes read.
        ;    It is assumed the file size is less than 64 K.
        uword actual
        repeat {
            %ir {{
                syscall 54 (): r0.w
                storem.w r0,$ff02
            }}
            if cx16.r0H==0
                return actual
            @(bufferpointer) = cx16.r0L
            bufferpointer++
            actual++
        }
    }

    sub f_readline(uword bufptr) -> ubyte {
        ; Routine to read text lines from a text file. Lines must be less than 255 characters.
        ; Reads characters from the input file UNTIL a newline or return character (or EOF).
        ; The line read will be 0-terminated in the buffer (and not contain the end of line character).
        ; The length of the line is returned. Note that an empty line is okay and is length 0!
        ; The success status is returned in the Carry flag instead: C set = success, C clear = failure/endoffile
        ubyte size
        repeat {
            %ir {{
                syscall 54 (): r0.w
                storem.w r0,$ff02
            }}

            if cx16.r0H==0 {
                sys.clear_carry()
                return size
            } else {
                if cx16.r0L == '\n' or cx16.r0L=='\r' {
                    @(bufptr) = 0
                    sys.set_carry()
                    return size
                }
                @(bufptr) = cx16.r0L
                bufptr++
                size++
                if_z {
                    @(bufptr) = 0
                    return 255
                }
            }
        }
    }

    sub f_close() {
        ; -- end an iterative file loading session (close channels).
        ;    it is safe to call this multiple times, or when no file is open for reading.
        %ir {{
            syscall 56 ()
        }}
    }


    ; ----- iterative file writing functions (uses write io channel) -----

    sub f_open_w(uword filenameptr) -> bool {
        ; -- open a file for iterative writing with f_write
        ;    WARNING: returns true if the open command was received by the device,
        ;    but this can still mean the file wasn't successfully opened for writing!
        ;    (for example, if it already exists). This is different than f_open()!
        ;    To be 100% sure if this call was successful, you have to use status()
        ;    and check the drive's status message!
        %ir {{
            loadm.w r65535,diskio.f_open_w.filenameptr
            syscall 53 (r65535.w): r0.b
            returnr.b r0
        }}
    }

    sub f_write(uword bufferpointer, uword num_bytes) -> bool {
        ; -- write the given number of bytes to the currently open file
        ;    you can call this multiple times to append more data
        repeat num_bytes {
            %ir {{
                loadm.w r0,diskio.f_write.bufferpointer
                loadi.b r1,r0
                syscall 55 (r1.b): r0.b
                storem.b r0,$ff02
            }}
            if cx16.r0L==0
                return false
            bufferpointer++
        }
        return true
    }

    sub f_close_w() {
        ; -- end an iterative file writing session (close channels).
        ;    it is safe to call this multiple times, or when no file is open for reading.
        %ir {{
            syscall 57 ()
        }}
    }


    ; ---- other functions ----

    sub chdir(str path) {
        ; -- change current directory.
        %ir {{
            loadm.w r65535,diskio.chdir.path
            syscall 50 (r65535.w)
        }}
    }

    sub mkdir(str name) {
        ; -- make a new subdirectory.
        %ir {{
            loadm.w r65535,diskio.mkdir.name
            syscall 49 (r65535.w)
        }}
    }

    sub rmdir(str name) {
        ; -- remove a subdirectory.
        %ir {{
            loadm.w r65535,diskio.rmdir.name
            syscall 51 (r65535.w)
        }}
    }

    sub curdir() -> uword {
        ; return current directory name or 0 if error
        %ir {{
            syscall 48 (): r0.w
            returnr.w r0
        }}
    }

    sub status() -> str {
        ; -- retrieve the disk drive's current status message
        return "unknown"
    }

    sub status_code() -> ubyte {
        ; -- return status code instead of whole CBM-DOS status string. (in this case always 255, which means 'unable to return sensible value')
        return 255
    }


    sub save(uword filenameptr, uword start_address, uword savesize) -> bool {
        %ir {{
            load.b r65532,0
            loadm.w r65533,diskio.save.filenameptr
            loadm.w r65534,diskio.save.start_address
            loadm.w r65535,diskio.save.savesize
            syscall 42 (r65532.b, r65533.w, r65534.w, r65535.w): r0.b
            returnr.b r0
        }}
    }

    ; like save() but omits the 2 byte prg header.
    sub save_raw(uword filenameptr, uword startaddress, uword savesize) -> bool {
        %ir {{
            load.b r65532,1
            loadm.w r65533,diskio.save.filenameptr
            loadm.w r65534,diskio.save.start_address
            loadm.w r65535,diskio.save.savesize
            syscall 42 (r65532.b, r65533.w, r65534.w, r65535.w): r0.b
            returnr.b r0
        }}
    }

    ; Use kernal LOAD routine to load the given program file in memory.
    ; This is similar to Basic's  LOAD "filename",drive  /  LOAD "filename",drive,1
    ; If you don't give an address_override, the location in memory is taken from the 2-byte file header.
    ; If you specify a custom address_override, the first 2 bytes in the file are ignored
    ; and the rest is loaded at the given location in memory.
    ; Returns the end load address+1 if successful or 0 if a load error occurred.
    sub load(uword filenameptr, uword address_override) -> uword {
        %ir {{
            loadm.w r65534,diskio.load.filenameptr
            loadm.w r65535,diskio.load.address_override
            syscall 40 (r65534.w, r65535.w): r0.w
            returnr.w r0
        }}
    }

    ; Identical to load(), but DOES INCLUDE the first 2 bytes in the file.
    ; No program header is assumed in the file. Everything is loaded.
    ; See comments on load() for more details.
    sub load_raw(uword filenameptr, uword start_address) -> uword {
        %ir {{
            loadm.w r65534,diskio.load_raw.filenameptr
            loadm.w r65535,diskio.load_raw.start_address
            syscall 41 (r65534.w, r65535.w): r0.w
            returnr.w r0
        }}
    }

    sub delete(uword filenameptr) {
        ; -- delete a file on the drive
        %ir {{
            loadm.w r65535,diskio.delete.filenameptr
            syscall 43 (r65535.w)
        }}
    }

    sub rename(uword oldfileptr, uword newfileptr) {
        ; -- rename a file on the drive
        %ir {{
            loadm.w r65534,diskio.rename.oldfileptr
            loadm.w r65535,diskio.rename.newfileptr
            syscall 44 (r65534.w, r65535.w)
        }}
    }

    sub exists(str filename) -> bool {
        ; -- returns true if the given file exists on the disk, otherwise false
        if f_open(filename) {
            f_close()
            return true
        }
        return false
    }

}
