// Copyright 2020 Joe Drago. All rights reserved.
// SPDX-License-Identifier: BSD-2-Clause

#if !defined(_WIN32)
// Ensure off_t is 64 bits.
#undef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 64
// Ensure we have some POSIX compatibility with fseeko/ftello.
#undef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200112L
#endif

#include "avif/internal.h"

#include <assert.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#if defined(_WIN32)
// Windows uses _fseeki64 / _ftelli64 for large file support
typedef __int64 avif_off_t;
#define AVIF_OFF_MAX INT64_MAX

static int avif_fseeko(FILE * stream, avif_off_t offset, int whence)
{
    return _fseeki64(stream, offset, whence);
}

static avif_off_t avif_ftello(FILE * stream)
{
    return _ftelli64(stream);
}
#else

#include <unistd.h>

#if defined(__ANDROID__)
#include <android/api-level.h>
#if __ANDROID_API__ >= 24
#define AVIF_USE_FSEEKO
#endif
#elif defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L
// Standard Modern POSIX. The _POSIX_VERSION >= 200112L test for fseeko/ftello
// is used in the first example in the APPLICATION USAGE section in
// https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/unistd.h.html.
#define AVIF_USE_FSEEKO
#endif

#if defined(AVIF_USE_FSEEKO)
// POSIX large file support
static_assert(sizeof(off_t) == sizeof(int64_t), "");
typedef off_t avif_off_t;
#define AVIF_OFF_MAX INT64_MAX

static int avif_fseeko(FILE * stream, avif_off_t offset, int whence)
{
    return fseeko(stream, offset, whence);
}

static avif_off_t avif_ftello(FILE * stream)
{
    return ftello(stream);
}
#else
// Unknown or very old platform. Fall back on fseek/ftell.
typedef long avif_off_t;
#define AVIF_OFF_MAX LONG_MAX

static int avif_fseeko(FILE * stream, avif_off_t offset, int whence)
{
    return fseek(stream, offset, whence);
}

static avif_off_t avif_ftello(FILE * stream)
{
    return ftell(stream);
}
#endif // defined(AVIF_USE_FSEEKO)

#endif // defined(_WIN32)

void avifIODestroy(avifIO * io)
{
    if (io && io->destroy) {
        io->destroy(io);
    }
}

// --------------------------------------------------------------------------------------
// avifIOMemoryReader

typedef struct avifIOMemoryReader
{
    avifIO io; // this must be the first member for easy casting to avifIO*
    avifROData rodata;
} avifIOMemoryReader;

static avifResult avifIOMemoryReaderRead(struct avifIO * io, uint32_t readFlags, uint64_t offset, size_t size, avifROData * out)
{
    // printf("avifIOMemoryReaderRead offset %" PRIu64 " size %zu\n", offset, size);

    if (readFlags != 0) {
        // Unsupported readFlags
        return AVIF_RESULT_IO_ERROR;
    }

    avifIOMemoryReader * reader = (avifIOMemoryReader *)io;

    // Sanitize/clamp incoming request
    if (offset > reader->rodata.size) {
        // The offset is past the end of the buffer.
        return AVIF_RESULT_IO_ERROR;
    }
    uint64_t availableSize = reader->rodata.size - offset;
    if (size > availableSize) {
        size = (size_t)availableSize;
    }

    // Prevent the offset addition from triggering an undefined behavior
    // sanitizer error if data is NULL (happens even with offset zero).
    out->data = offset ? reader->rodata.data + offset : reader->rodata.data;
    out->size = size;
    return AVIF_RESULT_OK;
}

static void avifIOMemoryReaderDestroy(struct avifIO * io)
{
    avifFree(io);
}

avifIO * avifIOCreateMemoryReader(const uint8_t * data, size_t size)
{
    avifIOMemoryReader * reader = (avifIOMemoryReader *)avifAlloc(sizeof(avifIOMemoryReader));
    if (reader == NULL) {
        return NULL;
    }
    memset(reader, 0, sizeof(avifIOMemoryReader));
    reader->io.destroy = avifIOMemoryReaderDestroy;
    reader->io.read = avifIOMemoryReaderRead;
    reader->io.sizeHint = size;
    reader->io.persistent = AVIF_TRUE;
    reader->rodata.data = data;
    reader->rodata.size = size;
    return (avifIO *)reader;
}

// --------------------------------------------------------------------------------------
// avifIOFileReader

typedef struct avifIOFileReader
{
    avifIO io; // this must be the first member for easy casting to avifIO*
    avifRWData buffer;
    FILE * f;
} avifIOFileReader;

static avifResult avifIOFileReaderRead(struct avifIO * io, uint32_t readFlags, uint64_t offset, size_t size, avifROData * out)
{
    // printf("avifIOFileReaderRead offset %" PRIu64 " size %zu\n", offset, size);

    if (readFlags != 0) {
        // Unsupported readFlags
        return AVIF_RESULT_IO_ERROR;
    }

    avifIOFileReader * reader = (avifIOFileReader *)io;

    // Sanitize/clamp incoming request
    if (offset > reader->io.sizeHint) {
        // The offset is past the EOF.
        return AVIF_RESULT_IO_ERROR;
    }
    uint64_t availableSize = reader->io.sizeHint - offset;
    if (size > availableSize) {
        size = (size_t)availableSize;
    }

    if (size > 0) {
        if (offset > AVIF_OFF_MAX) {
            return AVIF_RESULT_IO_ERROR;
        }
        if (reader->buffer.size < size) {
            AVIF_CHECKRES(avifRWDataRealloc(&reader->buffer, size));
        }
        if (avif_fseeko(reader->f, (avif_off_t)offset, SEEK_SET) != 0) {
            return AVIF_RESULT_IO_ERROR;
        }
        size_t bytesRead = fread(reader->buffer.data, 1, size, reader->f);
        if (size != bytesRead) {
            if (ferror(reader->f)) {
                return AVIF_RESULT_IO_ERROR;
            }
            size = bytesRead;
        }
    }

    out->data = reader->buffer.data;
    out->size = size;
    return AVIF_RESULT_OK;
}

static void avifIOFileReaderDestroy(struct avifIO * io)
{
    avifIOFileReader * reader = (avifIOFileReader *)io;
    fclose(reader->f);
    avifRWDataFree(&reader->buffer);
    avifFree(io);
}

avifIO * avifIOCreateFileReader(const char * filename)
{
    FILE * f = fopen(filename, "rb");
    if (!f) {
        return NULL;
    }

    if (avif_fseeko(f, 0, SEEK_END) != 0) {
        fclose(f);
        return NULL;
    }
    avif_off_t fileSize = avif_ftello(f);
    if (fileSize < 0) {
        fclose(f);
        return NULL;
    }
    if (avif_fseeko(f, 0, SEEK_SET) != 0) {
        fclose(f);
        return NULL;
    }

    avifIOFileReader * reader = (avifIOFileReader *)avifAlloc(sizeof(avifIOFileReader));
    if (!reader) {
        fclose(f);
        return NULL;
    }
    memset(reader, 0, sizeof(avifIOFileReader));
    reader->f = f;
    reader->io.destroy = avifIOFileReaderDestroy;
    reader->io.read = avifIOFileReaderRead;
    reader->io.sizeHint = (uint64_t)fileSize;
    reader->io.persistent = AVIF_FALSE;
    if (avifRWDataRealloc(&reader->buffer, 1024) != AVIF_RESULT_OK) {
        avifFree(reader);
        fclose(f);
        return NULL;
    }
    return (avifIO *)reader;
}
