#include "file_backend.hpp"
#include <filesystem>
#include <string.h>

File::File() {
}
size_t File::get_len() {
    return len;
}
CFile::CFile() : File() {
}

void CFile::open(const char *fname) {
    name = fname;
    file = fopen(fname, "rb");
    if (file == NULL) {
        throw NotFoundException(fname);
    }
    fseek(file, 0, SEEK_END);
    len = ftell(file);
    fseek(file, 0, SEEK_SET);
}
void CFile::close() {
    if (file == NULL) return;
    fclose(file);
    file = NULL;
}
size_t CFile::read(void *ptr, size_t size, size_t len) {
    if (file == NULL) return 0;
    return fread(ptr, size, len, file);
}
void CFile::seek(int64_t pos, SeekType seek_type) {
    int whence;
    switch (seek_type) {
        case SeekType::SET: {
            whence = SEEK_SET;
            if (pos < 0) throw std::exception();
        } break;
        case SeekType::CUR: {
            whence = SEEK_CUR;
            if (get_pos() + pos < 0) throw std::exception();
        } break;
        case SeekType::END: {
            whence = SEEK_END;
            if (((int64_t)get_len()) + pos < 0) throw std::exception();
        } break;
    }
    if (file == NULL) throw std::exception();
    fseek(file, pos, whence);
}
int64_t CFile::get_pos() {
    if (file == NULL) return 0;
    return ftell(file);
}
bool CFile::is_open() {
    return file != NULL;
}
MemFile::MemFile() {
    ptr = NULL;
    len = 0;
}
void MemFile::open(const char *path) {
    CFile file;
    file.open(path);
    this->name = strdup(file.name);
    this->ptr = malloc(file.get_len());
    this->len = file.read(this->ptr, 1, file.get_len());
    file.close();
    this->pos = 0;
}
void MemFile::close() {
    free(this->ptr);
    free((void*)this->name);
    this->ptr = NULL;
    this->name = NULL;
    this->len = 0;
    this->pos = 0;
}
size_t MemFile::read(void *ptr, size_t size, size_t len) {
    if (this->ptr == NULL) {
        ERROR.writeln("Attempt to read invalid memfile");
        return 0;
    }
    if (size == 0) return 0;
    if (len == 0) return 0;
    uint8_t *ptr8 = (uint8_t*)ptr;
    uint8_t *myptr8 = (uint8_t*)this->ptr;
    size_t i;
    size_t maxbytes = size * len;
    size_t validbytes = std::min(maxbytes, (size_t)std::max((int64_t)this->len - (int64_t)this->pos, (int64_t)0));
    size_t remainder = maxbytes - validbytes;
    memcpy(ptr8, myptr8 + this->pos, validbytes);
    if (this->pos < this->len) {
        if (remainder > 0) {
            seek(0, SeekType::END);
        } else {
            seek(maxbytes, SeekType::CUR);
        }
    }
    return validbytes / size;
}
int64_t MemFile::get_pos() {
    return pos;
}
bool MemFile::is_open() {
    return ptr != NULL;
}
size_t MemFile::get_len() {
    return len;
}
void MemFile::seek(int64_t pos, SeekType seek_type) {
	int64_t new_pos = this->pos;
    switch (seek_type) {
        case SeekType::CUR: {
            new_pos += pos;
        } break;
        case SeekType::END: {
            new_pos = this->len + pos;
        } break;
        case SeekType::SET: {
            new_pos = pos;
        } break;
    }
    if (new_pos < 0) throw std::exception();
    this->pos = new_pos;
}

size_t rwops_read(SDL_RWops *rwops, void *ptr, size_t size, size_t maxnum) {
    File *file = (File*)rwops->hidden.unknown.data1;
    int64_t oldpos = file->get_pos();
    try {
        return file->read(ptr, size, maxnum);
    } catch (std::exception) {
        return (file->get_pos() - oldpos) / size;
    }
}
int rwops_close(SDL_RWops *rwops) {
    File *file = (File*)rwops->hidden.unknown.data1;
    file->close();
    return 0;
}
Sint64 rwops_seek(SDL_RWops *rwops, Sint64 offset, int whence) {
    SeekType type;
    switch (whence) {
        case RW_SEEK_CUR: {
            type = SeekType::CUR;
        } break;
        case RW_SEEK_SET: {
            type = SeekType::SET;
        } break;
        case RW_SEEK_END: {
            type = SeekType::END;
        } break;
    }
    File *file = (File*)rwops->hidden.unknown.data1;
    if (type == SeekType::CUR && offset == 0) return file->get_pos();
    try {
        file->seek((int64_t)offset, type);
    } catch (std::exception) {
        return -1;
    }
    return (Sint64)file->get_pos();
}
Sint64 rwops_size(SDL_RWops *rwops) {
    FILE_TYPE *file = (FILE_TYPE*)rwops->hidden.unknown.data1;
    return (Sint64)file->get_len();
}
SDL_RWops *get_sdl_file(File *file) {
    SDL_RWops *rwops = new SDL_RWops();
    rwops->read = &rwops_read;
    rwops->close = &rwops_close;
    rwops->write = NULL;
    rwops->seek = &rwops_seek;
    rwops->size = &rwops_size;
    rwops->type = SDL_RWOPS_UNKNOWN;
    rwops->hidden.unknown.data1 = file;
    return rwops;
}
struct file_streamfile {
    STREAMFILE vt;
    File *file;
};
static file_streamfile *sf_open(file_streamfile *sf, const char *const filename, size_t buf_size) {
    sf->file->open(filename);
    return sf;
}
static size_t sf_read(file_streamfile *sf, uint8_t *dst, offv_t offset, size_t length) {
    File *file = sf->file;
    file->seek(offset, SeekType::SET);
    return file->read(dst, 1, length);
}
static size_t sf_size(file_streamfile *sf) {
    File *file = sf->file;
    return file->get_len();
}
static void sf_close(file_streamfile *sf) {
    File *file = sf->file;
    file->close();
}
static void sf_get_name(file_streamfile *sf, char *name, size_t name_size) {
    File *file = sf->file;
    int copy_size = strlen(file->name) + 1;
    if (copy_size > name_size) copy_size = name_size;
    memcpy(name, file->name, copy_size);
    name[copy_size - 1] = '\0';
}
static offv_t sf_get_offset(file_streamfile *sf) {
    return (offv_t)sf->file->get_pos();
}
STREAMFILE *get_sf_from_file(File *file) {
    file_streamfile *sf = new file_streamfile();
    sf->vt.read = (size_t(*)(STREAMFILE*, uint8_t*, offv_t, size_t))(void*)sf_read;
    sf->vt.get_size = (size_t(*)(STREAMFILE*))(void*)(sf_size);
    sf->vt.get_offset = (offv_t(*)(STREAMFILE*))(void*)(sf_get_offset);
    sf->vt.get_name = (void(*)(STREAMFILE*,char*,size_t))(void*)(sf_get_name);
    sf->vt.open = (STREAMFILE*(*)(STREAMFILE*,const char *const, size_t))(sf_open);
    sf->vt.close = (void(*)(STREAMFILE*))(void*)(sf_close);
    sf->vt.stream_index = 0;
    sf->file = file;
    return &sf->vt;
}
File *open_file(const char *name) {
    auto output = new FILE_TYPE();
    output->open(name);
    return output;
}