#include <errno.h>
#include <fcntl.h>
#include <luna.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

FILE* __stderr;
FILE* __stdout;

extern "C"
{
    int fclose(FILE* stream)
    {
        int status = close(stream->f_fd);
        if (status < 0)
        {
            int savederr = errno;
            free(stream); // We do not want to leak memory. man fclose(3) says that whether fclose() fails or not, any
                          // further operation on the stream results in undefined behavior. So we are free to free the
                          // stream.
            errno = savederr; // free might reset errno. We don't want that.
        }
        else { free(stream); }
        return status;
    }

    int fflush(FILE*)
    {
        return 0; // FIXME: Implement buffered IO.
    }

    FILE* fopen(const char* pathname, const char*)
    {
        int fd = open(pathname, O_RDWR); // FIXME: Use the mode string.
        if (fd < 0) { return 0; }
        FILE* stream = (FILE*)malloc(sizeof(FILE));
        stream->f_fd = fd;
        clearerr(stream);
        return stream;
    }

    FILE* fdopen(int fd, const char*)
    {
        if (fd < 0) // FIXME: Also check if the mode string is compatible with how fd was opened.
        {
            errno = EBADF;
            return 0;
        }
        FILE* stream = (FILE*)malloc(sizeof(FILE));
        stream->f_fd = fd;
        clearerr(stream);
        return stream;
    }

    size_t fread(void* buf, size_t size, size_t nmemb, FILE* stream)
    {
        ssize_t status = read(stream->f_fd, buf, size * nmemb);
        if (status < 0)
        {
            stream->f_err = 1;
            return 0;
        }
        if (status == 0) stream->f_eof = 1;
        return (size_t)status;
    }

    int ferror(FILE* stream)
    {
        return stream->f_err;
    }

    int feof(FILE* stream)
    {
        return stream->f_eof;
    }

    void clearerr(FILE* stream)
    {
        stream->f_err = stream->f_eof = 0;
    }

    int fseek(FILE* stream, long offset, int whence)
    {
        long result = lseek(stream->f_fd, offset, whence);
        if (result < 0) { return -1; }
        return 0;
    }

    long ftell(FILE* stream)
    {
        return lseek(stream->f_fd, 0,
                     SEEK_CUR); // FIXME: Store the last seeked position in the file struct to avoid redundant syscalls
                                // maybe? We'd have to update this value in fread() and fwrite() as well...
    }

    void rewind(FILE* stream)
    {
        lseek(stream->f_fd, 0, SEEK_SET);
        clearerr(stream);
    }

    size_t fwrite(const void* buf, size_t size, size_t nmemb, FILE* stream)
    {
        ssize_t status = write(stream->f_fd, buf, size * nmemb);
        if (status < 0)
        {
            stream->f_err = 1;
            return 0;
        }
        if (status == 0) stream->f_eof = 1;
        return (size_t)status;
    }

    void setbuf(FILE*, char*)
    {
        NOT_IMPLEMENTED("setbuf");
    }
}