#ifndef _file_h_INCLUDED
#define _file_h_INCLUDED

#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>

#include "attribute.h"
#include "keatures.h"

bool kissat_file_exists (const char *path);
bool kissat_file_readable (const char *path);
bool kissat_file_writable (const char *path);
size_t kissat_file_size (const char *path);
bool kissat_find_executable (const char *name);

typedef struct file file;

struct file {
  FILE *file;
  bool close;
  bool reading;
  bool compressed;
  const char *path;
  uint64_t bytes;
};

void kissat_read_already_open_file (file *, FILE *, const char *path);
void kissat_write_already_open_file (file *, FILE *, const char *path);

bool kissat_open_to_read_file (file *, const char *path);
bool kissat_open_to_write_file (file *, const char *path);
bool kissat_open_to_append_file (file *, const char *path);

void kissat_close_file (file *);

#ifndef KISSAT_HAS_COMPRESSION

bool kissat_looks_like_a_compressed_file (const char *path);

#endif

// clang-format off

static inline size_t
kissat_read (file *, void *, size_t) ATTRIBUTE_ALWAYS_INLINE;

static inline size_t
kissat_write (file *, void *, size_t) ATTRIBUTE_ALWAYS_INLINE;

static inline int kissat_getc (file *) ATTRIBUTE_ALWAYS_INLINE;

inline int kissat_putc (file *, int) ATTRIBUTE_ALWAYS_INLINE;

static inline void kissat_flush (file *) ATTRIBUTE_ALWAYS_INLINE;

// clang-format on

static inline size_t kissat_read (file *file, void *ptr, size_t bytes) {
  assert (file);
  assert (file->file);
  assert (file->reading);
#ifdef KISSAT_HAS_UNLOCKEDIO
  size_t res = fread_unlocked (ptr, 1, bytes, file->file);
#else
  size_t res = fread (ptr, 1, bytes, file->file);
#endif
  file->bytes += res;
  return res;
}

static inline size_t kissat_write (file *file, void *ptr, size_t bytes) {
  assert (file);
  assert (file->file);
  assert (!file->reading);
#ifdef KISSAT_HAS_UNLOCKEDIO
  size_t res = fwrite_unlocked (ptr, 1, bytes, file->file);
#else
  size_t res = fwrite (ptr, 1, bytes, file->file);
#endif
  file->bytes += res;
  return res;
}

static inline int kissat_getc (file *file) {
  assert (file);
  assert (file->file);
  assert (file->reading);
#ifdef KISSAT_HAS_UNLOCKEDIO
  int res = getc_unlocked (file->file);
#else
  int res = getc (file->file);
#endif
  if (res != EOF)
    file->bytes++;
  return res;
}

inline int kissat_putc (file *file, int ch) {
  assert (file);
  assert (file->file);
  assert (!file->reading);
#ifdef KISSAT_HAS_UNLOCKEDIO
  int res = putc_unlocked (ch, file->file);
#else
  int res = putc (ch, file->file);
#endif
  if (res != EOF)
    file->bytes++;
  return ch;
}

static inline void kissat_flush (file *file) {
  assert (file);
  assert (file->file);
  assert (!file->reading);
#ifdef KISSAT_HAS_UNLOCKEDIO
  fflush_unlocked (file->file);
#else
  fflush (file->file);
#endif
}

#endif
