// create_*...

Create_Package :: struct {
    data: String_Builder;

    entries: [..] Entry_Info;
    pool: Pool;  // Stores entry names and user data.
}

Entry_Info :: struct {
    name: string;
    data: [] u8;
    offset_from_start_of_file: s64;
    size_of_entry: s64;  // Equal to data.count if we hae the data, but we might not have the data if loading Table_Of_Contents only!
}

deinit :: (using package: *Create_Package) {
    free_buffers(*data);
    array_reset(*entries);
    release(*pool);
}


KNOWN_HEADER_SIZE :: 64;
#assert size_of(Package_Header) == KNOWN_HEADER_SIZE;

Package_Header :: struct {
    magic:     u32;
    version:   u32;
    flags:     u32;
    reserved0: u32;

    table_of_contents_offset: s64;
    
    reserved:  [5] u64;
}

Load_Package :: struct {
    header: Package_Header;
    data: [] u8;

    table_of_contents: Table_Of_Contents;

    entries: [] Entry_Info;

    lookup: Table(string, *Entry_Info);
}

KNOWN_TABLE_OF_CONTENTS_SIZE :: 64;
#assert size_of(Table_Of_Contents) == KNOWN_TABLE_OF_CONTENTS_SIZE;

Table_Of_Contents :: struct {
    table_of_contents_magic: u32;
    reserved0:               u32;
    
    number_of_entries:       s64;

    reserved:                [6] u64;
}

append_struct :: (builder: *String_Builder, to_append: *$T) {
    append(builder, cast(*u8) to_append, size_of(T));
}


init_from_memory :: (package: *Load_Package, data: [] u8, name_for_debugging := "") -> bool {
    #assert(TARGET_IS_LITTLE_ENDIAN);  // We can endian-swap the header later.

    if data.count < KNOWN_HEADER_SIZE {
        log_error("Package '%' is too small to even contain a header! (It's % bytes).\n", name_for_debugging, data.count);
        return false;  // Too small!
    }

    _header := cast(*Package_Header) data.data;
    package.header = (.*) _header;

    header_success := check_header(*package.header, name_for_debugging, data.count);
    if !header_success return false;

    offset := package.header.table_of_contents_offset;
    toc_start := data;
    toc_start.data  += offset;
    toc_start.count -= offset;
    assert(toc_start.count >= 0);
    
    toc_success := load_table_of_contents(package, toc_start, data.data, name_for_debugging, offset);

    return toc_success;
}


load_table_of_contents :: (package: *Load_Package, data: [] u8, base_file_data_if_file_is_loaded: *u8, name_for_debugging: string, offset_for_debugging: s64) -> bool {
    toc := cast(*Table_Of_Contents) data.data;
    if toc.table_of_contents_magic != TABLE_OF_CONTENTS_MAGIC {
        log_error("In package '%', we skipped to the supposed table_of_contents at offset %, but did not find the magic number there that we expected to find. (We wanted %, but got %).\n", name_for_debugging, offset_for_debugging, formatInt(TABLE_OF_CONTENTS_MAGIC, base=16), formatInt(toc.table_of_contents_magic, base=16));
        return false;
    }

    entries: [..] Entry_Info;
    array_resize(*entries, toc.number_of_entries);

    s: string;
    s.data  = data.data  + KNOWN_TABLE_OF_CONTENTS_SIZE;
    s.count = data.count - KNOWN_TABLE_OF_CONTENTS_SIZE;

    assert(s.count >= 0);
    
    for 0..toc.number_of_entries-1 {
        entry := *entries[it];

        if s.count < 4 {
            log_error("Table_Of_Contents Entry % is too short!\n", it+1);
            return false;
        }
        
        name_length: u32;
        get(*s, *name_length);

        entry.name.data  = s.data;
        entry.name.count = name_length;

        if s.count < name_length + 1 + 16 { // The 16 is for the chunk size and offset.
            log_error("Table_Of_Contents Entry % is too short!\n", it+1);
            return false;
        }

        advance(*s, name_length + 1);

        chunk_size:   s64;
        chunk_offset: s64;
        
        get(*s, *chunk_size);
        get(*s, *chunk_offset);

        if chunk_offset >= package.header.table_of_contents_offset {
            log_error("The offset_from_start_of_file for Entry % is out of range; it must be before the table_of_contents, which starts at %, but it was %.\n", it+1, package.header.table_of_contents_offset, chunk_offset);
            return false;
        }

        base := base_file_data_if_file_is_loaded;
        if base {
            entry.data.data = base + chunk_offset;
            entry.data.count = chunk_size;
        }

        entry.size_of_entry = chunk_size;
        entry.offset_from_start_of_file = chunk_offset;

        table_add(*package.lookup, entry.name, entry);
    }

    package.entries = entries;

    return true;
}

#scope_file

MAGIC :: #run (.*) cast(*u32) "simp".data;
TABLE_OF_CONTENTS_MAGIC :: #run (.*) cast(*u32) "toc!".data;

FILE_VERSION :: 1;
TARGET_IS_LITTLE_ENDIAN :: true;  // @Endian: Need a way to change this based on target CPU!

// @Copypasta from the game!
put :: (builder: *String_Builder, x: $T)
#modify {
    using Type_Info_Tag;
    
    ti := cast(*Type_Info) T;
    if ti.type == INTEGER return true;  // Accept integers.
    if ti.type == FLOAT   return true;  // Accept floats.
    if ti.type == BOOL    return true;  // Accept bools.
    if ti.type == ENUM    return true;  // Accept enums.

    return false;                       // Reject anything else.
}
{
    ensure_contiguous_space(builder, size_of(T));

    #if TARGET_IS_LITTLE_ENDIAN {
        // @Speed: Just write the target type!
        simple_memcpy(builder, x);
    } else {
        // @Incomplete: Do a slow-path if we know we are not little-endian. This can be generated by #run_and_insert?
        assert(false);
    }
}

get :: (s: *string, x: *$T)
#modify {  // @Cutnpaste from put
    using Type_Info_Tag;
    
    ti := cast(*Type_Info) T;
    if ti.type == INTEGER return true;  // Accept integers.
    if ti.type == FLOAT   return true;  // Accept floats.
    if ti.type == BOOL    return true;  // Accept bools.
    if ti.type == ENUM    return true;  // Accept enums.

    return false;                  // Reject anything else.
}
{
    assert(s.count >= size_of(T));

    if TARGET_IS_LITTLE_ENDIAN {
        memcpy(x, s.data, size_of(T));
    } else {
        // @Incomplete: Do a slow-path if we know we are not little-endian. This can be generated by #run_and_insert?
        assert(false);
    }

    s.data  += size_of(T);
    s.count -= size_of(T);
}

check_header :: (header: *Package_Header, name: string, file_size_in_bytes_or_minus_1: s64) -> bool {
    using header;

    if magic != MAGIC {
        log_error("Package '%' has an incorrect magic number! (Wanted %, got %).\n", name, formatInt(MAGIC, base=16), formatInt(magic, base=16));
        return false;
    }

    if version > FILE_VERSION {  // 'version' is unsigned, so, don't check < 0.
        log_error("Package '%' has an invalid version number, or a version created by code that is newer than we are, so we don't understand the format. (Its version is %; our highest known version is %).\n", name, version, FILE_VERSION);
        return false;
    }

    if file_size_in_bytes_or_minus_1 >= 0 {
        size := file_size_in_bytes_or_minus_1;
        if table_of_contents_offset + KNOWN_TABLE_OF_CONTENTS_SIZE >= size {
            log_error("Package '%' has an invalid table_of_contents_offset; it claims to be %, but the file is only % bytes long.\n", name, table_of_contents_offset, size);
            return false;
        }
    }
    
    return true;
}

#import "Basic";
#import "Hash_Table";
#import "Pool";
