#include "KinoSearch/Util/ToolSet.h"

#include <errno.h>
#include <stdio.h>

#define KINO_WANT_RAMFILEDES_VTABLE
#include "KinoSearch/Store/RAMFileDes.r"


RAMFileDes*
RAMFileDes_new(const char *path) 
{
    CREATE(self, RAMFileDes, RAMFILEDES);

    /* init */
    self->buffers      = VA_new(1);
    self->pos          = 0;
    self->stream_count = 0;
    self->len          = 0;

    /* assign */
    self->path     = strdup(path);

    /* track number of live FileDes released into the wild */
    FileDes_object_count++;
    FileDes_open_count++;

    return self;
}

void
RAMFileDes_destroy(RAMFileDes *self) 
{
    REFCOUNT_DEC(self->buffers);
    free(self->path);

    /* decrement count of FileDes structs in existence */
    FileDes_object_count--;

    free(self);
}

bool_t
RAMFileDes_fdseek(RAMFileDes *self, u64_t target)
{
    if (target > self->len) {
        Carp_set_kerror("Attempt to seek past EOF: %lu %lu",
            (unsigned long)target, (unsigned long)self->len);
        return false;
    }
    self->pos = target;
    return true;
}

bool_t
RAMFileDes_fdread(RAMFileDes *self, char *dest, u32_t dest_offset, u32_t len)
{
    VArray *const buffers = self->buffers;
    u32_t bytes_wanted = len;
    u32_t buf_num = self->pos / IO_STREAM_BUF_SIZE;

    if (self->pos + len > self->len) {
        Carp_set_kerror(
            "Attempt to read %lu bytes starting at %lu goes past EOF %lu",
            (unsigned long)len, (unsigned long)self->pos, 
            (unsigned long)self->len
        );
        return false;
    }

    dest += dest_offset;

    while (bytes_wanted) {
        const u32_t source_offset = self->pos % IO_STREAM_BUF_SIZE;
        const u32_t bytes_in_buf = IO_STREAM_BUF_SIZE - source_offset;
        const u32_t bytes_to_copy = bytes_in_buf > bytes_wanted
            ? bytes_wanted
            : bytes_in_buf;
        ByteBuf *const buffer = (ByteBuf*)VA_Fetch(buffers, buf_num);
        char *const source = buffer->ptr + source_offset; 

        memcpy(dest, source, bytes_to_copy);

        buf_num++;
        dest += bytes_to_copy;
        bytes_wanted -= bytes_to_copy;
        self->pos += bytes_to_copy;
    }

    return true;
} 

bool_t
RAMFileDes_fdwrite(RAMFileDes *self, const char *source, u32_t len) 
{
    VArray *const buffers = self->buffers;
    u32_t bytes_left = len;
    u32_t dest_buf_num = self->pos / IO_STREAM_BUF_SIZE;
    
    while (bytes_left) {
        ByteBuf *buffer;
        char *dest;
        const u32_t dest_offset = self->pos % IO_STREAM_BUF_SIZE;
        const u32_t room_in_dest_buf = IO_STREAM_BUF_SIZE - dest_offset;
        const u32_t bytes_to_copy = bytes_left > room_in_dest_buf 
            ? room_in_dest_buf 
            : bytes_left;

        if (dest_buf_num >= buffers->size) {
            buffer = BB_new(IO_STREAM_BUF_SIZE);
            VA_Push(buffers, (Obj*)buffer);
            REFCOUNT_DEC(buffer);
        }
        else {
            buffer = (ByteBuf*)VA_Fetch(buffers, dest_buf_num);
        }
        
        dest = buffer->ptr + dest_offset;
        memcpy(dest, source, bytes_to_copy * sizeof(char));
        if (dest_offset + bytes_to_copy > buffer->len)
            buffer->len = dest_offset + bytes_to_copy;

        dest_buf_num++;
        source += bytes_to_copy;
        self->pos += bytes_to_copy;
        bytes_left -= bytes_to_copy;
    }

    if (self->pos > self->len) {
        self->len = self->pos;
    }

    return true;
}

ByteBuf*
RAMFileDes_contents(RAMFileDes *self)
{
    ByteBuf *retval     = BB_new(self->len);
    VArray  *buffers    = self->buffers;
    u32_t    bytes_left = self->len;
    u32_t    i;
    

    for (i = 0; i < buffers->size; i++) {
        ByteBuf *const buffer = (ByteBuf*)VA_Fetch(buffers, i);
        if (bytes_left < buffer->len)
            buffer->len = bytes_left;
        BB_Cat_BB(retval, buffer);
        bytes_left -= buffer->len;
        if (!bytes_left)
            break;
    }

    return retval;
}

u64_t
RAMFileDes_fdlength(RAMFileDes *self)
{
    return self->len;
}

bool_t
RAMFileDes_fdclose(RAMFileDes *self)
{
    UNUSED_VAR(self);
    FileDes_open_count--;
    return true;
}

/* Copyright 2006-2007 Marvin Humphrey
 *
 * This program is free software; you can redistribute it and/or modify
 * under the same terms as Perl itself.
 */