/*
 * Copyright ©2026 Soham Pardeshi. All rights reserved.
 * Permission is hereby granted to students registered for University of
 * Washington CSE 333 for use solely during Summer Quarter 2026 for
 * purposes of the course. No other use, copying, distribution, or
 * modification is permitted without prior written consent. Copyrights
 * for third-party components of this work must be honored. Instructors
 * interested in reusing these course materials should contact the author.
 */

#include "fileutil.h"

#include <errno.h>   // for errno, EINTR, EAGAIN
#include <fcntl.h>   // for open, O_RDONLY
#include <unistd.h>  // for read, close, lseek, SEEK_END

ssize_t FileSize(const char* filename) {
  // Open the file in read-only mode.
  int fd = open(filename, O_RDONLY);
  if (fd == -1) {
    return -1;
  }
  // Seek to the end of the file to find its size.
  off_t size = lseek(fd, 0, SEEK_END);
  // Close the file in all cases, including when lseek failed.
  int close_res = close(fd);
  if (size == -1 || close_res == -1) {
    return -1;
  }
  return size;
}

ssize_t ReadFile(const char* filename, char* buf, size_t buflen,
                 size_t chunk_size) {
  // Open the file in read-only mode.
  int fd = open(filename, O_RDONLY);
  if (fd == -1) {
    return -1;
  }

  // 'buflen' is how many bytes the caller wants read into 'buf'; give that
  // a clearer name for use in the loop below.
  size_t target = buflen;

  // Read until we've read 'target' bytes or hit the end of the file, asking
  // for at most 'chunk_size' bytes on each read() call.
  size_t bytes_read = 0;
  while (bytes_read < target) {
    // How many bytes to request this time: a full chunk, unless fewer than
    // a chunk's worth of space is left in the buffer.
    size_t bytes_remaining = target - bytes_read;
    size_t to_read = chunk_size;
    if (to_read > bytes_remaining) {
      to_read = bytes_remaining;
    }

    // Attempt to read 'to_read' bytes from the file into the buffer.
    ssize_t res = read(fd, buf + bytes_read, to_read);

    // Case 1: End of file.
    if (res == 0) {
      break;
    }

    // Case 2: Error.
    if (res == -1) {
      if (errno == EINTR || errno == EAGAIN) {
        continue;  // recoverable error; try again
      }
      close(fd);  // clean up before reporting the error
      return -1;
    }

    bytes_read += res;  // may be a partial read; keep looping
  }

  // Close the file and report how much we read.
  if (close(fd) == -1) {
    return -1;
  }
  return bytes_read;
}