#include "mz.h"
#include "mz_strm.h"
#include "mz_strm_mem.h"

#include "ioapi.h"

typedef struct mz_stream_ioapi_s {
    mz_stream stream;
    void *handle;
    zlib_filefunc_def filefunc;
    zlib_filefunc64_def filefunc64;
} mz_stream_ioapi;

/***************************************************************************/

static int32_t mz_stream_ioapi_open(void *stream, const char *path, int32_t mode);
static int32_t mz_stream_ioapi_is_open(void *stream);
static int32_t mz_stream_ioapi_read(void *stream, void *buf, int32_t size);
static int32_t mz_stream_ioapi_write(void *stream, const void *buf, int32_t size);
static int64_t mz_stream_ioapi_tell(void *stream);
static int32_t mz_stream_ioapi_seek(void *stream, int64_t offset, int32_t origin);
static int32_t mz_stream_ioapi_close(void *stream);
static int32_t mz_stream_ioapi_error(void *stream);

/***************************************************************************/

static mz_stream_vtbl mz_stream_ioapi_vtbl = {mz_stream_ioapi_open,
                                              mz_stream_ioapi_is_open,
                                              mz_stream_ioapi_read,
                                              mz_stream_ioapi_write,
                                              mz_stream_ioapi_tell,
                                              mz_stream_ioapi_seek,
                                              mz_stream_ioapi_close,
                                              mz_stream_ioapi_error,
                                              mz_stream_ioapi_create,
                                              mz_stream_ioapi_delete,
                                              NULL,
                                              NULL};

/***************************************************************************/

static int32_t mz_stream_ioapi_open(void *stream, const char *path, int32_t mode) {
    mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream;
    int32_t ioapi_mode = 0;

    if ((mode & MZ_OPEN_MODE_READWRITE) == MZ_OPEN_MODE_READ)
        ioapi_mode = ZLIB_FILEFUNC_MODE_READ;
    else if (mode & MZ_OPEN_MODE_APPEND)
        ioapi_mode = ZLIB_FILEFUNC_MODE_EXISTING;
    else if (mode & MZ_OPEN_MODE_CREATE)
        ioapi_mode = ZLIB_FILEFUNC_MODE_CREATE;
    else
        return MZ_OPEN_ERROR;

    if (ioapi->filefunc64.zopen64_file)
        ioapi->handle = ioapi->filefunc64.zopen64_file(ioapi->filefunc64.opaque, path, ioapi_mode);
    else if (ioapi->filefunc.zopen_file)
        ioapi->handle = ioapi->filefunc.zopen_file(ioapi->filefunc.opaque, path, ioapi_mode);

    if (!ioapi->handle)
        return MZ_PARAM_ERROR;

    return MZ_OK;
}

static int32_t mz_stream_ioapi_is_open(void *stream) {
    mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream;
    if (!ioapi->handle)
        return MZ_OPEN_ERROR;
    return MZ_OK;
}

static int32_t mz_stream_ioapi_read(void *stream, void *buf, int32_t size) {
    mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream;
    read_file_func zread = NULL;
    void *opaque = NULL;

    if (mz_stream_ioapi_is_open(stream) != MZ_OK)
        return MZ_OPEN_ERROR;

    if (ioapi->filefunc64.zread_file) {
        zread = ioapi->filefunc64.zread_file;
        opaque = ioapi->filefunc64.opaque;
    } else if (ioapi->filefunc.zread_file) {
        zread = ioapi->filefunc.zread_file;
        opaque = ioapi->filefunc.opaque;
    } else
        return MZ_PARAM_ERROR;

    return (int32_t)zread(opaque, ioapi->handle, buf, size);
}

static int32_t mz_stream_ioapi_write(void *stream, const void *buf, int32_t size) {
    mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream;
    write_file_func zwrite = NULL;
    int32_t written = 0;
    void *opaque = NULL;

    if (mz_stream_ioapi_is_open(stream) != MZ_OK)
        return MZ_OPEN_ERROR;

    if (ioapi->filefunc64.zwrite_file) {
        zwrite = ioapi->filefunc64.zwrite_file;
        opaque = ioapi->filefunc64.opaque;
    } else if (ioapi->filefunc.zwrite_file) {
        zwrite = ioapi->filefunc.zwrite_file;
        opaque = ioapi->filefunc.opaque;
    } else
        return MZ_PARAM_ERROR;

    written = (int32_t)zwrite(opaque, ioapi->handle, buf, size);
    return written;
}

static int64_t mz_stream_ioapi_tell(void *stream) {
    mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream;

    if (mz_stream_ioapi_is_open(stream) != MZ_OK)
        return MZ_OPEN_ERROR;

    if (ioapi->filefunc64.ztell64_file)
        return ioapi->filefunc64.ztell64_file(ioapi->filefunc64.opaque, ioapi->handle);
    else if (ioapi->filefunc.ztell_file)
        return ioapi->filefunc.ztell_file(ioapi->filefunc.opaque, ioapi->handle);

    return MZ_INTERNAL_ERROR;
}

static int32_t mz_stream_ioapi_seek(void *stream, int64_t offset, int32_t origin) {
    mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream;

    if (mz_stream_ioapi_is_open(stream) != MZ_OK)
        return MZ_OPEN_ERROR;

    if (ioapi->filefunc64.zseek64_file) {
        if (ioapi->filefunc64.zseek64_file(ioapi->filefunc64.opaque, ioapi->handle, offset, origin) != 0)
            return MZ_INTERNAL_ERROR;
    } else if (ioapi->filefunc.zseek_file) {
        if (ioapi->filefunc.zseek_file(ioapi->filefunc.opaque, ioapi->handle, (int32_t)offset, origin) != 0)
            return MZ_INTERNAL_ERROR;
    } else
        return MZ_PARAM_ERROR;

    return MZ_OK;
}

static int32_t mz_stream_ioapi_close(void *stream) {
    mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream;
    close_file_func zclose = NULL;
    void *opaque = NULL;

    if (mz_stream_ioapi_is_open(stream) != MZ_OK)
        return MZ_OPEN_ERROR;

    if (ioapi->filefunc.zclose_file) {
        zclose = ioapi->filefunc.zclose_file;
        opaque = ioapi->filefunc.opaque;
    } else if (ioapi->filefunc64.zclose_file) {
        zclose = ioapi->filefunc64.zclose_file;
        opaque = ioapi->filefunc64.opaque;
    } else
        return MZ_PARAM_ERROR;

    if (zclose(opaque, ioapi->handle) != 0)
        return MZ_CLOSE_ERROR;
    ioapi->handle = NULL;
    return MZ_OK;
}

static int32_t mz_stream_ioapi_error(void *stream) {
    mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream;
    testerror_file_func zerror = NULL;
    void *opaque = NULL;

    if (mz_stream_ioapi_is_open(stream) != MZ_OK)
        return MZ_OPEN_ERROR;

    if (ioapi->filefunc.zerror_file) {
        zerror = ioapi->filefunc.zerror_file;
        opaque = ioapi->filefunc.opaque;
    } else if (ioapi->filefunc64.zerror_file) {
        zerror = ioapi->filefunc64.zerror_file;
        opaque = ioapi->filefunc64.opaque;
    } else
        return MZ_PARAM_ERROR;

    return zerror(opaque, ioapi->handle);
}

int32_t mz_stream_ioapi_set_filefunc(void *stream, zlib_filefunc_def *filefunc) {
    mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream;
    memcpy(&ioapi->filefunc, filefunc, sizeof(zlib_filefunc_def));
    return MZ_OK;
}

int32_t mz_stream_ioapi_set_filefunc64(void *stream, zlib_filefunc64_def *filefunc) {
    mz_stream_ioapi *ioapi = (mz_stream_ioapi *)stream;
    memcpy(&ioapi->filefunc64, filefunc, sizeof(zlib_filefunc64_def));
    return MZ_OK;
}

void *mz_stream_ioapi_create(void) {
    mz_stream_ioapi *ioapi = (mz_stream_ioapi *)calloc(1, sizeof(mz_stream_ioapi));
    if (ioapi)
        ioapi->stream.vtbl = &mz_stream_ioapi_vtbl;
    return ioapi;
}

void mz_stream_ioapi_delete(void **stream) {
    mz_stream_ioapi *ioapi = NULL;
    if (!stream)
        return;
    ioapi = (mz_stream_ioapi *)*stream;
    free(ioapi);
    *stream = NULL;
}

/***************************************************************************/

void fill_fopen_filefunc(zlib_filefunc_def *pzlib_filefunc_def) {
    /* For 32-bit file support only, compile with MZ_FILE32_API */
    if (pzlib_filefunc_def)
        memset(pzlib_filefunc_def, 0, sizeof(zlib_filefunc_def));
}

void fill_fopen64_filefunc(zlib_filefunc64_def *pzlib_filefunc_def) {
    /* All mz_stream_os_* support large files if compilation supports it */
    if (pzlib_filefunc_def)
        memset(pzlib_filefunc_def, 0, sizeof(zlib_filefunc64_def));
}

void fill_win32_filefunc(zlib_filefunc_def *pzlib_filefunc_def) {
    /* Handled by mz_stream_os_win32 */
    if (pzlib_filefunc_def)
        memset(pzlib_filefunc_def, 0, sizeof(zlib_filefunc_def));
}

void fill_win32_filefunc64(zlib_filefunc64_def *pzlib_filefunc_def) {
    /* Automatically supported in mz_stream_os_win32 */
    if (pzlib_filefunc_def)
        memset(pzlib_filefunc_def, 0, sizeof(zlib_filefunc64_def));
}

void fill_win32_filefunc64A(zlib_filefunc64_def *pzlib_filefunc_def) {
    /* Automatically supported in mz_stream_os_win32 */
    if (pzlib_filefunc_def)
        memset(pzlib_filefunc_def, 0, sizeof(zlib_filefunc64_def));
}

/* NOTE: fill_win32_filefunc64W is no longer necessary since wide-character
   support is automatically handled by the underlying os stream. Do not
   pass wide-characters to zipOpen or unzOpen. */

void fill_memory_filefunc(zlib_filefunc_def *pzlib_filefunc_def) {
    /* Use opaque to indicate which stream interface to create */
    if (pzlib_filefunc_def) {
        memset(pzlib_filefunc_def, 0, sizeof(zlib_filefunc_def));
        pzlib_filefunc_def->opaque = mz_stream_mem_get_interface();
    }
}
