#include <glib.h>
#include <cstdlib>

#include <libaudcore/plugin.h>

extern "C" {
#include "../src/vgmstream.h"
}
#include "plugin.h"
#include "vfs.h"

typedef struct {
    STREAMFILE sf;
    VFSFile *vfsFile;
    offv_t offset;
    char name[32768];
} VFS_STREAMFILE;

static STREAMFILE *open_vfs_by_VFSFILE(VFSFile *file, const char *path);

static size_t read_vfs(VFS_STREAMFILE *streamfile, uint8_t *dest, offv_t offset, size_t length) {
    size_t bytes_read;

    if (/*!streamfile->vfsFile ||*/ !dest || length <= 0 || offset < 0)
        return 0;

    // if the offsets don't match, then we need to perform a seek
    if (streamfile->offset != offset) {
        int ok = streamfile->vfsFile->fseek(offset, VFS_SEEK_SET);
        if (ok != 0) return 0;
        streamfile->offset = offset;
    }

    bytes_read = streamfile->vfsFile->fread(dest, 1, length);
    streamfile->offset += bytes_read;

    return bytes_read;
}

static void close_vfs(VFS_STREAMFILE *streamfile) {
    //if (streamfile->vfsFile)
    delete streamfile->vfsFile; //fcloses the internal file too
    free(streamfile);
}

static size_t get_size_vfs(VFS_STREAMFILE *streamfile) {
    //if (!streamfile->vfsFile)
    //    return 0;
    return streamfile->vfsFile->fsize();
}

static size_t get_offset_vfs(VFS_STREAMFILE *streamfile) {
    return streamfile->offset;
}

static void get_name_vfs(VFS_STREAMFILE *streamfile, char *buffer, size_t length) {
    strncpy(buffer, streamfile->name, length);
    buffer[length - 1] = '\0';
}

static STREAMFILE *open_vfs_impl(VFS_STREAMFILE *streamfile, const char *const filename, size_t buffersize) {
    if (!filename)
        return NULL;

    return open_vfs(filename);
}

STREAMFILE *open_vfs_by_VFSFILE(VFSFile *file, const char *path) {
    VFS_STREAMFILE *streamfile = (VFS_STREAMFILE *)malloc(sizeof(VFS_STREAMFILE));
    if (!streamfile)
        return NULL;

    // success, set our pointers
    memset(streamfile, 0, sizeof(VFS_STREAMFILE));

    streamfile->sf.read = (size_t (*)(STREAMFILE *, uint8_t *, offv_t, size_t))read_vfs;
    streamfile->sf.get_size = (size_t (*)(STREAMFILE *))get_size_vfs;
    streamfile->sf.get_offset = (offv_t (*)(STREAMFILE *))get_offset_vfs;
    streamfile->sf.get_name = (void (*)(STREAMFILE *, char *, size_t))get_name_vfs;
    streamfile->sf.open = (STREAMFILE *(*)(STREAMFILE *, const char *, size_t))open_vfs_impl;
    streamfile->sf.close = (void (*)(STREAMFILE *))close_vfs;

    streamfile->vfsFile = file;
    streamfile->offset = 0;
    strncpy(streamfile->name, path, sizeof(streamfile->name));
    streamfile->name[sizeof(streamfile->name) - 1] = '\0';

    // for reference, actual file path ("name" has protocol path, file://...).
    // name should work for all situations but in case it's needed again maybe
    // get_name should always return realname, as it's used to open companion VFSFiles
    //{
    //    gchar *realname = g_filename_from_uri(path, NULL, NULL);
    //    strncpy(streamfile->realname, realname, sizeof(streamfile->realname));
    //    streamfile->realname[sizeof(streamfile->realname) - 1] = '\0';
    //    g_free(realname);
    //}

    return &streamfile->sf;
}

STREAMFILE *open_vfs(const char *path) {
    VFSFile *vfsFile = new VFSFile(path, "rb");
    if (!vfsFile || !*vfsFile) {
        delete vfsFile;
        return NULL;
    }

#if 0 // files that don't exist seem blocked by probe.cc before reaching here
    bool infile_exists = vfsFile && *vfsFile;
    if (!infile_exists) {
        /* allow non-existing files in some cases */
        if (!vgmstream_is_virtual_filename(path)) {
            delete vfsFile;
            return NULL;
        }
        vfsFile = NULL;
    }
#endif
    return open_vfs_by_VFSFILE(vfsFile, path);
}