// Copyright (c) 2024 Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)

internal LNK_ChunkRef
lnk_chunk_ref(U64 sect_id, U64 chunk_id)
{
  LNK_ChunkRef ref = {0};
  ref.sect_id      = sect_id;
  ref.chunk_id     = chunk_id;
  return ref;
}

internal B32
lnk_chunk_ref_is_equal(LNK_ChunkRef a, LNK_ChunkRef b)
{
  B32 is_equal = a.sect_id == b.sect_id && a.chunk_id == b.chunk_id;
  return is_equal;
}

internal LNK_ChunkNode *
lnk_chunk_list_push(Arena *arena, LNK_ChunkList *list, LNK_Chunk *chunk)
{
  LNK_ChunkNode *node = push_array_no_zero(arena, LNK_ChunkNode, 1);
  node->next = 0;
  node->data = chunk;

  SLLQueuePush(list->first, list->last, node);
  ++list->count;

  return node;
}

internal void
lnk_chunk_list_concat_in_place(LNK_ChunkList *list, LNK_ChunkList *to_concat)
{
  SLLConcatInPlace(list, to_concat);
}

internal void
lnk_chunk_list_concat_in_place_arr(LNK_ChunkList *list, LNK_ChunkList *arr, U64 count)
{
  SLLConcatInPlaceArray(list, arr, count);
}

internal LNK_ChunkList **
lnk_make_chunk_list_arr_arr(Arena *arena, U64 slot_count, U64 per_count)
{
  LNK_ChunkList **arr_arr = push_array_no_zero(arena, LNK_ChunkList *, slot_count);
  for (U64 i = 0; i < slot_count; i += 1) {
    arr_arr[i] = push_array(arena, LNK_ChunkList, per_count);
  }
  return arr_arr;
}

internal int
lnk_chunk_sort_index_is_before(void *raw_a, void *raw_b)
{
  // Grouped Sections (PE Format)
  //  "All contributions with the same object-section name are allocated contiguously in the image,
  //  and the blocks of contributions are sorted in lexical order by object-section name." 
  LNK_ChunkPtr *a = raw_a;
  LNK_ChunkPtr *b = raw_b;
          
  // sort on section postfix
  int cmp = str8_compar_case_sensitive(&(*a)->sort_idx, &(*b)->sort_idx);

  // sort on obj position on command line
  if (cmp == 0) {
    cmp = u64_compar(&(*a)->input_idx, &(*b)->input_idx);
  }

  int is_before = cmp < 0;
  return is_before;
}

internal void
lnk_chunk_array_sort(LNK_ChunkArray arr)
{
  radsort(arr.v, arr.count, lnk_chunk_sort_index_is_before);
}

internal LNK_ChunkManager *
lnk_chunk_manager_alloc(Arena *arena, U64 id, U64 align)
{
  ProfBeginFunction();

  LNK_ChunkList temp_list = {0};

  LNK_Chunk temp_chunk = {0};
  temp_chunk.ref       = lnk_chunk_ref(id, 0);
  temp_chunk.align     = align;
  temp_chunk.type      = LNK_Chunk_List;
  temp_chunk.u.list    = &temp_list;

  LNK_ChunkManager *cman  = push_array_no_zero(arena, LNK_ChunkManager, 1);
  cman->total_chunk_count = 1; // null chunk
  cman->root              = 0;
  cman->root              = lnk_chunk_push_list(arena, cman, &temp_chunk, str8(0,0));
  cman->root->align       = align;
  
  ProfEnd();
  return cman;
}

internal LNK_Chunk *
lnk_chunk_push_(Arena *arena, LNK_Chunk *parent, U64 chunk_id, String8 sort_index)
{
  ProfBeginFunction();

  Assert(parent->type == LNK_Chunk_List);
  LNK_ChunkList *list = parent->u.list;

  LNK_Chunk *chunk    = push_array_no_zero(arena, LNK_Chunk, 1);
  chunk->ref          = lnk_chunk_ref(parent->ref.sect_id, chunk_id);
  chunk->align        = 1;
  chunk->is_discarded = 0;
  chunk->sort_chunk   = 1;
  chunk->type         = LNK_Chunk_Null;
  chunk->sort_idx     = push_str8_copy(arena, sort_index);
  chunk->input_idx    = list->count;
  chunk->flags        = 0;
  chunk->associate    = 0;

  lnk_chunk_list_push(arena, list, chunk);

  ProfEnd();
  return chunk;
}

internal LNK_Chunk *
lnk_chunk_push(Arena *arena, LNK_ChunkManager *cman, LNK_Chunk *parent, String8 sort_index)
{
  U64 chunk_id = cman->total_chunk_count;
  ++cman->total_chunk_count;
  LNK_Chunk *chunk = lnk_chunk_push_(arena, parent, chunk_id, sort_index);
  return chunk;
}

internal LNK_Chunk *
lnk_chunk_push_leaf(Arena *arena, LNK_ChunkManager *cman, LNK_Chunk *parent, String8 sort_index, void *raw_ptr, U64 raw_size)
{
  LNK_Chunk *chunk = lnk_chunk_push(arena, cman, parent, sort_index);
  chunk->type      = LNK_Chunk_Leaf;
  chunk->u.leaf    = str8((U8 *)raw_ptr, raw_size);
  return chunk;
}

internal LNK_Chunk *
lnk_chunk_push_list(Arena *arena, LNK_ChunkManager *cman, LNK_Chunk *parent, String8 sort_index)
{
  LNK_Chunk *chunk = lnk_chunk_push(arena, cman, parent, sort_index);
  chunk->type      = LNK_Chunk_List;
  chunk->u.list    = push_array(arena, LNK_ChunkList, 1);
  return chunk;
}

internal LNK_ChunkNode *
lnk_chunk_deep_copy(Arena *arena, LNK_Chunk *chunk)
{
  ProfBeginFunction();
  Temp scratch = scratch_begin(&arena, 1);
  
  LNK_ChunkNode *dst_root_node = push_array_no_zero(arena, LNK_ChunkNode, 1);
  LNK_ChunkNode *src_root_node = push_array_no_zero(scratch.arena, LNK_ChunkNode, 1);
  src_root_node->next = 0;
  src_root_node->data = chunk;
  
  struct Stack {
    struct Stack  *next;
    LNK_ChunkNode *src_node;
    LNK_ChunkNode *dst_node;
  };
  struct Stack *stack = push_array_no_zero(scratch.arena, struct Stack, 1);
  stack->next         = 0;
  stack->src_node     = src_root_node;
  stack->dst_node     = dst_root_node;
  
  while (stack) {
    while (stack->src_node) {
      LNK_Chunk *src = stack->src_node->data;
      LNK_Chunk *dst = stack->dst_node->data;

      stack->src_node = stack->src_node->next;
      stack->dst_node = stack->dst_node->next;

      dst->ref          = src->ref;
      dst->type         = src->type;
      dst->align        = src->align;
      dst->is_discarded = src->is_discarded;
      dst->sort_chunk   = src->sort_chunk;
      dst->sort_idx     = push_str8_copy(arena, src->sort_idx);
      dst->input_idx    = src->input_idx;
      dst->flags        = src->flags;
      //dst->associate    = src->associate;
      Assert(src->associate == 0);
      lnk_chunk_set_debugf(arena, dst, "%S", src->debug);
    
      switch (src->type) {
      case LNK_Chunk_Null: break;
      case LNK_Chunk_Leaf: {
        B32 is_bss = src->u.leaf.str == 0;
        if (is_bss) {
          dst->u.leaf = src->u.leaf;
        } else {
          dst->u.leaf = push_str8_copy(arena, src->u.leaf);
        }
      } break;
      case LNK_Chunk_List: {
        LNK_ChunkNode *chain = 0;
        LNK_ChunkNode *curr  = 0;
        if (src->u.list->count > 0) {
          chain = push_array(arena, LNK_ChunkNode, src->u.list->count);
          curr = chain;
          for (U64 i = 1; i < src->u.list->count; ++i) {
            curr->next = &chain[i];
            curr = curr->next;
          }
          curr->next = 0;
        }

        dst->u.list        = push_array_no_zero(arena, LNK_ChunkList, 1);
        dst->u.list->count = src->u.list->count;
        dst->u.list->first = chain;
        dst->u.list->last  = curr;

        struct Stack *frame = push_array_no_zero(scratch.arena, struct Stack, 1);
        frame->next         = 0;
        frame->src_node     = src->u.list->first;
        frame->dst_node     = dst->u.list->first;
        SLLStackPush(stack, frame);
      } break;
      default: InvalidPath; break;
      }
    }
    
    SLLStackPop(stack);
  }
  
  scratch_end(scratch);
  ProfEnd();
  return dst_root_node;
}

internal LNK_ChunkNode *
lnk_merge_chunks(Arena *arena, LNK_ChunkManager *dst_cman, LNK_Chunk *dst, LNK_Chunk *src, U64 *id_map_out, U64 id_map_max)
{
  ProfBeginFunction();
  Temp scratch = scratch_begin(&arena, 0);

  Assert(src->ref.sect_id != dst->ref.sect_id);
  Assert(dst->type == LNK_Chunk_List);
  Assert(src->type != LNK_Chunk_Null);
  
  LNK_ChunkNode *src_node = push_array(arena, LNK_ChunkNode, 1);
  src_node->data = src;
  
  struct Stack {
    struct Stack *next;
    LNK_ChunkNode *node;
  };
  struct Stack *stack = push_array_no_zero(scratch.arena, struct Stack, 1);
  stack->next         = 0;
  stack->node         = src_node;
  
  while (stack) {
    while (stack->node) {
      LNK_Chunk *chunk = stack->node->data;
      
      // advance node
      stack->node = stack->node->next;
      
      // allocate id
      U64 new_id = dst_cman->total_chunk_count++;
      
      // write id map
      Assert(chunk->ref.chunk_id < id_map_max);
      id_map_out[chunk->ref.chunk_id] = new_id;
      
      // update id
      chunk->ref = lnk_chunk_ref(dst->ref.sect_id, new_id);
      
      // recurse down on lists
      if (chunk->type == LNK_Chunk_List) {
        struct Stack *frame = push_array_no_zero(scratch.arena, struct Stack, 1);
        frame->next         = 0;
        frame->node         = chunk->u.list->first;
        SLLStackPush(stack, frame);
      }
    }
    
    // reached end of chunk list, pop frame
    SLLStackPop(stack);
  }
  
  // move source root copy to destination section
  LNK_ChunkList *list = dst->u.list;
  ++list->count;
  SLLQueuePush(list->first, list->last, src_node);
  
  scratch_end(scratch);
  ProfEnd();
  return src_node;
}

internal void
lnk_chunk_associate(Arena *arena, LNK_Chunk *head, LNK_Chunk *chunk)
{
  // for simplicity we don't support multiple associations,
  // but it's possible to craft symbol table with multiple associations
  Assert(!chunk->associate);
  chunk->associate = head;
}

internal B32
lnk_chunk_is_discarded(LNK_Chunk *chunk)
{
  B32        is_discarded = chunk->is_discarded;
  LNK_Chunk *curr         = chunk->associate;
  while (!is_discarded && curr) {
    is_discarded = curr->is_discarded;
    curr = curr->associate;
  }
  return is_discarded;
}

internal U64
lnk_chunk_get_size(LNK_Chunk *chunk)
{
  U64 result = 0;
  switch (chunk->type) {
  case LNK_Chunk_Null: break;
  case LNK_Chunk_Leaf: { 
    result = chunk->u.leaf.size;
  } break;
  case LNK_Chunk_LeafArray:
  case LNK_Chunk_List: {
    Assert(!"TODO: list size");
  } break;
  }
  return result;
}

internal U64
lnk_chunk_list_get_node_count(LNK_Chunk *chunk)
{
  Assert(chunk->type == LNK_Chunk_List);
  return chunk->u.list->count;
}

internal void
lnk_chunk_pad_array_list_push(Arena *arena, Arena *scratch, LNK_ChunkPadArrayList *list, U64 cap, U64 align_off, U64 align_size)
{
  if (align_size > 0) {
    if (list->last == 0 || list->last->data.count >= list->last->cap) {
      LNK_ChunkPadArrayNode *node = push_array(scratch, LNK_ChunkPadArrayNode, 1);
      node->cap                     = cap;
      node->data.v                  = push_array_no_zero(arena, LNK_ChunkPad, cap);

      SLLQueuePush(list->first, list->last, node);
      ++list->count;
    }

    LNK_ChunkPadArray *last_array = &list->last->data;
    LNK_ChunkPad *align = &last_array->v[last_array->count++];
    align->off            = align_off;
    align->size           = align_size;
  }
}

internal
LNK_CHUNK_VISITOR_SIG(lnk_offset_chunks)
{
  LNK_OffsetChunks *offset_chunks = ud;
  U64               offset        = offset_chunks->offset;
  LNK_ChunkLayout  *layout        = offset_chunks->layout;

  layout->chunk_off_array[chunk->ref.chunk_id] += offset;

  return 0;
}

internal LNK_ChunkLayout
lnk_layout_from_chunk(Arena *arena, LNK_Chunk *root, U64 total_chunk_count)
{
  ProfBeginV("lnk_layout_from_chunk [total_chunk_count = %llu]", total_chunk_count);
  Temp scratch = scratch_begin(&arena, 1);
  
  LNK_ChunkLayout layout       = {0};
  layout.total_count           = total_chunk_count;
  layout.chunk_ptr_array       = push_array_no_zero(arena, LNK_ChunkPtr, total_chunk_count);
  layout.chunk_off_array       = push_array_no_zero(arena, U64,          total_chunk_count);
  layout.chunk_file_size_array = push_array_no_zero(arena, U64,          total_chunk_count);
  layout.chunk_virt_size_array = push_array_no_zero(arena, U64,          total_chunk_count);

  ProfBegin("Init Arrays");
  for (U64 i = 0; i < total_chunk_count; ++i) {
    layout.chunk_ptr_array[i] = &g_null_chunk;
  }
#if BUILD_DEBUG
  MemorySet(layout.chunk_off_array,       0xff, total_chunk_count * sizeof(layout.chunk_off_array));
  MemorySet(layout.chunk_file_size_array, 0xff, total_chunk_count * sizeof(layout.chunk_file_size_array));
  MemorySet(layout.chunk_virt_size_array, 0xff, total_chunk_count * sizeof(layout.chunk_virt_size_array));
#endif
  ProfEnd();

  // handle null chunk
  layout.chunk_off_array[0]       = 0;
  layout.chunk_file_size_array[0] = 0;
  layout.chunk_virt_size_array[0] = 0;

  // setup stack
  struct Stack {
    struct Stack  *next;
    LNK_ChunkArray chunk_array;
    U64            ichunk;
  };
  struct Stack *stack      = push_array(scratch.arena, struct Stack, 1);
  stack->chunk_array.count = 1;
  stack->chunk_array.v     = &root;

  U64                   pad_cap  = 4096;
  LNK_ChunkPadArrayList pad_list = {0};

  U64 cursor = 0;

  ProfBegin("Traverse chunks from root");
  for (; stack != 0; ) {
    for (; stack->ichunk < stack->chunk_array.count; ) {
      LNK_Chunk *chunk = stack->chunk_array.v[stack->ichunk++];
      
      // skip discarded chunk
      if (lnk_chunk_is_discarded(chunk)) {
        continue;
      }

      switch (chunk->type) {
      case LNK_Chunk_Leaf: {
        // push pad
        if (chunk->u.leaf.size < chunk->min_size) {
          U64 pad_size = chunk->min_size - chunk->u.leaf.size;
          lnk_chunk_pad_array_list_push(arena, scratch.arena, &pad_list, pad_cap, cursor, pad_size);
          cursor += pad_size;
        }

        // push align
        U64 align_size = AlignPadPow2(cursor, chunk->align);
        lnk_chunk_pad_array_list_push(arena, scratch.arena, &pad_list, pad_cap, cursor, align_size);
        cursor += align_size;
		
        // store id -> chunk
        Assert(chunk->ref.chunk_id < total_chunk_count);
        Assert(layout.chunk_ptr_array[chunk->ref.chunk_id] == &g_null_chunk);
        layout.chunk_ptr_array[chunk->ref.chunk_id] = chunk;

        // store id -> offset
        Assert(layout.chunk_off_array[chunk->ref.chunk_id] == max_U64);
        layout.chunk_off_array[chunk->ref.chunk_id] = cursor;

        // store id -> file size
        Assert(layout.chunk_file_size_array[chunk->ref.chunk_id] == max_U64);
        layout.chunk_file_size_array[chunk->ref.chunk_id] = chunk->u.leaf.size;
		
        // store id -> virt size
        Assert(layout.chunk_virt_size_array[chunk->ref.chunk_id] == max_U64);
        layout.chunk_virt_size_array[chunk->ref.chunk_id] = chunk->u.leaf.size;

        // advance
        cursor += chunk->u.leaf.size;
      } break;

      case LNK_Chunk_LeafArray: {
        // push align
        U64 align_size = AlignPadPow2(cursor, chunk->align);
        lnk_chunk_pad_array_list_push(arena, scratch.arena, &pad_list, pad_cap, cursor, align_size);
        cursor += align_size;

        // store id -> chunk
        Assert(chunk->ref.chunk_id < total_chunk_count);
        Assert(layout.chunk_ptr_array[chunk->ref.chunk_id] == &g_null_chunk);
        layout.chunk_ptr_array[chunk->ref.chunk_id] = chunk;

        // store id -> offset
        Assert(layout.chunk_off_array[chunk->ref.chunk_id] == max_U64);
        layout.chunk_off_array[chunk->ref.chunk_id] = cursor;

        // apply sort
        if (chunk->sort_chunk) {
          lnk_chunk_array_sort(*chunk->u.arr);
        }

        // recurse into sub chunks
        struct Stack *frame = push_array(scratch.arena, struct Stack, 1);
        frame->chunk_array  = *chunk->u.arr;
        SLLStackPush(stack, frame);
      } goto _continue;
      
      case LNK_Chunk_List: {
        // push align
        U64 align_size = AlignPadPow2(cursor, chunk->align);
        lnk_chunk_pad_array_list_push(arena, scratch.arena, &pad_list, pad_cap, cursor, align_size);
        cursor += align_size;

        // store id -> chunk
        Assert(chunk->ref.chunk_id < total_chunk_count);
        Assert(layout.chunk_ptr_array[chunk->ref.chunk_id] == &g_null_chunk);
        layout.chunk_ptr_array[chunk->ref.chunk_id] = chunk;

        // store id -> offset
        Assert(layout.chunk_off_array[chunk->ref.chunk_id] == max_U64);
        layout.chunk_off_array[chunk->ref.chunk_id] = cursor;

        // list -> array
        LNK_ChunkArray chunk_array = {0};
        chunk_array.v              = push_array_no_zero(scratch.arena, LNK_ChunkPtr, chunk->u.list->count);
        for (LNK_ChunkNode *cptr = chunk->u.list->first; cptr != 0; cptr = cptr->next) {
          chunk_array.v[chunk_array.count++] = cptr->data;
        }
        
        // apply sort
        if (chunk->sort_chunk) {
          lnk_chunk_array_sort(chunk_array);
        }
        
        // recurse into sub chunks
        struct Stack *frame = push_array(scratch.arena, struct Stack, 1);
        frame->chunk_array  = chunk_array;
        SLLStackPush(stack, frame);
      } goto _continue;
      
      case LNK_Chunk_Null: break;
      }
    }
    
    // terminate series
    if (stack->next) {
      // pop node chunk from stack
      struct Stack *prev = stack->next;

      Assert(prev->ichunk > 0);
      LNK_Chunk *chunk = prev->chunk_array.v[prev->ichunk-1];

      U64 chunk_data_off = layout.chunk_off_array[chunk->ref.chunk_id];
      Assert(chunk_data_off != max_U64);
      Assert(chunk_data_off <= cursor);

      U64 chunk_data_size = cursor - chunk_data_off;

      // store id -> virt size (no pad and align)
      Assert(layout.chunk_virt_size_array[chunk->ref.chunk_id] == max_U64);
      layout.chunk_virt_size_array[chunk->ref.chunk_id] = chunk_data_size;

      // push pad
      if (chunk_data_size < chunk->min_size) {
        U64 pad_size = chunk->min_size - chunk->u.leaf.size;
        lnk_chunk_pad_array_list_push(arena, scratch.arena, &pad_list, pad_cap, chunk_data_off, pad_size);

        LNK_OffsetChunks ud = {0};
        ud.offset           = pad_size;
        ud.layout           = &layout;
        lnk_visit_chunks(0, chunk, lnk_offset_chunks, &ud);
      }

      // align chunk end
      U64 align_size = AlignPadPow2(cursor, chunk->align);
      lnk_chunk_pad_array_list_push(arena, scratch.arena, &pad_list, pad_cap, cursor, align_size);
      cursor += align_size;

      chunk_data_size = cursor - chunk_data_off;

      // store id -> file size (pad + align)
      Assert(layout.chunk_file_size_array[chunk->ref.chunk_id] == max_U64);
      layout.chunk_file_size_array[chunk->ref.chunk_id] = chunk_data_size;
    }
    
    // move to next frame
    SLLStackPop(stack);
    
    _continue:;
  }
  ProfEnd();

  ProfBegin("Build Pad Array");
  layout.pad_array_count = 0;
  layout.pad_array       = push_array(arena, LNK_ChunkPadArray, pad_list.count);
  for (LNK_ChunkPadArrayNode *node = pad_list.first; node != 0; node = node->next) {
    layout.pad_array[layout.pad_array_count++] = node->data;
  }
  ProfEnd();

  scratch_end(scratch);
  ProfEnd();
  return layout;
}

internal LNK_ChunkLayout
lnk_build_chunk_layout(Arena *arena, LNK_ChunkManager *cman)
{
  ProfBeginFunction();
  LNK_ChunkLayout layout = lnk_layout_from_chunk(arena, cman->root, cman->total_chunk_count);
  ProfEnd();
  return layout;
}

internal
THREAD_POOL_TASK_FUNC(lnk_fill_chunks_task)
{
  ProfBeginFunction();

  LNK_ChunkLayoutSerializer *task   = raw_task;
  Rng1U64                    range  = task->ranges[task_id];
  LNK_ChunkLayout            layout = task->layout;
  String8                    buffer = task->buffer;

  for (U64 chunk_idx = range.min; chunk_idx < range.max; ++chunk_idx) {
    LNK_Chunk *chunk = layout.chunk_ptr_array[chunk_idx];

    if (lnk_chunk_is_discarded(chunk)) {
      continue;
    }

    if (chunk->type == LNK_Chunk_Leaf) {
      U64 off = layout.chunk_off_array[chunk->ref.chunk_id];
      Assert(off + chunk->u.leaf.size <= buffer.size);
      U8 *buffer_ptr = buffer.str + off;

      if (chunk->u.leaf.str == 0) {
        // zero out chunk bytes
        MemorySet(buffer_ptr, 0, chunk->u.leaf.size);
      } else {
        // copy chunk bytes
        MemoryCopy(buffer_ptr, chunk->u.leaf.str, chunk->u.leaf.size);
      }
    }
  }
  
  ProfEnd();
}

internal
THREAD_POOL_TASK_FUNC(lnk_fill_pads_task)
{
  ProfBeginFunction();

  LNK_ChunkLayoutSerializer *task      = raw_task;
  Rng1U64                    range     = task->ranges[task_id];
  LNK_ChunkLayout            layout    = task->layout;
  String8                    buffer    = task->buffer;
  U8                         fill_byte = task->fill_byte;

  for (U64 pad_array_idx = range.min; pad_array_idx < range.max; ++pad_array_idx) {
    LNK_ChunkPadArray pad_array = layout.pad_array[pad_array_idx];
    for (U64 pad_idx = 0; pad_idx < pad_array.count; ++pad_idx) {
      LNK_ChunkPad pad = pad_array.v[pad_idx];
      Assert(pad.off + pad.size <= buffer.size);
      MemorySet(buffer.str + pad.off, fill_byte, pad.size);
    }
  }

  ProfEnd();
}

internal void
lnk_serialize_chunk_layout(TP_Context *tp, LNK_ChunkLayout layout, String8 buffer, U8 fill_byte)
{
  ProfBeginFunction();
  Temp scratch = scratch_begin(0,0);

  LNK_ChunkLayoutSerializer task;
  task.layout    = layout;
  task.buffer    = buffer;
  task.fill_byte = fill_byte;

  ProfBeginV("Fill Chunks [Chunk Count %llu]", layout.total_count);
  task.ranges = tp_divide_work(scratch.arena, layout.total_count, tp->worker_count);
  tp_for_parallel(tp, 0, tp->worker_count, lnk_fill_chunks_task, &task);
  ProfEnd();

  ProfBeginV("Fill Pads [Array Count %llu]", layout.pad_array_count);
  task.ranges = tp_divide_work(scratch.arena, layout.pad_array_count, tp->worker_count);
  tp_for_parallel(tp, 0, tp->worker_count, lnk_fill_pads_task, &task);
  ProfEnd();

  scratch_end(scratch);
  ProfEnd();
}

internal B32
lnk_visit_chunks_(U64 sect_id, LNK_Chunk *chunk, LNK_ChunkVisitorSig *cb, void *ud)
{
  // visit chunk
  B32 is_done = cb(sect_id, chunk, ud);
  if (is_done) {
    return is_done;
  }
  
  switch (chunk->type) {
  case LNK_Chunk_Null:
  case LNK_Chunk_Leaf: {
    // reached leaf
  } break;
  case LNK_Chunk_LeafArray: {
    for (U64 idx = 0; idx < chunk->u.arr->count; idx += 1) {
      is_done = lnk_visit_chunks_(sect_id, chunk->u.arr->v[idx], cb, ud);
      if (is_done) {
        break;
      }
    }
  } break;
  case LNK_Chunk_List: {
    for (LNK_ChunkNode *i = chunk->u.list->first; i != 0; i = i->next) {
      is_done = lnk_visit_chunks_(sect_id, i->data, cb, ud);
      if (is_done) {
        break;
      }
    }
  } break;
  }

  return is_done;
}

internal void
lnk_visit_chunks(U64 sect_id, LNK_Chunk *chunk, LNK_ChunkVisitorSig *cb, void *ud)
{
  lnk_visit_chunks_(sect_id, chunk, cb, ud);
}

LNK_CHUNK_VISITOR_SIG(lnk_save_chunk_ptr)
{
  LNK_Chunk **id_map = (LNK_Chunk **)ud;
  if (!chunk->is_discarded) {
    id_map[chunk->ref.chunk_id] = chunk;
  }
  return 0;
}

internal LNK_ChunkPtr *
lnk_make_chunk_id_map(Arena *arena, LNK_ChunkManager *cman)
{
  LNK_ChunkPtr *map = push_array_no_zero(arena, LNK_ChunkPtr, cman->total_chunk_count);
  lnk_visit_chunks(0, cman->root, lnk_save_chunk_ptr, map);
  map[0] = &g_null_chunk;
  return map;
}

internal LNK_ChunkNode *
lnk_chunk_ptr_list_reserve(Arena *arena, LNK_ChunkList *list, U64 count)
{
  LNK_ChunkNode *arr = 0;
  if (count) {
    arr = push_array(arena, LNK_ChunkNode, count);
    LNK_Chunk *chunk_arr = push_array(arena, LNK_Chunk, count);
    for (U64 i = 0; i < count; i += 1) {
      arr[i].data = &chunk_arr[i];
      SLLQueuePush(list->first, list->last, &arr[i]);
    }
    list->count += count;
  }
  return arr;
}

internal String8Array
lnk_data_arr_from_chunk_ptr_list(Arena *arena, LNK_ChunkList list)
{
  String8Array arr = {0};
  arr.v            = push_array(arena, String8, list.count);
  for (LNK_ChunkNode *n = list.first; n != 0; n = n->next) {
    LNK_ChunkPtr c = n->data;
    Assert(c->type == LNK_Chunk_Leaf);
    arr.v[arr.count] = c->u.leaf;
    arr.count += 1;
  }
  return arr;
}

internal String8Array *
lnk_data_arr_from_chunk_ptr_list_arr(Arena *arena, LNK_ChunkList *list_arr, U64 count)
{
  String8Array *result = push_array(arena, String8Array, count);
  for (U64 i = 0; i < count; i += 1) {
    result[i] = lnk_data_arr_from_chunk_ptr_list(arena, list_arr[i]);
  }
  return result;
}

