#define _LUNA_SYSTEM_ERROR_EXTENSIONS
#include <luna/Alignment.h>
#include <luna/Alloc.h>
#include <luna/CString.h>
#include <luna/NumberParsing.h>
#include <luna/TarStream.h>

TarStream::TarStream(void* base, usize size) : m_base(base), m_pos(base), m_size(size)
{
}

void TarStream::initialize(void* base, usize size)
{
    m_base = m_pos = base;
    m_size = size;
    m_offset = 0;
}

void TarStream::rewind()
{
    m_pos = m_base;
    m_offset = 0;
}

Result<void> TarStream::read_header(TarHeader* out)
{
    if ((m_offset + 512) > m_size) return err(0);
    memcpy(out, m_pos, sizeof(TarHeader));
    m_pos = offset_ptr(m_pos, 512);
    m_offset += 512;
    return {};
}

Result<TarStream::Entry> TarStream::parse_header(const TarStream::TarHeader* hdr)
{
    Entry entry;

    char size[13];
    nullcpy(size, hdr->size, 12);
    entry.size = parse_unsigned_integer(size, nullptr, 8);

    entry.pos = m_offset;

    switch (hdr->typeflag)
    {
    case '\0':
    case '0': entry.type = EntryType::RegularFile; break;
    case '5': entry.type = EntryType::Directory; break;
    default: return err(EFIXME);
    }

    if (!strlen(hdr->prefix)) { nullcpy(entry.name, hdr->name, 100); }
    else { return err(EFIXME); }

    if (entry.size)
    {
        m_pos = offset_ptr(m_pos, align_up<512ul>(entry.size));
        m_offset += align_up<512ul>(entry.size);
    }

    return entry;
}

Result<void> TarStream::find_valid_header(TarStream::TarHeader* out, bool& success)
{
    success = false;
    while (true)
    {
        TRY(read_header(out));
        if (!memcmp(out->magic, "ustar", 5))
        {
            success = true;
            return {};
        }
    }
}

// FIXME: How do we know whether an error is an error or just EOF? Returning an error code of 0 as EOF seems a bit
// clunky to me...
Result<TarStream::Entry> TarStream::read_next_entry()
{
    bool success;
    TarHeader header;
    auto rc = find_valid_header(&header, success);
    if (!success) return rc.release_error();

    return parse_header(&header);
}

usize TarStream::read_contents(const Entry& entry, char* buf, usize offset, usize length)
{
    if (offset >= entry.size) return 0;
    if ((length + offset) > entry.size) length = entry.size - offset;

    memcpy(buf, offset_ptr(m_base, entry.pos + offset), length);

    return length;
}

Result<OwnedStringView> TarStream::read_contents_as_string(const Entry& entry, usize offset, usize max)
{
    char* buf = TRY(make_array<char>(max + 1));

    usize nread = read_contents(entry, buf, offset, max);

    buf[nread] = 0;

    return OwnedStringView{buf};
}