// vim:fileencoding=utf-8:foldmethod=marker

#include "file.h"
#include "../cpath/cpath.h"
#include "../../common/assert/assert.h"
#include "../../common/aliases/aliases.h"
#include "../../base/array/array.h"
#include "../../base/strings/str8/str8.h"
#include <stdio.h>

File *wapp_file_open(Str8RO *filepath, FileAccessMode mode) {
  wapp_persist const char *modes[FILE_ACCESS_MODE_COUNT] = {
    [WAPP_FA_MODE_R]      = "r",
    [WAPP_FA_MODE_W]      = "w",
    [WAPP_FA_MODE_A]      = "a",
    [WAPP_FA_MODE_R_EX]   = "r+",
    [WAPP_FA_MODE_W_EX]   = "w+",
    [WAPP_FA_MODE_A_EX]   = "a+",
    [WAPP_FA_MODE_RB]     = "rb",
    [WAPP_FA_MODE_WB]     = "wb",
    [WAPP_FA_MODE_AB]     = "ab",
    [WAPP_FA_MODE_RB_EX]  = "rb+",
    [WAPP_FA_MODE_WB_EX]  = "wb+",
    [WAPP_FA_MODE_AB_EX]  = "ab+",
    [WAPP_FA_MODE_WX]     = "wx",
    [WAPP_FA_MODE_WX_EX]  = "wx+",
    [WAPP_FA_MODE_WBX]    = "wbx",
    [WAPP_FA_MODE_WBX_EX] = "wbx+",
  };
  wapp_persist c8 tmp[WAPP_PATH_MAX] = {0};
  wapp_debug_assert(filepath->size < WAPP_PATH_MAX, "`filepath` exceeds max path limit.");

  memset(tmp, 0, WAPP_PATH_MAX);
  memcpy(tmp, filepath->buf, filepath->size);

  return fopen((const char *)tmp, modes[mode]);
}

u64 wapp_file_get_current_position(File *file) {
  wapp_debug_assert(file != NULL, "`file` should not be NULL.");
  return (u64)ftell(file);
}

i32 wapp_file_seek(File *file, u64 offset, FileSeekOrigin origin) {
  wapp_debug_assert(file != NULL, "`file` should not be NULL.");
  // TODO (Abdelrahman): Revisit conversion to long
  return fseek(file, (long)offset, origin);
}

u64 wapp_file_get_length(File *file) {
  wapp_debug_assert(file != NULL, "`file` should not be NULL.");

  u64 current = wapp_file_get_current_position(file);

  wapp_file_seek(file, 0, WAPP_SEEK_END);

  u64 output = ftell(file);

  // Restore position
  wapp_file_seek(file, current, WAPP_SEEK_START);

  return output;
}

u64 wapp_file_read(GenericArray *dst_buf, File *file, u64 item_count) {
  wapp_debug_assert(dst_buf != NULL && file != NULL,
                    "`dst_buf` and `file` should not be NULL.");

  u64 file_length       = wapp_file_get_length(file);
  u64 item_size         = dst_buf->item_size;
  u64 dst_byte_capacity = dst_buf->capacity * item_size;
  u64 req_byte_count    = item_count * item_size;
  u64 copy_byte_count   = 0;

  if (req_byte_count <= file_length && req_byte_count <= dst_byte_capacity) {
    copy_byte_count = req_byte_count;
  } else {
    copy_byte_count = file_length <= dst_byte_capacity ? file_length : dst_byte_capacity;
  }

  u64 count = fread(dst_buf->items, sizeof(u8), copy_byte_count, file);
  if (ferror(file)) { return 0; }

  dst_buf->count = count / item_size;

  return dst_buf->count;
}

u64 wapp_file_write(const GenericArray *src_buf, File *file, u64 item_count) {
  wapp_debug_assert(src_buf != NULL && file != NULL,
                    "`src_buf` and `file` should not be NULL.");

  u64 item_size      = src_buf->item_size;
  u64 src_byte_count = src_buf->count * item_size;
  u64 req_byte_count = item_count * item_size;
  u64 to_copy        = req_byte_count <= src_byte_count ? req_byte_count : src_byte_count;

  return fwrite(src_buf->items, sizeof(u8), to_copy, file);
}

i32 wapp_file_flush(File *file) {
  wapp_debug_assert(file != NULL, "`file` should not be NULL.");
  return fflush(file);
}

i32 wapp_file_close(File *file) {
  wapp_debug_assert(file != NULL, "`file` should not be NULL.");
  return fclose(file);
}
