//
// BME (Blasphemous Multimedia Engine) datafile IO main module
//

#include "bme_io.h"

#include "bme_main.h"
#include "ltlog.h"

#include <SDL3/SDL.h>

#include <new>

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>

// Up to 16 simultaneous files open from the datafile
constexpr int MAX_HANDLES = 16;

struct Header
{
    Uint32 offset;
    Sint32 length;
    char name[13];
};

struct Handle
{
    Header *currentheader;
    int filepos;
    bool open;
};

static const char *idstring = "DAT!";

static bool io_datafileopen = false;
static Header *fileheaders;
static unsigned files;
static Handle handle[MAX_HANDLES];
static unsigned char *datafileptr;
static unsigned char *datafilestart;

static void linkedseek(unsigned pos);
static void linkedread(void *buffer, int length);
static unsigned linkedreadle32(void);

bool io_openlinkeddatafile(unsigned char *ptr)
{
    datafilestart = ptr;
    linkedseek(0);

    char ident[4];
    linkedread(ident, 4);
    if (std::memcmp(ident, idstring, 4))
    {
        ltlog::error("Invalid datafile");
        return false;
    }

    files = linkedreadle32();
    fileheaders = new (std::nothrow) Header[files];
    if (!fileheaders)
    {
        ltlog::error("Out of memory");
        return false;
    }
    for (unsigned index = 0; index < files; index++)
    {
        fileheaders[index].offset = linkedreadle32();
        fileheaders[index].length = linkedreadle32();
        linkedread(&fileheaders[index].name, 13);
    }

    for (unsigned index = 0; index < MAX_HANDLES; index++) handle[index].open = false;
    io_datafileopen = true;
    return true;
}

void io_closelinkeddatafile()
{
    delete [] fileheaders;
}

// Returns nonnegative file handle if successful, -1 on error

int io_open(const char *name)
{
    if (!name || !io_datafileopen) return -1;

    size_t namelength = std::strlen(name);
    if (namelength > 12) namelength = 12;
    char namecopy[13];
    std::memcpy(namecopy, name, namelength + 1);
    namelength = std::strlen(namecopy);
    for (size_t index = 0; index < namelength; index++)
    {
        namecopy[index] = std::toupper(namecopy[index]);
    }

    for (size_t index = 0; index < MAX_HANDLES; index++)
    {
        if (!handle[index].open)
        {
            unsigned count = files;
            handle[index].currentheader = fileheaders;

            while (count)
            {
                if (!std::strcmp(namecopy, handle[index].currentheader->name))
                {
                        handle[index].open = true;
                        handle[index].filepos = 0;
                        return (int)index;
                }
                count--;
                handle[index].currentheader++;
            }
            return -1;
        }
    }
    return -1;
}

// Returns file position after seek or -1 on error

int io_lseek(int index, int offset, int whence)
{
    if (!io_datafileopen)  return -1;

    if ((index < 0) || (index >= MAX_HANDLES)) return -1;

    if (!handle[index].open) return -1;
    int newpos;
    switch(whence)
    {
        default:
        case SEEK_SET:
        newpos = offset;
        break;

        case SEEK_CUR:
        newpos = offset + handle[index].filepos;
        break;

        case SEEK_END:
        newpos = offset + handle[index].currentheader->length;
        break;
    }
    if (newpos < 0) newpos = 0;
    if (newpos > handle[index].currentheader->length) newpos = handle[index].currentheader->length;
    handle[index].filepos = newpos;
    return newpos;
}

// Returns number of bytes actually read, -1 on error

int io_read(int index, void *buffer, int length)
{
    if (!io_datafileopen) return -1;

    if ((index < 0) || (index >= MAX_HANDLES)) return -1;

    if (!handle[index].open) return -1;
    if (length + handle[index].filepos > handle[index].currentheader->length)
    length = handle[index].currentheader->length - handle[index].filepos;

    linkedseek(handle[index].currentheader->offset + handle[index].filepos);
    linkedread(buffer, length);

    handle[index].filepos += length;
    return length;
}

// Returns nothing

void io_close(int index)
{
    if (io_datafileopen)
    {
        if ((index < 0) || (index >= MAX_HANDLES)) return;

        handle[index].open = false;
    }
}

unsigned io_read8(int index)
{
    unsigned char byte;

    io_read(index, &byte, 1);
    return byte;
}

unsigned io_readle16(int index)
{
    unsigned char bytes[2];

    io_read(index, bytes, 2);
    return (bytes[1] << 8) | bytes[0];
}

unsigned io_readhe16(int index)
{
    unsigned char bytes[2];

    io_read(index, bytes, 2);
    return (bytes[0] << 8) | bytes[1];
}

unsigned io_readle32(int index)
{
    unsigned char bytes[4];

    io_read(index, bytes, 4);
    return (bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | bytes[0];
}

unsigned io_readhe32(int index)
{
    unsigned char bytes[4];

    io_read(index, bytes, 4);
    return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
}

static void linkedseek(unsigned pos)
{
    datafileptr = &datafilestart[pos];
}

static void linkedread(void *buffer, int length)
{
    unsigned char *dest = (unsigned char *)buffer;
    while (length--)
    {
        *dest++ = *datafileptr++;
    }
}

static unsigned linkedreadle32()
{
    unsigned char bytes[4];

    linkedread(&bytes, 4);
    return (bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | bytes[0];
}
