/**
 * @file source.h
 *
 * An opaque type representing the source code being parsed, regardless of
 * origin (constant memory, file, memory-mapped file, or stream).
 */
#ifndef PRISM_SOURCE_H
#define PRISM_SOURCE_H

#include "prism/compiler/exported.h"
#include "prism/compiler/filesystem.h"
#include "prism/compiler/nodiscard.h"
#include "prism/compiler/nonnull.h"

#include <stddef.h>
#include <stdint.h>

/**
 * An opaque type representing source code being parsed.
 */
typedef struct pm_source_t pm_source_t;

/**
 * This function is used to retrieve a line of input from a stream. It closely
 * mirrors that of fgets so that fgets can be used as the default
 * implementation.
 *
 * As with fgets, implementations MUST write at most `size` bytes into
 * `string`, including the terminating NUL byte (i.e. at most `size - 1` bytes
 * of data followed by a '\0'). The caller relies on this bound for memory
 * safety, so an implementation that reads from a source which may return more
 * data than requested (for example a multi-byte-aware `gets`) is responsible
 * for clamping the amount it copies. Returns `string`, or NULL on EOF.
 */
typedef char * (pm_source_stream_fgets_t)(char *string, int size, void *stream);

/**
 * This function is used to check whether a stream is at EOF. It closely mirrors
 * that of feof so that feof can be used as the default implementation.
 */
typedef int (pm_source_stream_feof_t)(void *stream);

/**
 * Represents the result of initializing a source from a file.
 */
typedef enum {
    /** Indicates that the source was successfully initialized. */
    PM_SOURCE_INIT_SUCCESS = 0,

    /**
     * Indicates a generic error from a source init function, where the type
     * of error should be read from `errno` or `GetLastError()`.
     */
    PM_SOURCE_INIT_ERROR_GENERIC = 1,

    /**
     * Indicates that the file that was attempted to be opened was a directory.
     */
    PM_SOURCE_INIT_ERROR_DIRECTORY = 2,

    /**
     * Indicates that the file is not a regular file (e.g. a pipe or character
     * device) and the caller should handle reading it.
     */
    PM_SOURCE_INIT_ERROR_NON_REGULAR = 3
} pm_source_init_result_t;

/**
 * Create a new source that wraps existing constant memory. The memory is not
 * owned and will not be freed.
 *
 * @param data The pointer to the source data.
 * @param length The length of the source data in bytes.
 * @returns A new source. Aborts on allocation failure.
 */
PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_source_t * pm_source_constant_new(const uint8_t *data, size_t length);

/**
 * Create a new source that wraps existing shared memory. The memory is not
 * owned and will not be freed. Semantically a "slice" of another source.
 *
 * @param data The pointer to the source data.
 * @param length The length of the source data in bytes.
 * @returns A new source. Aborts on allocation failure.
 */
PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_source_t * pm_source_shared_new(const uint8_t *data, size_t length);

/**
 * Create a new source that owns its memory. The memory will be freed with
 * xfree when the source is freed.
 *
 * @param data The pointer to the heap-allocated source data.
 * @param length The length of the source data in bytes.
 * @returns A new source. Aborts on allocation failure.
 */
PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_source_t * pm_source_owned_new(uint8_t *data, size_t length);

/**
 * Create a new source by reading a file into a heap-allocated buffer.
 *
 * @param filepath The path to the file to read.
 * @param result Out parameter for the result of the initialization.
 * @returns A new source, or NULL on error (with result written to out param).
 */
PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_source_t * pm_source_file_new(const char *filepath, pm_source_init_result_t *result) PRISM_NONNULL(1, 2);

/**
 * Create a new source by memory-mapping a file. Falls back to file reading on
 * platforms without mmap support.
 *
 * If the file is a non-regular file (e.g. a pipe or character device),
 * PM_SOURCE_INIT_ERROR_NON_REGULAR is returned, allowing the caller to handle
 * it appropriately (e.g. by reading it through their own I/O layer).
 *
 * @param filepath The path to the file to read.
 * @param open_flags Additional flags to pass to open(2) (e.g. O_NONBLOCK).
 * @param result Out parameter for the result of the initialization.
 * @returns A new source, or NULL on error (with result written to out param).
 */
PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_source_t * pm_source_mapped_new(const char *filepath, int open_flags, pm_source_init_result_t *result) PRISM_NONNULL(1, 3);

/**
 * Create a new source by reading from a stream using the provided callbacks.
 *
 * @param stream The stream to read from.
 * @param fgets The function to use to read from the stream.
 * @param feof The function to use to check if the stream is at EOF.
 * @returns A new source. Aborts on allocation failure.
 */
PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_source_t * pm_source_stream_new(void *stream, pm_source_stream_fgets_t *fgets, pm_source_stream_feof_t *feof);

/**
 * Free the given source and any memory it owns.
 *
 * @param source The source to free.
 */
PRISM_EXPORTED_FUNCTION void pm_source_free(pm_source_t *source) PRISM_NONNULL(1);

/**
 * Returns the length of the source data in bytes.
 *
 * @param source The source to get the length of.
 * @returns The length of the source data.
 */
PRISM_EXPORTED_FUNCTION size_t pm_source_length(const pm_source_t *source) PRISM_NONNULL(1);

/**
 * Returns a pointer to the source data.
 *
 * @param source The source to get the data of.
 * @returns A pointer to the source data.
 */
PRISM_EXPORTED_FUNCTION const uint8_t * pm_source_source(const pm_source_t *source) PRISM_NONNULL(1);

#endif
