#include "exefmt/pe/mr4th_pe.h"
#include "exefmt/elf/mr4th_elf.h"

////////////////////////////////
// Tinker Functions

// tinker object read/write

MR4TH_SYMBOL TNK_Object*
tnk_object_new(void){
  Arena *arena = arena_alloc();
  TNK_Object *obj = push_array(arena, TNK_Object, 1);
  obj->arena = arena;
  buffer_alloc(&obj->strcons, MB(64));
  hash_buckets_init(&obj->strcons, 512);
  buffer_alloc(&obj->nodes.buffer, TNK_MAX_NODE_COUNT*sizeof(TNK__Node));
  return(obj);
}

MR4TH_SYMBOL void
tnk_object_release(TNK_Object *obj){
  buffer_release(&obj->strcons);
  buffer_release(&obj->nodes.buffer);
  arena_release(obj->arena);
}

MR4TH_SYMBOL TNK_ID
tnk_node_new(TNK_Object *obj, TNK_NodeKind kind){
  TNK__Node *node = obj->free_nodes;
  if (node != 0){
    SLLStackPop(obj->free_nodes);
  }
  else if (obj->node_alloc_pos < TNK_MAX_NODE_COUNT){
    buffer_typed_commit_idx(&obj->nodes, obj->node_alloc_pos + 1);
    node = obj->nodes.ptr + obj->node_alloc_pos;
    obj->node_alloc_pos += 1;
  }
  
  TNK_ID id = {0};
  if (node != 0){
    DLLPushBack(obj->node_first[kind], obj->node_last[kind], node);
    node->gen += 1;
    node->kind = kind;
    MemoryZeroStruct(&node->name);
    node->flags = 0;
    MemoryZeroStruct(&node->u);
    obj->node_count[kind] += 1;
    id = tnk__id_from_ptr(obj, node);
  }
  
  return(id);
}

MR4TH_SYMBOL void
tnk_node_delete(TNK_Object *obj, TNK_ID node){
  TNK__Node *ptr = tnk__ptr_from_id(obj, node);
  if (ptr != 0 && 0 < ptr->kind && ptr->kind < TNK_NodeKind_COUNT){
    DLLRemove(obj->node_first[ptr->kind], obj->node_last[ptr->kind], ptr);
    obj->node_count[ptr->kind] -= 1;
    if (ptr->kind == TNK_NodeKind_Section){
      tnk__buffer_free(obj, ptr->u.section.buffer);
    }
    ptr->gen += 1;
    ptr->kind = 0;
    SLLStackPush(obj->free_nodes, ptr);
  }
}

MR4TH_SYMBOL TNK_ID
tnk_node_first(TNK_Object *obj, TNK_NodeKind kind){
  TNK_ID result = {0};
  if (0 < kind && kind < TNK_NodeKind_COUNT){
    result = tnk__id_from_ptr(obj, obj->node_first[kind]);
  }
  return(result);
}

MR4TH_SYMBOL TNK_ID
tnk_node_next(TNK_Object *obj, TNK_ID node){
  TNK_ID result = {0};
  TNK__Node *ptr = tnk__ptr_from_id(obj, node);
  if (ptr != 0){
    result = tnk__id_from_ptr(obj, ptr->next);
  }
  return(result);
}

/*
** Section: { name, flags, --------, ------, data }
** Symbol:  { name, flags, position, ------, ---- }
** Reloc:   { ----, flags, position, symbol, ---- }
*/

MR4TH_SYMBOL String8
tnk_node_get_name(TNK_Object *obj, TNK_ID node){
  String8 result = {0};
  TNK__Node *ptr = tnk__ptr_from_id(obj, node);
  if (ptr != 0){
    result = ptr->name;
  }
  return(result);
}

MR4TH_SYMBOL U32
tnk_node_get_flags(TNK_Object *obj, TNK_ID node){
  U32 result = 0;
  TNK__Node *ptr = tnk__ptr_from_id(obj, node);
  if (ptr != 0){
    result = ptr->flags;
  }
  return(result);
}

MR4TH_SYMBOL void
tnk_node_get_position(TNK_Object *obj, TNK_ID node, TNK_Position *out){
  TNK__Node *ptr = tnk__ptr_from_id(obj, node);
  if (ptr != 0){
    switch (ptr->kind){
      case TNK_NodeKind_Symbol: {
        out->section = ptr->u.symbol.section;
        out->offset = ptr->u.symbol.offset;
      }break;
      case TNK_NodeKind_Reloc: {
        out->section = ptr->u.reloc.section;
        out->offset = ptr->u.reloc.offset;
      }break;
    }
  }
}

MR4TH_SYMBOL TNK_ID
tnk_node_get_symbol(TNK_Object *obj, TNK_ID node){
  TNK_ID result = {0};
  TNK__Node *ptr = tnk__ptr_from_id(obj, node);
  if (ptr != 0 && ptr->kind == TNK_NodeKind_Reloc){
    result = ptr->u.reloc.symbol;
  }
  return(result);
}

MR4TH_SYMBOL String8
tnk_node_read_data(TNK_Object *obj, TNK_ID node){
  String8 result = {0};
  TNK__Node *ptr = tnk__ptr_from_id(obj, node);
  if (ptr != 0 && ptr->kind == TNK_NodeKind_Section){
    if (ptr->u.section.buffer != 0){
      result = tnk__buffer_read(ptr->u.section.buffer);
    }
  }
  return(result);
}

MR4TH_SYMBOL void
tnk_node_set_name(TNK_Object *obj, TNK_ID node, String8 name){
  TNK__Node *ptr = tnk__ptr_from_id(obj, node);
  if (ptr != 0){
    ptr->name = tnk__str_cons(obj, name);
  }
}

MR4TH_SYMBOL void
tnk_node_set_flags(TNK_Object *obj, TNK_ID node, U32 flags){
  TNK__Node *ptr = tnk__ptr_from_id(obj, node);
  if (ptr != 0){
    ptr->flags = flags;
  }
}

MR4TH_SYMBOL void
tnk_node_set_position(TNK_Object *obj, TNK_ID node, TNK_ID section, U32 offset){
  TNK__Node *ptr = tnk__ptr_from_id(obj, node);
  if (ptr != 0){
    switch (ptr->kind){
      case TNK_NodeKind_Symbol: {
        ptr->u.symbol.section = section;
        ptr->u.symbol.offset = offset;
      }break;
      case TNK_NodeKind_Reloc: {
        ptr->u.reloc.section = section;
        ptr->u.reloc.offset = offset;
      }break;
    }
  }
}

MR4TH_SYMBOL void
tnk_node_set_symbol(TNK_Object *obj, TNK_ID node, TNK_ID symbol){
  TNK__Node *ptr = tnk__ptr_from_id(obj, node);
  if (ptr != 0 && ptr->kind == TNK_NodeKind_Reloc){
    ptr->u.reloc.symbol = symbol;
  }
}

MR4TH_SYMBOL void
tnk_node_data_edit(TNK_Object *obj, TNK_ID node, U32 first, U32 opl, String8 data){
  TNK__Node *ptr = tnk__ptr_from_id(obj, node);
  if (ptr != 0 && ptr->kind == TNK_NodeKind_Section){
    // use buffer (allocating if necessary)
    TNK__Buffer *buffer = ptr->u.section.buffer;
    if (buffer == 0){
      buffer = ptr->u.section.buffer = tnk__buffer_alloc(obj);
    }
    
    // offset calculations
    S64 opl_c = ClampTop(opl, buffer->size);
    S64 first_c = ClampTop(first, opl_c);
    S64 insert_size = (S64)data.size;
    S64 delta = insert_size - (opl_c - first_c);
    S64 new_size = buffer->size + delta;
    Assert(0 <= new_size && new_size <= GB(1));
    
    // apply edit to buffer
    buffer_commit_off(&buffer->data.buffer, new_size);
    MemoryCopy(buffer->data.ptr + opl_c + delta,
               buffer->data.ptr + opl_c, (opl_c - first_c));
    MemoryCopy(buffer->data.ptr + first_c, data.str, data.size);
    buffer->size = (U32)new_size;
    
    // update symbol locations
    for (TNK__Node *symbol_ptr = obj->node_first[TNK_NodeKind_Symbol];
         symbol_ptr != 0; symbol_ptr = symbol_ptr->next){
      if (symbol_ptr->u.symbol.section.xu64 == node.xu64){
        U32 offset = symbol_ptr->u.symbol.offset;
        if (first <= offset && offset < opl){
          offset = first;
        }
        else if (opl <= offset){
          offset += delta;
        }
      }
    }
    
    // update reloc locations
    for (TNK__Node *reloc_ptr = obj->node_first[TNK_NodeKind_Reloc];
         reloc_ptr != 0; reloc_ptr = reloc_ptr->next){
      if (reloc_ptr->u.reloc.section.xu64 == node.xu64){
        U32 offset = reloc_ptr->u.reloc.offset;
        if (first <= offset && offset < opl){
          offset = first;
        }
        else if (opl <= offset){
          offset += delta;
        }
      }
    }
    
  }
}

MR4TH_SYMBOL TNK_ID
tnk__id_from_ptr(TNK_Object *obj, TNK__Node *ptr){
  TNK_ID id = {0};
  if (obj->nodes.ptr <= ptr && ptr < obj->nodes.ptr + obj->node_alloc_pos){
    id.idx = (ptr - obj->nodes.ptr);
    id.gen = ptr->gen;
  }
  return(id);
}

MR4TH_SYMBOL TNK__Node*
tnk__ptr_from_id(TNK_Object *obj, TNK_ID id){
  TNK__Node *ptr = 0;
  if (id.idx < obj->node_alloc_pos){
    ptr = &obj->nodes.ptr[id.idx];
    if (ptr->gen != id.gen){
      ptr = 0;
    }
  }
  return(ptr);
}

MR4TH_SYMBOL String8
tnk__str_cons(TNK_Object *obj, String8 string){
  String8 result = {0};
  if (string.size > 0){
    U64 hash = str8_hash(string);
    for (HASH_Node *hash_node = hash_buckets_first(&obj->strcons, hash);
         hash_node != 0; hash_node = hash_node->next){
      if (hash_node->hash == hash){
        String8 *s = (String8*)PtrFromInt(hash_node->val);
        if (str8_match(*s, string, 0)){
          result = *s;
          break;
        }
      }
    }
    
    if (result.size == 0){
      String8 *s = push_array(obj->arena, String8, 1);
      result = *s = str8_push_copy(obj->arena, string);
      
      obj->strcons_count += 1;
      hash_buckets_expand(&obj->strcons, obj->strcons_count);
      hash_buckets_insert(obj->arena, &obj->strcons, hash, IntFromPtr(s));
    }
  }
  return(result);
}

MR4TH_SYMBOL TNK__Buffer*
tnk__buffer_alloc(TNK_Object *obj){
  TNK__Buffer *buffer = obj->free_buffers;
  if (buffer != 0){
    SLLStackPop(obj->free_buffers);
  }
  else{
    buffer = push_array(obj->arena, TNK__Buffer, 1);
  }
  buffer->size = 0;
  buffer_alloc(&buffer->data.buffer, GB(1));
  return(buffer);
}

MR4TH_SYMBOL void
tnk__buffer_free(TNK_Object *obj, TNK__Buffer *buffer){
  buffer_release(&buffer->data.buffer);
  buffer->next = 0;
  SLLStackPush(obj->free_buffers, buffer);
}

MR4TH_SYMBOL String8
tnk__buffer_read(TNK__Buffer *buffer){
  String8 result = {0};
  if (buffer != 0){
    result.str = buffer->data.ptr;
    result.size = buffer->size;
  }
  return(result);
}

// generate

MR4TH_SYMBOL String8
tnk_generate(Arena *arena, TNK_Object *obj, TNK_Format format){
  String8 result = {0};
  switch (format){
    case TNK_Format_COFF: {
      result = tnk_generate_coff(arena, obj);
    }break;
    case TNK_Format_ELF: {
      result = tnk_generate_elf(arena, obj);
    }break;
  }
  return(result);
}

MR4TH_SYMBOL String8
tnk_generate_coff(Arena *arena, TNK_Object *obj){
  ArenaTemp scratch = arena_get_scratch(&arena, 1);
  
  // FILE LAYOUT:
  // header [ COFF header
  //        [ section headers
  // sections [ ... [ section data
  //                [ relocation data
  // symbol table [ ... symbols
  //              [ string_table_size: U32
  //              [ string data
  
  // flatten lists to pointer arrays
  TNK__Node ***node_arrays = tnk__flatten_node_lists(scratch.arena, obj);
  
  // node reindex map
  U32 *reindex_map = push_array(scratch.arena, U32, obj->node_alloc_pos);
  
  // get counts
  U32 section_count = obj->node_count[TNK_NodeKind_Section];
  U32 symbol_count = obj->node_count[TNK_NodeKind_Symbol];
  U32 reloc_count = obj->node_count[TNK_NodeKind_Reloc];
  
  // reindex sections
  for (U32 i = 0; i < section_count; i += 1){
    TNK__Node *ptr = node_arrays[TNK_NodeKind_Section][i];
    TNK_ID id = tnk__id_from_ptr(obj, ptr);
    reindex_map[id.idx] = 1 + i;
  }
  
  // reindex symbols & calculate symbol record count
  U32 symbol_record_count = symbol_count;
  for (U32 i = 0; i < symbol_count; i += 1){
    TNK__Node *ptr = node_arrays[TNK_NodeKind_Symbol][i];
    TNK_ID id = tnk__id_from_ptr(obj, ptr);
    reindex_map[id.idx] = i;
  }
  
  // get string table size
  U32 string_table_size = 0;
  for (U32 i = 0; i < symbol_count; i += 1){
    TNK__Node *ptr = node_arrays[TNK_NodeKind_Symbol][i];
    String8 name = ptr->name;
    if (name.size > 8){
      string_table_size += name.size + 1;
    }
  }
  
  // calculate layout
  U64 coff_header_foff = 0;
  U64 section_table_foff = coff_header_foff + sizeof(PE_CoffHeader);
  U64 section_table_fopl = (section_table_foff +
                            section_count*sizeof(PE_SectionHeader));
  
  U64 *section_foffs = push_array(scratch.arena, U64, section_count);
  U64 *reloc_foffs = push_array(scratch.arena, U64, section_count);
  U64 *reloc_counts = push_array(scratch.arena, U64, section_count);
  U64 section_data_fopl = 0;
  {
    U64 cursor_foff = section_table_fopl;
    
    for (U32 i = 0; i < section_count; i += 1){
      // get section fields
      TNK__Node *ptr = node_arrays[TNK_NodeKind_Section][i];
      TNK_ID id = tnk__id_from_ptr(obj, ptr);
      String8 data = tnk__buffer_read(ptr->u.section.buffer);
      
      if (data.size > 0){
        cursor_foff = AlignUpPow2(cursor_foff, 4);
        section_foffs[i] = cursor_foff;
        cursor_foff += data.size;
      }
      
      U32 section_reloc_count = 0;
      for (U32 j = 0; j < reloc_count; j += 1){
        TNK__Node *reloc_ptr = node_arrays[TNK_NodeKind_Reloc][j];
        if (reloc_ptr->u.reloc.section.xu64 == id.xu64){
          section_reloc_count += 1;
        }
      }
      
      if (section_reloc_count > 0){
        cursor_foff = AlignUpPow2(cursor_foff, 4);
        reloc_foffs[i] = cursor_foff;
        cursor_foff += section_reloc_count*sizeof(PE_CoffRelocation);
        reloc_counts[i] = section_reloc_count;
      }
    }
    
    section_data_fopl = cursor_foff;
  }
  
  U64 symbol_table_foff = AlignUpPow2(section_data_fopl, 4);
  U64 string_table_foff = symbol_table_foff + symbol_record_count*sizeof(PE_CoffSymbolRecord);
  U64 string_table_fopl = string_table_foff + 4 + string_table_size;
  
  U64 file_size = AlignUpPow2(string_table_fopl, 4);
  
  // allocate buffer
  U8 *buffer = push_array(arena, U8, file_size);
  
  // time stamp
  U32 time_stamp = 0;
  {
    DateTime date = os_now_universal_time();
    DateTime local_date = os_local_time_from_universal(&date);
    time_stamp = os_time_stamp_32_from_date_time(&local_date);
  }
  
  // fill COFF header
  {
    PE_CoffHeader *coff_header = (PE_CoffHeader*)buffer;
    coff_header->machine_type = PE_MachineType_AMD64;
    coff_header->section_count = section_count;
    coff_header->time_stamp = time_stamp;
    coff_header->symbol_table_foff = symbol_table_foff;
    coff_header->symbol_count = symbol_record_count;
    coff_header->optional_header_size = 0;
    coff_header->flags = 0;
  }
  
  // fill section headers
  {
    PE_SectionHeader *section_headers = (PE_SectionHeader*)(buffer + section_table_foff);
    for (U32 i = 0; i < section_count; i += 1){
      TNK__Node *ptr = node_arrays[TNK_NodeKind_Section][i];
      TNK_ID id = tnk__id_from_ptr(obj, ptr);
      String8 data = tnk__buffer_read(ptr->u.section.buffer);
      
      // calculate coff section flags
      PE_SectionFlags flags = PE_SectionFlag_CNT_INITIALIZED_DATA;
      if (ptr->flags & TNK_SectionFlag_R){
        flags |= PE_SectionFlag_MEM_READ;
      }
      if (ptr->flags & TNK_SectionFlag_W){
        flags |= PE_SectionFlag_MEM_WRITE;
      }
      if (ptr->flags & TNK_SectionFlag_X){
        flags |= PE_SectionFlag_MEM_EXECUTE;
      }
      
      // fill section header
      PE_SectionHeader *sechdr = section_headers + i;
      
      if (ptr->name.size < sizeof(sechdr->name)){
        MemoryCopy(sechdr->name, ptr->name.str, ptr->name.size);
      }
      else{
        MemoryCopy(sechdr->name, ptr->name.str, sizeof(sechdr->name));
        if (ptr->name.size > sizeof(sechdr->name)){
          // TODO(allen): emit warning
        }
      }
      
      sechdr->vsize = 0;
      sechdr->voff = 0;
      sechdr->fsize = data.size;
      sechdr->foff = section_foffs[i];
      sechdr->relocations_foff = reloc_foffs[i];
      sechdr->line_numbers_foff = 0;
      sechdr->relocation_count = reloc_count;
      sechdr->line_number_count = 0;
      sechdr->flags = flags;
    }
  }
  
  // section and relocation data
  for (U32 i = 0; i < section_count; i += 1){
    TNK__Node *ptr = node_arrays[TNK_NodeKind_Section][i];
    TNK_ID id = tnk__id_from_ptr(obj, ptr);
    String8 data = tnk__buffer_read(ptr->u.section.buffer);
    
    // write section data
    if (section_foffs[i] != 0){
      MemoryCopy(buffer + section_foffs[i], data.str, data.size);
    }
    
    // write relocation data
    if (reloc_foffs[i] != 0){
      PE_CoffRelocation *relocs = (PE_CoffRelocation*)(buffer + reloc_foffs[i]);
      PE_CoffRelocation *reloc = relocs;
      
      for (U32 j = 0; j < reloc_count; j += 1){
        TNK__Node *reloc_ptr = node_arrays[TNK_NodeKind_Reloc][j];
        if (reloc_ptr->u.reloc.section.xu64 == id.xu64){
          U32 symbol_idx = tnk__reindex(obj, reindex_map, reloc_ptr->u.reloc.symbol);
          
          // calculate relocation type
          PE_CoffRelocationType type = PE_CoffRelocationType_ADDR64;
          
          // fill relocation
          reloc->off = reloc_ptr->u.reloc.offset;
          reloc->symbol_idx = symbol_idx;
          reloc->type = type;
          reloc += 1;
        }
      }
    }
  }
  
  // fill symbol table & string table
  {
    PE_CoffSymbolRecord *records = (PE_CoffSymbolRecord*)(buffer + symbol_table_foff);
    U8 *string_table = (buffer + string_table_foff);
    *(U32*)string_table = 4 + string_table_size;
    U32 string_table_cursor = 4;
    
    for (U32 i = 0; i < symbol_count; i += 1){
      TNK__Node *ptr = node_arrays[TNK_NodeKind_Symbol][i];
      TNK_ID id = tnk__id_from_ptr(obj, ptr);
      U32 symbol_idx = tnk__reindex(obj, reindex_map, id);
      
      TNK_ID section_id = ptr->u.symbol.section;
      U32 sec_number = tnk__reindex(obj, reindex_map, section_id);
      
      // fill main symbol record
      {
        PE_CoffSymbolRecord *record = records + symbol_idx;
        
        String8 name = ptr->name;
        if (name.size <= 8){
          MemoryCopy(record->name.raw, name.str, name.size);
        }
        else{
          record->name.zero = 0;
          record->name.string_table_off = string_table_cursor;
          MemoryCopy(string_table + string_table_cursor,
                     name.str, name.size);
          string_table_cursor += name.size;
          string_table[string_table_cursor] = 0;
          string_table_cursor += 1;
        }
        
        record->val = ptr->u.reloc.offset;
        record->sec_number = sec_number;
        
        if (ptr->flags & TNK_SymbolFlag_Function){
          record->type = PE_CoffSymbolType(NULL, FUNCTION);
        }
        else{
          record->type = PE_CoffSymbolType(NULL, NULL);
        }
        
        if (ptr->flags & TNK_SymbolFlag_External){
          record->storage_class = PE_CoffSymbolStorageClass_EXTERNAL;
        }
        else{
          record->storage_class = PE_CoffSymbolStorageClass_STATIC;
        }
      }
    }
  }
  
  String8 result = str8(buffer, file_size);
  arena_release_scratch(&scratch);
  return(result);
}

MR4TH_SYMBOL String8
tnk_generate_elf(Arena *arena, TNK_Object *obj){
  ArenaTemp scratch = arena_get_scratch(&arena, 1);
  
  // FILE LAYOUT:
  // header
  // program header table
  // section data
  // section header table
  
  // flatten lists to pointer arrays
  TNK__Node ***node_arrays = tnk__flatten_node_lists(scratch.arena, obj);
  
  // node reindex map
  U32 *reindex_map = push_array(scratch.arena, U32, obj->node_alloc_pos);
  
  // get counts
  U32 section_count = obj->node_count[TNK_NodeKind_Section];
  U32 symbol_count = obj->node_count[TNK_NodeKind_Symbol];
  U32 reloc_count = obj->node_count[TNK_NodeKind_Reloc];
  
  // reindex sections
  for (U32 i = 0; i < section_count; i += 1){
    TNK__Node *ptr = node_arrays[TNK_NodeKind_Section][i];
    TNK_ID id = tnk__id_from_ptr(obj, ptr);
    reindex_map[id.idx] = 3 + i;
  }
  
  // reindex symbols
  for (U32 i = 0; i < symbol_count; i += 1){
    TNK__Node *ptr = node_arrays[TNK_NodeKind_Symbol][i];
    TNK_ID id = tnk__id_from_ptr(obj, ptr);
    reindex_map[id.idx] = 1 + i;
  }
  
  // calculate section string table size
  U64 section_string_table_size = 0;
  {
    section_string_table_size += 1;
    section_string_table_size += sizeof(".strtab");
    section_string_table_size += sizeof(".symtab");
    for (U32 i = 0; i < section_count; i += 1){
      TNK__Node *ptr = node_arrays[TNK_NodeKind_Section][i];
      section_string_table_size += ptr->name.size + 1;
    }
    for (U32 i = 0; i < symbol_count; i += 1){
      TNK__Node *ptr = node_arrays[TNK_NodeKind_Symbol][i];
      section_string_table_size += ptr->name.size + 1;
    }
  }
  
  // calculate symbol table size
  U64 full_symbol_count = symbol_count + 1;
  U64 symbol_table_size = full_symbol_count*sizeof(ELF_Symbol64);
  
  // calculate layout
  U64 elf_header_foff = 0;
  U64 elf_header_fopl = sizeof(ELF_Header64);
  
  U16 string_section_index = 1;
  U32 full_section_count = section_count + 3;
  
  U64 *section_foff = push_array(arena, U64, full_section_count);
  U64 section_table_foff = 0;
  {
    U64 section_foff_cursor = elf_header_fopl;
    {
      section_foff_cursor = AlignUpPow2(section_foff_cursor, 8);
      section_foff[1] = section_foff_cursor;
      section_foff_cursor += section_string_table_size;
    }
    {
      section_foff_cursor = AlignUpPow2(section_foff_cursor, 8);
      section_foff[2] = section_foff_cursor;
      section_foff_cursor += symbol_table_size;
    }
    for (U32 i = 0; i < section_count; i += 1){
      TNK__Node *ptr = node_arrays[TNK_NodeKind_Section][i];
      TNK_ID id = tnk__id_from_ptr(obj, ptr);
      U32 idx = tnk__reindex(obj, reindex_map, id);
      String8 data = tnk__buffer_read(ptr->u.section.buffer);
      if (data.size > 0){
        section_foff_cursor = AlignUpPow2(section_foff_cursor, 8);
        section_foff[idx] = section_foff_cursor;
        section_foff_cursor += data.size;
      }
    }
    section_table_foff = section_foff_cursor;
  }
  
  U64 section_table_fopl = section_table_foff + full_section_count*sizeof(ELF_Section64);
  
  U64 file_size = section_table_fopl;
  
  // allocate buffer
  U8 *buffer = push_array(arena, U8, file_size);
  
  ELF_Header64  *header = (ELF_Header64*)buffer;
  ELF_Section64 *sections = (ELF_Section64*)(buffer + section_table_foff);
  U8            *string_table = buffer + section_foff[1];
  ELF_Symbol64  *symbol_table = (ELF_Symbol64*)(buffer + section_foff[2]);
  
  // write header
  {
    header->ident[0] = ELF_Magic_Byte0;
    header->ident[1] = ELF_Magic_Byte1;
    header->ident[2] = ELF_Magic_Byte2;
    header->ident[3] = ELF_Magic_Byte3;
    header->ident[ELF_IdentificationIdx_CLASS] = ELF_Class_64;
    header->ident[ELF_IdentificationIdx_DATA] = ELF_Encoding_2LSB;
    header->ident[ELF_IdentificationIdx_VERSION] = 1;
    header->ident[ELF_IdentificationIdx_OSABI] = 0;
    header->ident[ELF_IdentificationIdx_ABIVERSION] = 0;
    
    header->type = ELF_FileType_REL;
    header->machine = ELF_MachineType_X86_64;
    header->version = 1;
    header->entry = 0;
    header->segment_table_foff = 0;
    header->section_table_foff = section_table_foff;
    header->flags = 0;
    header->header_size = sizeof(*header);
    header->segment_size = 0;
    header->segment_count = 0;
    header->section_size = sizeof(ELF_Section64);
    header->section_count = full_section_count;
    header->string_section_index = string_section_index;
  }
  
  // write section header string table
  {
    U8 *s_ptr = string_table;
    
    // write null byte
    *s_ptr = 0; s_ptr += 1;
    
    // write ".strtab"
    sections[1].name = (U32)(s_ptr - string_table);
    MemoryCopy(s_ptr, ".strtab", sizeof(".strtab"));
    s_ptr += sizeof(".strtab");
    
    // write ".symtab"
    sections[2].name = (U32)(s_ptr - string_table);
    MemoryCopy(s_ptr, ".symtab", sizeof(".symtab"));
    s_ptr += sizeof(".symtab");
    
    // write section names
    for (U32 i = 0; i < section_count; i += 1){
      TNK__Node *ptr = node_arrays[TNK_NodeKind_Section][i];
      TNK_ID id = tnk__id_from_ptr(obj, ptr);
      U32 idx = tnk__reindex(obj, reindex_map, id);
      sections[idx].name = (U32)(s_ptr - string_table);
      MemoryCopy(s_ptr, ptr->name.str, ptr->name.size + 1);
      s_ptr += ptr->name.size + 1;
    }
    
    // write symbol names
    for (U32 i = 0; i < symbol_count; i += 1){
      TNK__Node *ptr = node_arrays[TNK_NodeKind_Symbol][i];
      TNK_ID id = tnk__id_from_ptr(obj, ptr);
      U32 idx = tnk__reindex(obj, reindex_map, id);
      symbol_table[idx].name = (U32)(s_ptr - string_table);
      MemoryCopy(s_ptr, ptr->name.str, ptr->name.size + 1);
      s_ptr += ptr->name.size + 1;
    }
  }
  
  // write symbol table
  {
    for (U32 i = 0; i < symbol_count; i += 1){
      TNK__Node *ptr = node_arrays[TNK_NodeKind_Symbol][i];
      TNK_ID id = tnk__id_from_ptr(obj, ptr);
      U32 idx = tnk__reindex(obj, reindex_map, id);
      
      ELF_SymbolBinding binding = ((ptr->flags & TNK_SymbolFlag_External)?
                                   ELF_SymbolBinding_GLOBAL:
                                   ELF_SymbolBinding_LOCAL);
      ELF_SymbolType type = ((ptr->flags & TNK_SymbolFlag_Function)?
                             ELF_SymbolType_FUNC:
                             ELF_SymbolType_OBJECT);
      
      ELF_Symbol64 *symbol_ptr = &symbol_table[idx];
      symbol_ptr->info = ELF_Symbol_InfoFromBindType(binding, type);
      symbol_ptr->other = ELF_Symbol_OtherFromVis(ELF_SymbolVis_DEFAULT);
      symbol_ptr->section_index = tnk__reindex(obj, reindex_map, ptr->u.symbol.section);
      symbol_ptr->value = ptr->u.symbol.offset;
      symbol_ptr->size = 0;
    }
  }
  
  // write section data
  for (U32 i = 0; i < section_count; i += 1){
    TNK__Node *ptr = node_arrays[TNK_NodeKind_Section][i];
    TNK_ID id = tnk__id_from_ptr(obj, ptr);
    U32 idx = tnk__reindex(obj, reindex_map, id);
    String8 data = tnk__buffer_read(ptr->u.section.buffer);
    if (data.size > 0){
      MemoryCopy(buffer + section_foff[idx], data.str, data.size);
    }
  }
  
  // write section table
  {
    ELF_Section64 *section = &sections[1];
    section->type = ELF_SectionType_STRTAB;
    section->offset = section_foff[1];
    section->size = section_string_table_size;
  }
  {
    ELF_Section64 *section = &sections[2];
    section->type = ELF_SectionType_SYMTAB;
    section->offset = section_foff[2];
    section->size = symbol_table_size;
    section->link = 1;
    section->info = 1;
    section->addralign = 8;
    section->entsize = sizeof(ELF_Symbol64);
  }
  for (U32 i = 0; i < section_count; i += 1){
    TNK__Node *ptr = node_arrays[TNK_NodeKind_Section][i];
    TNK_ID id = tnk__id_from_ptr(obj, ptr);
    U32 idx = tnk__reindex(obj, reindex_map, id);
    String8 data = tnk__buffer_read(ptr->u.section.buffer);
    
    U64 flags = ELF_SectionFlag_ALLOC;
    if (ptr->flags & TNK_SectionFlag_W){
      flags |= ELF_SectionFlag_WRITE;
    }
    if (ptr->flags & TNK_SectionFlag_X){
      flags |= ELF_SectionFlag_EXECINSTR;
    }
    
    ELF_Section64 *section = &sections[idx];
    section->type = ELF_SectionType_PROGBITS;
    section->flags = flags;
    section->addr = 0;
    section->offset = section_foff[idx];
    section->size = data.size;
    section->addralign = 8;
    section->entsize = 0;
  }
  
  String8 result = str8(buffer, file_size);
  arena_release_scratch(&scratch);
  return(result);
}

MR4TH_SYMBOL TNK__Node***
tnk__flatten_node_lists(Arena *arena, TNK_Object *obj){
  TNK__Node ***node_arrays = push_array(arena, TNK__Node**, TNK_NodeKind_COUNT);
  for (U32 k = 1; k < TNK_NodeKind_COUNT; k += 1){
    node_arrays[k] = push_array(arena, TNK__Node*, obj->node_count[k]);
    for (TNK__Node *node = obj->node_first[k], **ptr = node_arrays[k];
         node != 0; node = node->next, ptr += 1){
      *ptr = node;
    }
  }
  return(node_arrays);
}

MR4TH_SYMBOL U32
tnk__reindex(TNK_Object *obj, U32 *reindex_map, TNK_ID id){
  U32 result = 0;
  if (id.idx < obj->node_alloc_pos &&
      obj->nodes.ptr[id.idx].gen == id.gen){
    result = reindex_map[id.idx];
  }
  return(result);
}
