/**
 * @file disk.h
 * @brief The group F file channels: DOPEN, DCLOSE, PRINT#, INPUT#, GET#.
 *
 * A Commodore's disk verbs address a 1541 over the serial bus. There is no 1541,
 * so what these mean here is the nearest thing a filesystem offers, and the
 * verbs that mean nothing without the hardware are refused by name rather than
 * faked -- see TODO.md section 5.
 *
 * Channels are the interpreter's, not the host's: a program that opens a file
 * and is then stepped by a game's frame loop keeps its channel across frames,
 * and a `NEW` closes the lot.
 */

#ifndef _AKBASIC_DISK_H_
#define _AKBASIC_DISK_H_

#include <stdio.h>

#include <akerror.h>

#include <akbasic/types.h>

/** @brief How many files may be open at once. A C128 allows ten; this allows the same. */
#define AKBASIC_MAX_CHANNELS	10

/** @brief One open file channel. */
typedef struct
{
    FILE *fp;
    char name[AKBASIC_MAX_STRING_LENGTH];
    bool writing;
} akbasic_Channel;

/** @brief Every open channel. Indexed by the number a program writes after `#`. */
typedef struct
{
    akbasic_Channel channels[AKBASIC_MAX_CHANNELS];
} akbasic_DiskState;

struct akbasic_Runtime;

/**
 * @brief Mark every channel closed. Does not close anything; nothing is open yet.
 * @param obj Object to initialize, inspect, or modify.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_disk_state_init(akbasic_DiskState *obj);

/**
 * @brief Close every open channel.
 *
 * Called by `DCLOSE` with no argument and by `NEW`. A program that ends without
 * closing its files leaves them to this -- and to the host, which owns the
 * process and outlives the program.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_disk_close_all(akbasic_DiskState *obj);

/**
 * @brief Write one line to a channel, terminator included.
 *
 * What `PRINT #n, ...` reaches. A line rather than raw bytes, because that is
 * what `INPUT #n` reads back and the pair has to agree.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param number The channel number.
 * @param text What to write.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` or `text` is NULL.
 * @throws AKBASIC_ERR_BOUNDS When the channel number is out of range.
 * @throws AKBASIC_ERR_STATE When the channel is closed, or was opened for reading.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_disk_write(struct akbasic_Runtime *obj, int64_t number, const char *text);

/**
 * @brief Read one line from a channel, without its terminator.
 *
 * What `INPUT #n, VAR` reaches. End of file is reported through @p eof rather
 * than raised, the same way the sink's `readline` reports it: running out of
 * input is a state a program handles, not a failure.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param number The channel number.
 * @param dest Output destination populated by the function.
 * @param len Capacity of @p dest.
 * @param eof Output destination populated by the function; true at end of file.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When any pointer argument is NULL.
 * @throws AKBASIC_ERR_BOUNDS When the channel number is out of range.
 * @throws AKBASIC_ERR_STATE When the channel is closed, or was opened for writing.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_disk_readline(struct akbasic_Runtime *obj, int64_t number, char *dest, size_t len, bool *eof);

#endif // _AKBASIC_DISK_H_
