/* SPDX-FileCopyrightText: 2024 Google LLC */
/* SPDX-License-Identifier: Apache-2.0 */

#pragma once

#include <inttypes.h>
#include <stddef.h>

#include "kernel/pebble_tasks.h"
#include "system/status_codes.h"
#include "util/list.h"

//! Exported APIs for the Pebble File System (PFS)
//!
//! Things to note:
//!  - All APIs are threadsafe
//!  - PFS implements a basic wear-leveling strategy to extend the life of
//!    the flash part
//!  - PFS allows the allocation of blocks of space which appear to the consumer
//!    as a contiguous region. It is up to the consumer to manage how they
//!    want to manage the allocated space
//!  - Assumes underlying HW is a NOR flash chip. This means that when a
//!    0 bit value is written to a given location, the file needs to be erased
//!    or rewritten to change it back to a 1. (pfs_open (i.e OP_FLAG_OVERWRITE)
//!    provides a mechanism that consumers can leverage to accomplish this)
//!  - Erasing flash sectors is a costly operation (from both a time/power
//!    perspective). Care should be taken not to constantly be deleting/creating
//!    files

#define OP_FLAG_READ                  (1 << 0)
#define OP_FLAG_WRITE                 (1 << 1)
#define OP_FLAG_OVERWRITE             (1 << 2)
#define OP_FLAG_SKIP_HDR_CRC_CHECK    (1 << 3)
#define OP_FLAG_USE_PAGE_CACHE        (1 << 4)

#define FILE_TYPE_STATIC        (0xfe)
#define FILE_MAX_NAME_LEN       (255)

typedef enum {
  FSeekSet,
  FSeekCur
} FSeekType;

//! Used by pfs_watch_file to know which events to trigger callbacks on
#define FILE_CHANGED_EVENT_CLOSED   (1 << 0)
#define FILE_CHANGED_EVENT_REMOVED  (1 << 1)
#define FILE_CHANGED_EVENT_ALL      (FILE_CHANGED_EVENT_CLOSED | FILE_CHANGED_EVENT_REMOVED)

//! Types used by pfs_watch_file()
typedef void (*PFSFileChangedCallback)(void *data);
typedef void *PFSCallbackHandle;

//! Used by pfs_list_files() and pfs_remove_files()
typedef bool (*PFSFilenameTestCallback)(const char *name);

//! Format of each entry in the linked list returned by pfs_create_file_list
typedef struct {
  ListNode list_node;
  char name[];
} PFSFileListEntry;


//! @param name - The name of the file to be opened
//! @param op_flags - The operation to be performed on the file
//!
//!   OP_FLAG_READ - Open a file such that pfs_read operations will work. If the
//!    file does not exist, opening a file with just this mode set will fail
//!
//!   OP_FLAG_WRITE - Creates a file if it does not exist, else opens a file
//!    such that pfs_write operations will work. It is up to the user to seek to
//!    the desired offset within the file
//!
//!   OP_FLAG_OVERWRITE - Provides a safe mechanism to incrementally overwrite
//!    a file that already exists with new data. Open will fail if the file does
//!    not already exist on flash. The changes for the overwritten file are not
//!    committed until the pfs_close is called. Until this time, pfs_open of the
//!    'name' will return a hdl to the original file. This way there is always a
//!    valid version of the file which can be read & the caller can copy parts
//!    of the orginal file in hunks rather than allocating a lot of RAM.
//!
//!   OP_FLAG_SKIP_HDR_CRC_CHECK - For files which are not accessed frequently,
//!    it is a good idea to sanity check the on-flash header CRCs to make sure
//!    nothing has gone astray. This has performance ramifications if you are
//!    doing thousands of opens on the same file so this flags allows consumers
//!    to turn the check off
//!
//!   OP_FLAG_USE_PAGE_CACHE - Turns on caching for the translation from
//!    virtual filesystem pages to their physical address. For large files with
//!    a lot of random access this is advantageous because we need to read
//!    flash bytes to get to the correct page. Ideally this should only be
//!    used for read operations so that heap corruption does not lead to us
//!    corrupting a file
//!
//! The following two parameters are only parsed when a file is
//! overwritten/created:
//!
//! @param file_type  - The type of file being opened
//! @param start_size - The initial space to be allocated for a file if it is
//!                     being created
//! @return - status_t error code if the operation failed,
//!           else a fd handle >= 0 if operation was successful
extern int pfs_open(const char *name, uint8_t op_flags, uint8_t file_type,
    size_t start_size);

//! Writes data to the fd specified. After each write, the internal file offset
//! is moved forward
//! @param fd - The fd to write data to
//! @param buf - The buffer of data to write
//! @param size - The number of bytes from buf to write
//!               (must be <= the size of buf)
//! @return - the number of bytes written or a status_t code on error
extern int pfs_write(int fd, const void *buf, size_t size);

//! Reads data from the fd specified. After each read, the internal file offset
//! is moved forward
//! @param fd - the file to read data from
//! @param buf - the buffer to store read data in
//! @param size - the amount of data to read (must be <= the size of buf)
//! @return - the number of bytes read or a status_t code on error
extern int pfs_read(int fd, void *buf, size_t size);

//! Seeks to offset specified
//! Returns the offset forwarded to on success,
//! status_t code < 0 to indicate type of failure
extern int pfs_seek(int fd, int offset, FSeekType seek_type);

//! Frees up internal tracking data associated with a given file.
//! @param fd - the fd to close
//! @return - S_SUCCESS or appropriate error code on failure
extern status_t pfs_close(int fd);

//! calls pfs_close and pfs_remove on a file successively
//! @param fd - the fd describing the file to remove
extern status_t pfs_close_and_remove(int fd);

//! Unlinks a given file from the filesystem
//! @param name - the name of the file to remove
//! @return - S_SUCCESS or appropriate error code on failure
extern status_t pfs_remove(const char *name);

//! Returns the size of the file. (The amount of bytes that can be read out)
extern size_t pfs_get_file_size(int fd);

//! Should only be called before using FS
extern status_t pfs_init(bool run_filesystem_check);

//! Should only be called once after reboot and before any file operations
//! are performed
extern void pfs_reboot_cleanup(void);

//! erases everything on the filesystem & removes any open
//! file entries from the cache
//! Note: assumes that pfs_init was called before this
extern void pfs_format(bool write_erase_headers);

//! Returns the size of the pfs filesystem.
extern uint32_t pfs_get_size(void);

//! Updates the size of the pfs filesystem.
//! @param new_size new size
//! @param new_region_erased if the pages being added have been erased and should be marked as so.
extern void pfs_set_size(uint32_t new_size, bool new_region_erased);

//! Returns true is pfs is active on this device
extern bool pfs_active(void);

//! Returns true is pfs is active in the region
extern bool pfs_active_in_region(uint32_t start_address, uint32_t ending_address);

//! In the case of a file which can actually make use of additional space
//! beyond a certain minimum, this function will return the optimal size
//! that should be used for such a file, in order to use no more sectors
//! than the minimum size would.
extern int pfs_sector_optimal_size(int min_size, int namelen);

//! Returns the number of bytes available on the filesystem
extern uint32_t get_available_pfs_space(void);

//! Watch a file. The callback is called whenever the given file (by name) is closed with
//! modifications or deleted
//! @param filename - name of the file to watch
//! @param callback - function to invoke when file is changed
//! @param event_flags - which events the callback should be triggered on
//! (see FILE_CHANGED_EVENT_ flags defined above)
//! @param data - pointer passed to callback when invoked
//! @return - cb handle to pass into \ref pfs_unwatch_file
PFSCallbackHandle pfs_watch_file(const char* filename, PFSFileChangedCallback callback,
                                 uint8_t event_flags, void* data);

//! Stop watching a file.
void pfs_unwatch_file(PFSCallbackHandle cb_handle);

//! calculate the CRC32 for a given part of a file
extern uint32_t pfs_crc_calculate_file(int fd, uint32_t offset, uint32_t num_bytes);

//! Get a directory listing, calling the filter callback on each filename.
//! Returns linked list of filenames, filtered by the callback.
//! @param callback - name filter function to be called for each filename, or NULL to include
//!          all files.
//! @return - pointer to head node of linked list of names, or NULL if no names match
extern PFSFileListEntry *pfs_create_file_list(PFSFilenameTestCallback callback);

//! Delete a directory list returned by pfs_list_files
//! @param callback - callback to be called for on filename
//! @return - pointer to head node of linked list of names, or NULL if no names match
extern void pfs_delete_file_list(PFSFileListEntry *list);

//! Run each filename in the filesystem through the filter callback and delete all files that match
//! @param callback - callback to be called for on filename
extern void pfs_remove_files(PFSFilenameTestCallback callback);
