#include "File.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#ifdef _WIN32
#define JES_O_BINARY O_BINARY
#else
#define JES_O_BINARY 0
#endif

/**
 * Open a file.
 *
 * @param[in] filename The filename.
 * @param[in] writing Whether to open for writing.
 */
bool File::open(const char * filename, bool writing)
{
    int fd;
    if (writing)
    {
        /* TODO: handle mode */
        fd = ::open(filename, O_WRONLY | O_CREAT | O_TRUNC | JES_O_BINARY, 0644);
    }
    else
    {
        fd = ::open(filename, O_RDONLY | JES_O_BINARY);
    }
    if (fd < 0)
    {
        return false;
    }

    m_fd = fd;
    return true;
}

/**
 * Close the file.
 */
void File::close()
{
    if (m_fd >= 0)
    {
        ::close(m_fd);
        m_fd = -1;
    }
}

/**
 * Get the file's size.
 *
 * This function will reset the file position to the beginning of the file.
 *
 * @return The file's size, or -1 on error.
 */
size_t File::get_size()
{
    if (m_fd >= 0)
    {
        off_t o = lseek(m_fd, 0, SEEK_END);
        lseek(m_fd, 0, SEEK_SET);
        return o;
    }
    return -1;
}

/**
 * Read from the file.
 *
 * @param[in] buf Memory buffer to read into. Must be large enough to hold
 *   size bytes.
 * @param[in] size Number of bytes to read.
 *
 * @retval false The file failed to read.
 * @retval true The file was read successfully.
 */
bool File::read(uint8_t * buf, size_t size)
{
    size_t n_bytes_read = 0u;
    for (;;)
    {
        off_t rd_size = ::read(m_fd, &buf[n_bytes_read], size - n_bytes_read);
        if (rd_size <= 0)
            break;
        n_bytes_read += (size_t)rd_size;
        if (n_bytes_read >= size)
            break;
    }
    if (n_bytes_read != size)
    {
        return false;
    }

    return true;
}

bool File::write(const uint8_t * buf, size_t size)
{
    if (size <= 0)
        return true;

    size_t n_bytes_written = 0u;
    for (;;)
    {
        off_t write_size = ::write(m_fd, &buf[n_bytes_written], size - n_bytes_written);
        if (write_size <= 0)
            break;
        n_bytes_written += (size_t)write_size;
        if (n_bytes_written >= size)
            break;
    }

    if (n_bytes_written != size)
    {
        return false;
    }

    return true;
}
