/**
 * @file system.h
 * @author Jennifer Taylor <dragonminded@dragonminded.com>
 * @brief newlib Interface Hooks
 * @ingroup system
 */
#ifndef __LIBDRAGON_SYSTEM_H
#define __LIBDRAGON_SYSTEM_H

/** 
 * @defgroup system newlib Interface Hooks
 * @brief System hooks to provide low level threading and filesystem functionality to newlib.
 *
 * newlib provides all of the standard C libraries for homebrew development.
 * In addition to standard C libraries, newlib provides some additional bridging
 * functionality to allow POSIX function calls to be tied into libdragon.
 * Currently this is used only for filesystems.  The newlib interface hooks here
 * are mostly stubs that allow homebrew applications to compile.
 *
 * The sbrk function is responsible for allowing newlib to find the next chunk
 * of free space for use with malloc calls. The size of the available heap is
 * computed using the memory size computed by the boot code (IPL3), and available
 * via #get_memory_size(), which is normally either 4 MiB or 8 MiB if the expansion
 * pak is available.
 *
 * libdragon has defined a custom callback structure for filesystems to use.
 * Providing relevant hooks for calls that your filesystem supports and passing
 * the resulting structure to #attach_filesystem will hook your filesystem into
 * newlib.  Calls to POSIX file operations will be passed on to your filesystem
 * code if the file prefix matches, allowing code to make use of your filesystyem
 * without being rewritten.
 *
 * For example, your filesystem provides libdragon an interface to access a 
 * homebrew SD card interface.  You register a filesystem with "sd:/" as the prefix
 * and then attempt to open "sd://directory/file.txt".  The open callback for your
 * filesystem will be passed the file "/directory/file.txt".  The file handle returned
 * will be passed into all subsequent calls to your filesystem until the file is
 * closed.
 * @{
 */

#include <dir.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <sys/time.h>

/** @brief Number of filesystems that can be attached to the system */
#define MAX_FILESYSTEMS     10
/** @brief Number of open handles that can be maintained at one time */
#define MAX_OPEN_HANDLES    4096

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief Filesystem hook structure
 *
 * Filesystems that do not support one of more of the following methods
 * should provide null pointers instead of empty functions.  The newlib
 * hooks will set errno to ENOSYS and return a proper error to userspace.
 * 
 * All filesystems functions must set errno in case of error, to report
 * the proper error to userspace.
 */
typedef struct
{
    /** 
     * @brief True if the filesystem is thread safe
     * 
     * This flag is used to determine if the filesystem can be accessed
     * concurrently by multiple threads, through *different* file handles.
     * 
     * If the filesystem is not thread safe, system code will protect
     * all accesses to the filesystem with a mutex. This guarantees that if
     * eg. a thread is suspended while reading a file, another thread will
     * not be able to read or write any other file until the first thread
     * resumes.
     * 
     * On the other hand, if the filesystem is thread safe, the system code
     * will not use a mutex to protect accesses to the filesystem. This means
     * that the filesystem code must be able to handle concurrent accesses
     * to different files without any protection.
     * 
     * In general, read-only filesystems are easily thread safe: only pay
     * attention to some shared mutable state like eg some global cache.
     * On the other hand, read-write filesystems are usually not thread safe
     * because of shared mutable structures like directories or inode tables.
     * 
     * Notice that concurrent accesses to the same file handle are always
     * meant to be unsafe and must be protected by the user code itself.
     */
    bool thread_safe;

    /** 
     * @brief Function to call when performing an open command
     *
     * @param[in] name
     *            Full path of the file to be opened, relative to the root
     *            of the filesystem.
     * @param[in] flags
     *            Open flags, such as binary, append, etc.  Follows POSIX flags.
     *
     * @return A pointer to an arbitrary file handle assigned by the filesystem code 
     *         or NULL on error (and errno is set).
     */
    void *(*open)( char *name, int flags );
    /** 
     * @brief Function to call when performing a fstat command
     *
     * @param[in]  file
     *             Arbitrary file handle returned by #filesystem_t::open
     * @param[out] st
     *             Stat structure to populate with file statistics
     *
     * @return 0 on success or a negative value on error (and errno is set).
     */
    int (*fstat)( void *file, struct stat *st );
    /** 
     * @brief Function to call when performing a stat command
     *
     * @param[in]  file
     *             Full path of the file to be examined, relative to the root
     *             of the filesystem.
     * @param[out] st
     *             Stat structure to populate with file statistics
     *
     * @return 0 on success or a negative value on error (and errno is set).
     */
    int (*stat)( char *name, struct stat *st );
    /** 
     * @brief Function to call when performing an lseek command
     *
     * @param[in] file
     *            Arbitrary file handle returned by #filesystem_t::open
     * @param[in] ptr
     *            An offset from dir to seek.
     * @param[in] dir
     *            A direction to see, either SEEK_SET, SEEK_CUR or SEEK_END
     *
     * @return The absolute offset in bytes after the seek or a negative value on failure
     *         (and errno is set).
     */
    int (*lseek)( void *file, int ptr, int dir );
    /** 
     * @brief Function to call when performing a read operation
     *
     * @param[in]  file
     *             Arbitrary file handle returned by #filesystem_t::open
     * @param[out] ptr
     *             Buffer to place data read into
     * @param[in]  len
     *             Length of data that should be read into ptr
     *
     * @return The actual number of bytes read into ptr or a negative value on failure
     *         (and errno is set).
     */
    int (*read)( void *file, uint8_t *ptr, int len );
    /** @brief Function to call when performing a write operation
     *
     * @param[in]  file
     *             Arbitrary file handle returned by #filesystem_t::open
     * @param[out] ptr
     *             Buffer to grab the data to be written
     * @param[in]  len
     *             Length of data that should be written out of ptr
     *
     * @return The actual number of bytes written or a negative value on failure
     *          (and errno is set).
     */
    int (*write)( void *file, uint8_t *ptr, int len );
    /** 
     * @brief Function to call when performing a close operation
     *
     * @param[in] file
     *            Arbitrary file handle returned by #filesystem_t::open
     *
     * @return 0 on success or a negative value on failure (and errno is set).
     */
    int (*close)( void *file );
    /** 
     * @brief Function to call when performing an unlink operation
     *
     * @param[in] name
     *            Full path of the file to be opened, relative to the root
     *            of the filesystem.
     *
     * @return 0 on success or a negative value on failure (and errno is set).
     */
    int (*unlink)( char *name );
    /** 
     * @brief Function to call when performing a findfirst operation
     *
     * @param[in]  path
     *             Full path of the directory to list files from, relative to the
     *             root of the filesystem. It always begins with a slash.
     * @param[out] dir
     *             Directory structure to place information on the first file in the
     *             directory.
     *
     * @return 0 on successful lookup, -1 if the directory existed and is empty,
     *         or a different negative value on error (in which case, errno will be set).
     */
    int (*findfirst)( char *path, dir_t *dir );

    ///@cond
    #ifndef SYSTEM_NO_DEPRECATED
    __attribute__((deprecated("Use findnext2 instead")))
    #endif
    int (*findnext)( dir_t *dir );
    ///@endcond
    
    /** 
     * @brief Function to call when performing a findnext operation
     *
     * @param[in]  path
     *             Full path of the directory to list files from, relative to the
     *             root of the filesystem. It always begins with a slash.
     * @param[out] dir
     *             Directory structure to place information on the next file in the
     *             directory.
     *
     * @return 0 on successful lookup, -1 if the directory existed and is empty,
     *         or a different negative value on error (in which case, errno will be set).
     */
    int (*findnext2)( const char *path, dir_t *dir );
    /**
     * @brief Truncate a file to a specified length
     * 
     * @param file    Arbitrary file handle returned by #filesystem_t::open
     * @param length  New length of the file
     * 
     * @return 0 on success or a negative value on failure (and errno is set)
     */
    int (*ftruncate)( void *file, int length );
    /**
     * @brief Create a directory
     * 
     * @param[in] path
     *            Full path of the directory to create, relative to the root of the filesystem.
     * @param[in] mode
     *            Directory permissions
     * 
     * @return 0 on success or a negative value on failure (errno must be set)
     */
    int (*mkdir)( char *path, mode_t mode );
    /**
     * @brief Perform IO Control Request
     *
     * @param[in] file
     *            File handle
     * @param[in] cmd
     *            Request ioctl command code 
     * @param[in] argp
     *            Pointer to a request-specific data structure
     *
     * @return 0 on success or a negative value on failure (errno must be set)
     */
    int (*ioctl)(void *file, unsigned long cmd, void *argp);
    /**
     * @brief Change the access and modification times of a file
     *
     * @param[in] path
     *            Full path of the file to modify
     * @param[in] times
     *            Pointer to a timeval structure containing the new access and
     *            modification times. This must not be NULL.
     *
     * @return 0 on success or a negative value on failure (errno must be set)
     */
    int (*utimes)(const char *path, const struct timeval times[2]);
} filesystem_t;

/**
 * @brief Standard I/O hook structure
 *
 * This structure provides optional callback hooks for code wishing to
 * respond to reads from STDIN or writes to STDOUT or STDERR.  Any function
 * that code does not wish to handle should be left as a null pointer.
 */
typedef struct
{
    /** 
     * @brief Function to call when performing a STDIN read
     *
     * @param[out] data
     *             Pointer to data buffer to place the read data
     * @param[in]  len
     *             Length of data in bytes expected to be read
     * 
     * @return Actual number of bytes read into data, not to exceed the original length.
     */
    int (*stdin_read)( char *data, unsigned int len );
    /** 
     * @brief Function to call when performing a STDOUT write
     *
     * @param[in] data
     *            Pointer to data buffer containing the data to write
     * @param[in] len
     *            Length of data in bytes expected to be written
     *
     * @return Actual number of bytes written from data, not to exceed the original length.
     */
    int (*stdout_write)( char *data, unsigned int len );
    /** 
     * @brief Function to call when performing a STDERR write
     *
     * @param[in] data
     *            Pointer to data buffer containing the data to write
     * @param[in] len
     *            Length of data in bytes expected to be written
     *
     * @return Actual number of bytes written from data, not to exceed the original length.
     */
    int (*stderr_write)( char *data, unsigned int len );
} stdio_t;

/**
 * @brief Real-time clock hook structure
 *
 * This structure provides optional callback hooks for code wishing to
 * implement C time functions.  Any function that code does not wish to handle
 * should be left as a NULL pointer.
 */
typedef struct
{
    /** 
     * @brief Function to call to retrieve the current date/time
     * 
     * @param[out] out pointer to time_t (number of seconds since 1970-01-01 00:00:00 UTC)
     *
     * @retval RTC_ESUCCESS if the operation was successful
     * @retval RTC_ENOCLOCK if the RTC is not available
     * @retval RTC_EBADCLOCK if the RTC is not operational
     * @retval RTC_EBADTIME if the RTC clock time is not representable
     */
    int (*gettime)( time_t * );
    /** 
     * @brief Function to call to set the current date/time
     * 
     * @param time number of seconds since 1970-01-01 00:00:00 UTC
     * 
     * @retval RTC_ESUCCESS if the operation was successful
     * @retval RTC_ENOCLOCK if the RTC is not available
     * @retval RTC_EBADCLOCK if the RTC is not operational
     * @retval RTC_EBADTIME if the RTC cannot represent the new time
     */
    int (*settime)( time_t );
} rtc_hooks_t;

/**
 * @brief Register a filesystem with newlib
 *
 * @param[in] prefix
 *            The prefix that will be used to register the filesystem
 * @param[in] filesystem
 *            Pointer to structure containing filesystem callbacks
 *
 * @retval -1 if the parameters are invalid
 * @retval -2 if the prefix is already in use
 * @retval -3 if there are no more slots for filesystems
 * @retval 0 if the filesystem was registered successfully
 */
int attach_filesystem( const char * const prefix, filesystem_t *filesystem );

/**
 * @brief Unregister a filesystem from newlib
 *
 * @note This function will make sure all files are closed before unregistering
 *       the filesystem.
 *
 * @param[in] prefix
 *            The prefix that was used to register the filesystem
 *
 * @retval -1 if the parameters were invalid
 * @retval -2 if the filesystem couldn't be found
 * @retval 0 if the filesystem was successfully unregistered
 */
int detach_filesystem( const char * const prefix );


/**
 * @brief Hook into stdio for STDIN, STDOUT and STDERR callbacks
 *
 * @param[in] stdio_calls
 *            Pointer to structure containing callbacks for stdio functions
 *
 * @return 0 on successful hook or a negative value on failure.
 */
int hook_stdio_calls( stdio_t *stdio_calls );

/**
 * @brief Unhook from stdio
 *
 * @param[in] stdio_calls
 *            Pointer to structure containing callbacks for stdio functions
 *
 * @return 0 on successful hook or a negative value on failure.
 */
int unhook_stdio_calls( stdio_t *stdio_calls );

/**
 * @brief Hook into POSIX time callbacks
 *
 * @param[in] hooks Pointer to real-time clock callbacks structure
 *
 * @return 0 on a successful hook or a negative value on failure
 */
int hook_rtc_calls( rtc_hooks_t *hooks );

/**
 * @brief Unhook from POSIX time callbacks
 *
 * @param[in] hooks Pointer to real-time clock callbacks structure
 *
 * @return 0 on a successful unhook or a negative value on failure
 */
int unhook_rtc_calls( rtc_hooks_t *hooks );

/**************************************
 *  DEPRECATED FUNCTIONS
 **************************************/

/// @cond

/**
 * @brief Time hook structure
 * @deprecated Use rtc_hooks_t instead
 *
 * This structure provides optional callback hooks for code wishing to
 * implement C time functions.  Any function that code does not wish to handle
 * should be left as a NULL pointer.
 */
typedef struct
{
    /** 
     * @brief Function to call to retrieve the current date/time
     * 
     * @return number of seconds since 1970-01-01 00:00:00 UTC
     */
    time_t (*gettime)( void );
    /** 
     * @brief Function to call to set the current date/time
     * 
     * @param time number of seconds since 1970-01-01 00:00:00 UTC
     * 
     * @return whether the time was set successfully
     */
    bool (*settime)( time_t );
} time_hooks_t;

#ifndef SYSTEM_NO_DEPRECATED
__attribute__((deprecated("use hook_rtc_calls instead")))
#endif
int hook_time_calls( time_hooks_t *hooks );

#ifndef SYSTEM_NO_DEPRECATED
__attribute__((deprecated("use unhook_rtc_calls instead")))
#endif
int unhook_time_calls( time_hooks_t *hooks );

#ifndef SYSTEM_NO_DEPRECATED
__attribute__((deprecated("use hook_time_calls instead")))
#endif
int hook_time_call( time_t (*time_fn)( void ) );

#ifndef SYSTEM_NO_DEPRECATED
__attribute__((deprecated("use unhook_time_calls instead")))
#endif
int unhook_time_call( time_t (*time_fn)( void ) );

/// @endcond

#ifdef __cplusplus
}
#endif

/** @} */

#endif
