/// \file
/// a bunch of OS-dependent functions
#ifndef OS_H_
#define OS_H_

#include "base.h"

typedef enum {
	FS_NON_EXISTENT,
	FS_FILE,
	FS_DIRECTORY,
	FS_OTHER
} FsType;

enum {
	FS_PERMISSION_READ = 0x01,
	FS_PERMISSION_WRITE = 0x02,
};
typedef u8 FsPermission;

typedef struct {
	FsType type;
	char name[];
} FsDirectoryEntry;

/// returns what kind of thing this is.
FsType fs_path_type(const char *path);
FsPermission fs_path_permission(const char *path);
/// Does this file exist? Returns false for directories.
bool fs_file_exists(const char *path);
/// Returns a NULL-terminated array of the files/directories in this directory, or NULL if the directory does not exist/out of memory.
/// When you're done with the entries, call fs_dir_entries_free (or call free on each entry, then on the whole array).
/// NOTE: The files/directories aren't returned in any particular order!
FsDirectoryEntry **fs_list_directory(const char *dirname);
/// Create the directory specified by `path`\n
/// Returns:\n
/// 1  if the directory was created successfully\n
/// 0  if the directory already exists\n
/// -1 if the path already exists, but it's not a directory, or if there's another error (e.g. don't have permission to create directory).\n
int fs_mkdir(const char *path);
// Puts the current working directory into buf, including a null-terminator, writing at most buflen bytes.\n
// Returns:\n
// 1  if the working directory was inserted into buf successfully\n
// 0  if buf is too short to hold the cwd\n
// -1 if we can't get the cwd for whatever reason.\n
int os_get_cwd(char *buf, size_t buflen);
// Unlike ISO C rename() function, this will overwrite `newname` if it exists.\n
// Returns:\n
// >= 0 if successful\n
// < 0 on error\n
int os_rename_overwrite(const char *oldname, const char *newname);
struct timespec time_last_modified(const char *filename);
struct timespec time_get(void);
/// sleep for a certain number of nanoseconds
void time_sleep_ns(u64 ns);


/// free the entries generated by fs_list_directory.
static void fs_dir_entries_free(FsDirectoryEntry **entries) {
	for (int i = 0; entries[i]; ++i)
		free(entries[i]);
	free(entries);
}

/// get current time in seconds since some arbitrary point
static double time_get_seconds(void) {
	struct timespec t = time_get();
	return (double)t.tv_sec
		+ (double)t.tv_nsec * 1e-9;
}



/// sleep for microseconds
static void time_sleep_us(u64 us) {
	time_sleep_ns(us * 1000);
}

/// sleep for milliseconds
static void time_sleep_ms(u64 ms) {
	time_sleep_ns(ms * 1000000);
}

/// sleep for seconds
static void time_sleep_s(u64 s) {
	time_sleep_ns(s * 1000000000);
}

/// a process
typedef struct Process Process;

/// zero everything except what you're using
typedef struct {
	bool separate_stderr;
	const char *working_directory;
	/// for forwards compatibility
	char _reserved[256];
} ProcessSettings;

typedef struct {
	/// string like "exited with code 9"
	char message[62];
	/// it might be possible that both `signalled` and `exited` are false,
	/// if something weird happens.
	bool signalled;
	bool exited;
	/// only relevant if `exited = true`
	int exit_code;
	/// only relevant if `signalled = true`
	int signal;
} ProcessExitInfo;

/// get process ID of this process
int process_get_id(void);
/// execute the given command (like if it was passed to `system()`), creating a new \ref Process object.
///
/// returns a valid process object on failure, but it will have an error, according to \ref process_geterr
Process *process_run_ex(const char *command, const ProcessSettings *settings);
/// like \ref process_run_ex, but with the default settings
Process *process_run(const char *command);
/// returns the error last error produced, or NULL if there was no error.
const char *process_geterr(Process *process);
/// write to stdin
///
/// returns -2 on error,
/// or a non-negative number indicating the number of bytes written.
/// Currently, this does a blocking write.
long long process_write(Process *process, const char *data, size_t size);
/// read from stdout+stderr.
///
/// returns:\n
/// -2 on error\n
/// -1 if no data is available right now\n
/// 0 on end of file\n
/// or a positive number indicating the number of bytes read to data (at most size)\n
/// This does a nonblocking read.
long long process_read(Process *process, char *data, size_t size);
/// like \ref process_read, but reads stderr.
///
/// this function ALWAYS RETURNS -2 if `separate_stderr` is not specified in the \ref ProcessSettings.
///   if `separate_stderr` is false, then both stdout and stderr will be sent via \ref process_read.
long long process_read_stderr(Process *process, char *data, size_t size);
/// Checks if the process has exited.
///
/// Returns:\n
/// -1 if the process returned a non-zero exit code, or got a signal.\n
/// 1  if the process exited successfully\n
/// 0  if the process hasn't exited.\n
/// If the process has exited, `*info` will be filled out with details.
/// if the process is no longer running, `*process` will be freed and set to NULL.
int process_check_status(Process **process, ProcessExitInfo *info);
/// kills process if still running
///
/// this also frees any resources used by `*process`.
/// `*process` will be set to NULL.
void process_kill(Process **process);

/// runs xdg-open or equivalent on the given path, which can be a URL.
///
/// returns `true` on success.
bool open_with_default_application(const char *path);

#endif // OS_H_