/*

Asset manifest operations
-Allen
06.06.2016

*/

// TOP

inline rptr32
to_rptr32(void *pos, void *base){
    rptr32 result = (rptr32)((char*)pos - (char*)base);
    return(result);
}

inline void*
to_ptr(rptr32 pos, void *base){
    void *result = ((char*)base + pos);
    return(result);
}

inline Asset_Node*
to_node_ptr(rptr32 pos, void *base){
    Asset_Node *result = (Asset_Node *)((char*)base + pos);
    return(result);
}

inline Asset_Node*
get_node(i32 image_id, Asset_Manifest *manifest){
    Asset_Node *node = to_node_ptr(manifest->asset_nodes, manifest);
    node += image_id - 1;
    return(node);
}

inline i32
get_image_id(rptr32 pos, Asset_Manifest *manifest){
    i32 dist = (pos - manifest->asset_nodes);
    Assert(dist % sizeof(Asset_Node) == 0);
    dist = 1 + (dist/sizeof(Asset_Node));
    return(dist);
}

void
init_sentinel(Asset_Node *node, void *base){
    rptr32 self = to_rptr32(node, base);
    node->next_sibling = self;
    node->prev_sibling = self;
}

void
insert_node(Asset_Node *node, Asset_Node *pos, void *base){
    rptr32 node_self = to_rptr32(node, base);
    rptr32 pos_self = to_rptr32(pos, base);
    rptr32 next_self = pos->next_sibling;
    Asset_Node *next = to_node_ptr(next_self, base);
    
    node->prev_sibling = pos_self;
    node->next_sibling = next_self;
    
    pos->next_sibling = node_self;
    next->prev_sibling = node_self;
}

void
remove_node(Asset_Node *node, void *base){
    rptr32 next_self = node->next_sibling;
    rptr32 prev_self = node->prev_sibling;
    Asset_Node *next = to_node_ptr(next_self, base);
    Asset_Node *prev = to_node_ptr(prev_self, base);
    
    next->prev_sibling = prev_self;
    prev->next_sibling = next_self;
}

void
sibling_insert_before(Asset_Node *node, Asset_Node *pos, void *base){
    rptr32 node_self = to_rptr32(node, base);
    rptr32 pos_self = to_rptr32(pos, base);
    rptr32 prev_self = pos->prev_sibling;
    Asset_Node *prev = to_node_ptr(prev_self, base);
    
    node->parent = pos->parent;
    
    node->next_sibling = pos_self;
    node->prev_sibling = prev_self;
    
    pos->prev_sibling = node_self;
    prev->next_sibling = node_self;
}

void
sibling_insert_after(Asset_Node *node, Asset_Node *pos, void *base){
    rptr32 node_self = to_rptr32(node, base);
    rptr32 pos_self = to_rptr32(pos, base);
    rptr32 next_self = pos->next_sibling;
    Asset_Node *next = to_node_ptr(next_self, base);
    
    node->parent = pos->parent;
    
    node->prev_sibling = pos_self;
    node->next_sibling = next_self;
    
    pos->next_sibling = node_self;
    next->prev_sibling = node_self;
}

void
tree_remove(Asset_Node *node, void *base){
    rptr32 parent_self = node->parent;
    rptr32 node_self = to_rptr32(node, base);
    rptr32 next_self = node->next_sibling;
    rptr32 prev_self = node->prev_sibling;
    Asset_Node *parent = to_node_ptr(parent_self, base);
    Asset_Node *next = to_node_ptr(next_self, base);
    Asset_Node *prev = to_node_ptr(prev_self, base);
    
    next->prev_sibling = prev_self;
    prev->next_sibling = next_self;
    
    if (parent->first_child == node_self){
        if (next_self != node_self){
            parent->first_child = next_self;
        }
        else{
            parent->first_child = 0;
        }
    }
}

void
insert_under(Asset_Node *node, Asset_Node *parent, void *base, b32 insert_as_first){
    if (parent->first_child == 0){
        rptr32 node_self = to_rptr32(node, base);
        rptr32 parent_self = to_rptr32(parent, base);
        
        parent->first_child = node_self;
        node->next_sibling = node_self;
        node->prev_sibling = node_self;
        node->parent = parent_self;
    }
    else{
        Asset_Node *first = to_node_ptr(parent->first_child, base);
        sibling_insert_before(node, first, base);
        
        if (insert_as_first){
            rptr32 node_self = to_rptr32(node, base);
            parent->first_child = node_self;
        }
    }
}

void
add_free_nodes(Asset_Manifest *manifest, Wrapped_Partition *part){
    i32 node_count = (manifest->part.max - manifest->part.pos) / sizeof(Asset_Node);
    
    if (node_count > 0){
        Asset_Node *nodes = push_array(part, Asset_Node, node_count);
        
        i32 j = manifest->asset_node_count + 1;
        
        if (manifest->asset_nodes == 0){
            manifest->asset_nodes = to_rptr32(nodes, manifest);
        }
        manifest->asset_node_count += node_count;
        manifest->asset_free_count += node_count;
        
        Asset_Node *node = nodes;
        for (i32 i = 0; i < node_count; ++i, ++j, ++node){
            insert_node(node, &manifest->free_sentinel, manifest);
            node->image_id = j;
            node->name[0] = 0;
        }
    }
}

void
initialize_empty_manifest(void *manifest_memory, i32 size){
    Asset_Manifest *manifest = (Asset_Manifest*)manifest_memory;
    manifest->version = ASSET_MANIFEST_VERSION;
    
    manifest->part = make_relative_partition(size);
    
    Wrapped_Partition part_ = make_wrapped_partition(&manifest->part, manifest_memory);
    Wrapped_Partition *part = &part_;
    
    push_type(part, Asset_Manifest);
    
    init_sentinel(&manifest->free_sentinel, manifest_memory);
    
    manifest->asset_node_count = 0;
    manifest->asset_free_count = 0;
    
    add_free_nodes(manifest, part);
}

#define MANIFEST_NAME "CDmanifest"

Asset_Manifest*
manifest_load(System_API *system, Partition *part){
    void *result = 0;
    
    Temp_Memory temp = begin_temp(part);
    File_Dump dump = system->DBG_dump_begin(MANIFEST_NAME);
    
    if (dump.size > 0){
        result = push_block(part, dump.size);
        
        if (result == 0){
#ifdef DEVELOPER
            DBG_expand_partition(system, part, dump.size);
            result = push_block(part, dump.size);
#else
            InvalidCodePath;
#endif
        }
        
        if (!system->DBG_dump_end(dump, result)){
            end_temp(temp);
            result = 0;
        }
    }
    
    return((Asset_Manifest*)result);
}

#ifdef DEVELOPER
void
manifest_dump(System_API *system, void *base){
    Asset_Manifest *manifest = (Asset_Manifest*)base;
    i32 memory_size = manifest->part.max;
    system->DBG_dump_out(MANIFEST_NAME, base, memory_size);
}
#endif

void
allocate_node(Asset_Manifest *manifest,
              Asset_Node *node,
              char *filename, i32 len){
    --manifest->asset_free_count;
    
    remove_node(node, manifest);
    cd_memcpy(node->name, filename, len+1);
    
    node->first_child = 0;
    node->next_sibling = 0;
    node->prev_sibling = 0;
}

void
free_node(Asset_Manifest *manifest,
          Asset_Node *node){
    
    ++manifest->asset_free_count;
    
    insert_node(node, &manifest->free_sentinel, manifest);
    cd_memcpy(node->name, "", 0);
}

rptr32
declare_image(void *manifest_memory, char *filename, i32 image_id, i32 expect_empty){
    rptr32 result = 0;
    i32 len = 0;
    
    len = cd_strlen(filename);
    
    if (len != 0 && len < ASSET_MAX_NAME){
        Asset_Manifest *manifest = (Asset_Manifest*)manifest_memory;
        Asset_Node *nodes = (Asset_Node*)
            to_ptr(manifest->asset_nodes, manifest_memory);
        Asset_Node *node = nodes + (image_id - 1);
        
        if (node->name[0] == 0 && expect_empty){
            allocate_node(manifest, node, filename, len);
            result = to_rptr32(node, manifest_memory);
        }
        else if (node->name[0] != 0 && !expect_empty){
            cd_memcpy(node->name, filename, len+1);
            result = to_rptr32(node, manifest_memory);
        }
    }
    
    return(result);
}

rptr32
bind_image(void *manifest_memory, Manifest_Setup *setup, char *filename, i32 image_id){
    rptr32 result = declare_image(manifest_memory, filename, image_id, true);
    
    if (setup->count > 0){
        Asset_Node *node = to_node_ptr(result, manifest_memory);
        Asset_Node *parent = to_node_ptr(setup->parents[setup->count-1], manifest_memory);
        
        insert_under(node, parent, manifest_memory, false);
        node->type = AssetType_Image;
    }
    
    return(result);
}

rptr32
replace_image(void *manifest_memory, char *filename, i32 image_id){
    return(declare_image(manifest_memory, filename, image_id, false));
}

void
begin_folder(void *manifest_memory, Manifest_Setup *setup, char *name, i32 image_id){
    Assert(setup->count < ArrayCount(setup->parents));
    
    rptr32 node_self = bind_image(manifest_memory, setup, name, image_id);
    setup->parents[setup->count++] = node_self;
    
    Asset_Node *node = to_node_ptr(node_self, manifest_memory);
    node->type = AssetType_GenericFolder;
}

void
end_folder(void *manifest_memory, Manifest_Setup *setup){
    Assert(setup->count > 0);
    --setup->count;
}

int
new_image(void *manifest_memory, char *filename){
    i32 result = 0;
    
    i32 len = cd_strlen(filename);
    
    if (len != 0 && len < ASSET_MAX_NAME){
        Asset_Manifest *manifest = (Asset_Manifest*)manifest_memory;
        Asset_Node *node =
            to_node_ptr(manifest->free_sentinel.next_sibling, manifest_memory);
        if (node != &manifest->free_sentinel){
            Assert(node->name[0] == 0);
            allocate_node(manifest, node, filename, len);
            result = 1;
        }
    }
    
    return(result);
}

void
delete_image(void *manifest_memory, i32 image_id){
    Asset_Manifest *manifest = (Asset_Manifest*)manifest_memory;
    Asset_Node *nodes = to_node_ptr(manifest->asset_nodes, manifest_memory);
    Asset_Node *node = nodes + (image_id - 1);
    
    if (node->name[0] != 0){
        free_node(manifest, node);
    }
}

void
move_manifest_to_bigger_block(void *manifest_memory, void *new_memory, i32 size){
    Asset_Manifest *manifest = (Asset_Manifest*)manifest_memory;
    
    Assert(manifest->part.max < size);
    
    if (new_memory != manifest_memory){
        cd_memcpy(new_memory, manifest_memory, manifest->part.max);
    }
    manifest = (Asset_Manifest*)new_memory;
    manifest->part.max = size;
    
    Wrapped_Partition part = make_wrapped_partition(&manifest->part, manifest_memory);
    add_free_nodes(manifest, &part);
}

#ifdef DEVELOPER
Asset_Node*
get_available_node(System_API *system, Partition *manifest_part, Asset_Manifest **manifest_memory_ptr){
    Asset_Manifest *manifest = (Asset_Manifest*)manifest_part->base;
    
    rptr32 node_self = manifest->free_sentinel.next_sibling;
    Asset_Node *result = to_node_ptr(node_self, manifest);
    
    if (result == &manifest->free_sentinel){
        i32 pos = manifest_part->rel_part.pos;
        Asset_Manifest *new_manifest = (Asset_Manifest*)push_block(manifest_part, pos);
        if (new_manifest == 0){
            DBG_expand_partition(system, manifest_part, pos);
            new_manifest = (Asset_Manifest*)push_block(manifest_part, pos);
            Assert(new_manifest);
            manifest = (Asset_Manifest*)manifest_part->base;
            *manifest_memory_ptr = (Asset_Manifest*)manifest_part->base;
        }
        move_manifest_to_bigger_block(manifest, manifest, manifest_part->rel_part.pos);
        
        node_self = manifest->free_sentinel.next_sibling;
        result = to_node_ptr(node_self, manifest);
        Assert(result != &manifest->free_sentinel);
    }
    
    allocate_node(manifest, result, "New", 3);
    
    return(result);
}
#endif

Asset_Walker_Entry
new_walker_entry(rptr32 first, i32 level){
    Asset_Walker_Entry result;
    result.first = first;
    result.current = first;
    result.level = level;
    return(result);
}

Asset_Node*
walk_first_asset_node(void *manifest_memory, Asset_Walker *walker){
    Asset_Manifest *manifest = (Asset_Manifest*)manifest_memory;
    Asset_Node *nodes = to_node_ptr(manifest->asset_nodes, manifest_memory);
    Asset_Node *node = nodes + (ROOT - 1);
    
    walker->current_level = 0;
    
    if (node->first_child){
        walker->stack[walker->top++] = new_walker_entry(node->first_child, 1);
    }
    
    return(node);
}

Asset_Node*
walk_next_asset_node(void *manifest_memory, Asset_Walker *walker){
    Asset_Node *node = 0;
    
    if (walker->top != 0){
        walker->current_level = walker->stack[walker->top-1].level;
    }
    
    if (walker->top > 0){
        Asset_Walker_Entry *top = walker->stack + (walker->top - 1);
        
        node = to_node_ptr(top->current, manifest_memory);
        
        top->current = node->next_sibling;
        
        if (top->current == top->first){
            --walker->top;
        }
        
        if (node->first_child){
            walker->stack[walker->top++] =
                new_walker_entry(node->first_child, top->level + 1);
        }
    }
    
    return(node);
}

Asset_Node*
walk_skip_children_asset_node(void *manifest_memory, Asset_Walker *walker){
    Asset_Node *node = 0;
    
    if (walker->top > 0){
        Asset_Walker_Entry *top = walker->stack + (walker->top - 1);
        
        while (top->level > walker->current_level){
            --walker->top;
            if (walker->top > 0){
                top = walker->stack + (walker->top - 1);
            }
            else{
                break;
            }
        }
        
        node = walk_next_asset_node(manifest_memory, walker);
    }
    
    return(node);
}

// BOTTOM