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

internal LNK_LibNode *
lnk_lib_list_reserve(Arena *arena, LNK_LibList *list, U64 count)
{
  LNK_LibNode *arr = 0;
  if (count) {
    arr = push_array(arena, LNK_LibNode, count);
    for (LNK_LibNode *ptr = arr, *opl = arr + count; ptr < opl; ++ptr) {
      SLLQueuePush(list->first, list->last, ptr);
    }
    list->count += count;
  }
  return arr;
}

internal LNK_LibMemberNode *
lnk_lib_member_list_push(Arena *arena, LNK_LibMemberList *list, LNK_LibMember member)
{
  LNK_LibMemberNode *n = push_array_no_zero(arena, LNK_LibMemberNode, 1);
  n->next              = 0;
  n->data              = member;
  
  SLLQueuePush(list->first, list->last, n);
  ++list->count;
  
  return n;
}

internal LNK_LibMember *
lnk_lib_member_array_from_list(Arena *arena, LNK_LibMemberList list)
{
  ProfBeginFunction();
  LNK_LibMember *arr = push_array_no_zero(arena, LNK_LibMember, list.count);
  LNK_LibMember *ptr = arr;
  for (LNK_LibMemberNode *i = list.first; i != 0; i = i->next, ptr += 1) {
    ptr->name = push_str8_copy(arena, i->data.name);
    ptr->data = push_str8_copy(arena, i->data.data); 
  }
  ProfEnd();
  return arr;
}

internal LNK_LibSymbolNode *
lnk_lib_symbol_list_push(Arena *arena, LNK_LibSymbolList *list, LNK_LibSymbol symbol)
{
  LNK_LibSymbolNode *n = push_array_no_zero(arena, LNK_LibSymbolNode, 1);
  n->next              = 0;
  n->data              = symbol;
  
  SLLQueuePush(list->first, list->last, n);
  ++list->count;
  
  return n;
}

internal LNK_LibSymbol *
lnk_lib_symbol_array_from_list(Arena *arena, LNK_LibSymbolList list)
{
  LNK_LibSymbol *arr = push_array_no_zero(arena, LNK_LibSymbol, list.count + 2);
  LNK_LibSymbol *ptr = arr + 1;
  for (LNK_LibSymbolNode *i = list.first; i != 0; i = i->next, ptr += 1) {
    ptr->name = push_str8_copy(arena, i->data.name);
    ptr->member_idx = i->data.member_idx;
  }
  MemoryZeroStruct(&arr[0]);
  MemoryZeroStruct(&arr[list.count+1]);
  return arr;
}

int
lnk_lib_symbol_name_compar(const void *raw_a, const void *raw_b)
{
  const LNK_Symbol *sa = (const LNK_Symbol *)raw_a;
  const LNK_Symbol *sb = (const LNK_Symbol *)raw_b;
  return str8_compar_case_sensitive(&sa->name, &sb->name);
}

int
lnk_lib_symbol_name_compar_is_before(void *raw_a, void *raw_b)
{
  int compar = lnk_lib_symbol_name_compar(raw_a, raw_b);
  int is_before = compar < 0;
  return is_before;
}

internal void
lnk_lib_symbol_array_sort(LNK_LibSymbol *arr, U64 count)
{
  Assert(count >= 2);
  radsort(arr + 1, count - 2, lnk_lib_symbol_name_compar_is_before);
}

////////////////////////////////

internal LNK_Lib
lnk_lib_from_data(Arena *arena, String8 data, String8 path)
{
  ProfBeginFunction();

  U64     symbol_count;
  String8 string_table;
  U32    *member_off_arr;

  // is data archive?
  COFF_ArchiveType type = coff_archive_type_from_data(data);
  if (type == COFF_Archive_Null) {
    lnk_not_implemented("TODO: data is not archive");
  }

  COFF_ArchiveParse parse = coff_archive_parse_from_data(data);

  // report archive parser errors
  if (parse.error.size) {
    lnk_error(LNK_Error_IllData, "%S: %S", path, parse.error);
  }
  
  // try to init library from optional second member
  if (parse.second_member.member_count) {
    COFF_ArchiveSecondMember second_member = parse.second_member;
    Assert(second_member.symbol_count == second_member.symbol_index_count);
    Assert(second_member.member_count == second_member.member_offset_count);
    
    symbol_count   = second_member.symbol_count;
    string_table   = second_member.string_table;
    member_off_arr = push_array_no_zero(arena, U32, symbol_count);
    
    // decompress member offsets
    for (U64 symbol_idx = 0; symbol_idx < symbol_count; symbol_idx += 1) {
      U16 off_number = second_member.symbol_indices[symbol_idx];
      if (0 < off_number && off_number <= second_member.member_count) {
        member_off_arr[symbol_idx] = second_member.member_offsets[off_number - 1];
      } else {
        // TODO: log bad offset
        member_off_arr[symbol_idx] = max_U32;
      }
    }
  } 
  // first member is deprecated however tools emit it for compatibility reasons
  // and lld-link with /DLL emits only first member
  else if (parse.first_member.symbol_count) {
    COFF_ArchiveFirstMember first_member = parse.first_member;
    Assert(first_member.symbol_count == first_member.member_offset_count);
    
    symbol_count   = first_member.symbol_count;
    string_table   = first_member.string_table;
    member_off_arr = first_member.member_offsets;
    
    // convert big endian offsets
    for (U32 offset_idx = 0; offset_idx < symbol_count; offset_idx += 1) {
      member_off_arr[offset_idx] = from_be_u32(member_off_arr[offset_idx]);
    }
  } else {
    symbol_count   = 0;
    string_table   = str8_zero();
    member_off_arr = 0;
  }
  
  // parse string table
  String8List symbol_name_list = str8_split_by_string_chars(arena, string_table, str8_lit("\0"), StringSplitFlag_KeepEmpties);
  Assert(symbol_name_list.node_count >= symbol_count);
  symbol_count = Min(symbol_count, symbol_name_list.node_count);
  
  // init lib
  LNK_Lib lib = {0};
  lib.path             = push_str8_copy(arena, path);
  lib.data             = data;
  lib.type             = type;
  lib.symbol_count     = symbol_count;
  lib.member_off_arr   = member_off_arr;
  lib.symbol_name_list = symbol_name_list;
  lib.long_names       = parse.long_names;
  
  ProfEnd();
  return lib;
}

internal
THREAD_POOL_TASK_FUNC(lnk_lib_initer)
{
  LNK_LibIniter *task       = raw_task;
  LNK_LibNode   *lib_node   = task->node_arr + task_id;
  LNK_Lib       *lib        = &lib_node->data;
  String8        data       = task->data_arr[task_id];
  String8        path       = task->path_arr[task_id];
  
  *lib = lnk_lib_from_data(arena, data, path);
  lib->input_idx = task->base_input_idx + task_id;
}

internal LNK_LibNodeArray
lnk_lib_list_push_parallel(TP_Context *tp, TP_Arena *arena, LNK_LibList *list, String8Array data_arr, String8Array path_arr)
{
  Assert(data_arr.count == path_arr.count);
  U64 lib_count = data_arr.count;
  
  LNK_LibIniter task  = {0};
  task.node_arr       = lnk_lib_list_reserve(arena->v[0], list, lib_count);
  task.data_arr       = data_arr.v;
  task.path_arr       = path_arr.v;
  task.base_input_idx = list->count;
  tp_for_parallel(tp, arena, lib_count, lnk_lib_initer, &task);

  LNK_LibNodeArray arr = {0};
  arr.count            = lib_count;
  arr.v                = task.node_arr;
  return arr;
}

#if 0
internal LNK_LibNode *
lnk_lib_list_push(Arena *arena, LNK_LibList *list, String8 data, String8 path)
{
  ProfBeginFunction();
  
  TP_Arena pool_arena = {0};
  pool_arena.count    = 1;
  pool_arena.v        = &arena;
  
  String8Array data_arr = {0};
  data_arr.count        = 1;
  data_arr.v            = &data;
  
  String8Array path_arr = {0};
  path_arr.count        = 1;
  path_arr.v            = &path;
  
  LNK_LibNodeArray node_arr = lnk_lib_list_push_parallel(&pool_arena, list, data_arr, path_arr);

  ProfEnd();
  return node_arr.v;
}
#endif

////////////////////////////////

internal LNK_LibWriter *
lnk_lib_writer_alloc(void)
{
  Arena *arena = arena_alloc();
  LNK_LibWriter *writer = push_array(arena, LNK_LibWriter, 1);
  writer->arena = arena;
  return writer;
}

internal void
lnk_lib_writer_release(LNK_LibWriter **writer_ptr)
{
  arena_release((*writer_ptr)->arena);
  *writer_ptr = 0;
}

internal void
lnk_lib_writer_push_obj(LNK_LibWriter *writer, LNK_Obj *obj)
{
  ProfBeginFunction();
  
  U64 member_idx = writer->member_list.count;
  
  // push obj member
  LNK_LibMember member = {0};
  member.name          = obj->path;
  member.data          = obj->data;
  lnk_lib_member_list_push(writer->arena, &writer->member_list, member);
  
  // push external symbols
  for (LNK_SymbolNode *node = obj->symbol_list.first; node != 0; node = node->next) {
    LNK_Symbol *symbol = node->data;
    B32 is_extern = symbol->type == LNK_Symbol_DefinedExtern;
    if (is_extern) {
      LNK_LibSymbol lib_symbol = {0};
      lib_symbol.name       = symbol->name;
      lib_symbol.member_idx = member_idx;
      lnk_lib_symbol_list_push(writer->arena, &writer->symbol_list, lib_symbol);
    }
  }

  ProfEnd();
}

internal void
lnk_lib_writer_push_export(LNK_LibWriter *writer, COFF_MachineType machine, U64 time_stamp, String8 dll_name, LNK_Export *exp)
{
  ProfBeginFunction();
  
  U64 member_idx = writer->member_list.count;

  // make import header
  String8 import_data;
  if (exp->name.size) {
    U16 hint = safe_cast_u16(exp->id);
    import_data = coff_make_import_header_by_name(writer->arena, dll_name, machine, time_stamp, exp->name, hint, exp->type);
  } else {
    U16 ordinal = safe_cast_u16(exp->id);
    import_data = coff_make_import_header_by_ordinal(writer->arena, dll_name, machine, time_stamp, ordinal, exp->type);
  }
  
  // push import member
  LNK_LibMember member = {0};
  member.name          = dll_name;
  member.data          = import_data;
  lnk_lib_member_list_push(writer->arena, &writer->member_list, member);
  
  switch (exp->type) {
  case COFF_ImportHeaderType_CODE: {
    LNK_LibSymbol def_symbol = {0};
    def_symbol.name          = push_str8_copy(writer->arena, exp->name);
    def_symbol.member_idx    = member_idx;
    lnk_lib_symbol_list_push(writer->arena, &writer->symbol_list, def_symbol);
  }
  case COFF_ImportHeaderType_DATA: {
    LNK_LibSymbol imp_symbol = {0};
    imp_symbol.name          = push_str8f(writer->arena, "__imp_%S", exp->name);
    imp_symbol.member_idx    = member_idx;
    lnk_lib_symbol_list_push(writer->arena, &writer->symbol_list, imp_symbol);
  } break;
  case COFF_ImportHeaderType_CONST: {
    NotImplemented;
  } break;
  default: InvalidPath;
  }

  ProfEnd();
}

internal LNK_LibBuild
lnk_lib_build_from_writer(Arena *arena, LNK_LibWriter *writer)
{
  ProfBeginFunction();
  
  LNK_LibBuild lib = {0};
  lib.symbol_count = writer->symbol_list.count + 2;
  lib.member_count = writer->member_list.count;
  lib.symbol_array = lnk_lib_symbol_array_from_list(arena, writer->symbol_list);
  lib.member_array = lnk_lib_member_array_from_list(arena, writer->member_list);
  lnk_lib_symbol_array_sort(lib.symbol_array, lib.symbol_count);

  ProfEnd();
  return lib;
}

internal String8List
lnk_coff_archive_from_lib_build(Arena *arena, LNK_LibBuild *lib, B32 emit_second_member, COFF_TimeStamp time_stamp, U32 mode)
{
  ProfBeginFunction();
  
  Temp scratch = scratch_begin(&arena, 1);

  U64            symbol_count = lib->symbol_count - 2;
  LNK_LibSymbol *symbol_arr   = lib->symbol_array + 1;

  HashTable   *name_ht          = hash_table_init(scratch.arena, 1024);
  U64         *member_off_arr   = push_array_no_zero(scratch.arena, U64, lib->member_count);
  String8List  long_names_list  = {0};
  String8List  member_data_list = {0};
  
  for (U64 member_idx = 0; member_idx < lib->member_count; member_idx += 1) {
    LNK_LibMember *member = &lib->member_array[member_idx];
    
    // make member name
    String8 name;
    U64 name_with_slash_size = member->name.size + 1;
    if (name_with_slash_size > COFF_ARCHIVE_MAX_SHORT_NAME_SIZE) {
      // have we seen this member name before?
      KeyValuePair *is_present = hash_table_search_string(name_ht, member->name);
      if (is_present) {
        name = is_present->value_string;
      } else {
        name = push_str8f(scratch.arena, "/%u", long_names_list.total_size);
        str8_list_pushf(scratch.arena, &long_names_list, "%S/\n", member->name);
        hash_table_push_string_string(scratch.arena, name_ht, member->name, name);
      }
    } else {
      name = push_str8f(scratch.arena, "%S/", member->name);
    }
    
    member_off_arr[member_idx] = member_data_list.total_size;
    
    String8 member_data = member->data;
    String8 member_header = lnk_build_lib_member_header(arena, name, time_stamp, 0, 0, mode, member_data.size);
    
    str8_list_push(arena, &member_data_list, member_header);
    str8_list_push(arena, &member_data_list, member_data);
    str8_list_push_pad(arena, &member_data_list, member_data_list.total_size, COFF_ARCHIVE_ALIGN);
  }
  
  // long names member
  if (long_names_list.total_size > 0) {
    String8 header = lnk_build_lib_member_header(arena, str8_lit("//"), time_stamp, 0, 0, mode, long_names_list.total_size);
    String8 data = str8_list_join(arena, &long_names_list, 0);
    U64 member_offset = member_data_list.total_size + data.size + header.size;
    str8_list_push_pad_front(arena, &member_data_list, member_offset, COFF_ARCHIVE_ALIGN);
    str8_list_push_front(arena, &member_data_list, data);
    str8_list_push_front(arena, &member_data_list, header);
  }
  
  // compute size for symbol string table
  U32 name_buffer_size = 0;
  for (LNK_LibSymbol *ptr = &symbol_arr[0], *opl = ptr + symbol_count; ptr < opl; ptr += 1) {
    name_buffer_size += ptr->name.size;
    name_buffer_size += 1; // null
  }

  // write symbol name buffer
  U8 *name_buffer = push_array_no_zero(scratch.arena, U8, name_buffer_size);
  {
    U64 name_cursor = 0;
    for (LNK_LibSymbol *ptr = &symbol_arr[0], *opl = ptr + symbol_count; ptr < opl; ptr += 1) {
      MemoryCopy(name_buffer + name_cursor, ptr->name.str, ptr->name.size);
      name_buffer[name_cursor + ptr->name.size] = '\0';
      name_cursor += ptr->name.size + 1;
    }
  }
  
  U64 member_base_off;
  {
    U64 sizeof_first_header  = COFF_ARCHIVE_MEMBER_HEADER_SIZE + sizeof(U32) + sizeof(U32) * symbol_count + name_buffer_size;
    U64 sizeof_second_header = COFF_ARCHIVE_MEMBER_HEADER_SIZE + sizeof(U32) + sizeof(U32) * lib->member_count + sizeof(U32) + sizeof(U16) * symbol_count + name_buffer_size;
    U64 sizeof_long_names    = COFF_ARCHIVE_MEMBER_HEADER_SIZE + long_names_list.total_size;
    
    sizeof_first_header  = AlignPow2(sizeof_first_header, COFF_ARCHIVE_ALIGN);
    sizeof_second_header = AlignPow2(sizeof_second_header, COFF_ARCHIVE_ALIGN);
    sizeof_long_names    = AlignPow2(sizeof_long_names, COFF_ARCHIVE_ALIGN);

    member_base_off = sizeof(g_coff_archive_sig);
    member_base_off += sizeof_first_header;
    if (emit_second_member) {
      member_base_off += sizeof_second_header;
    }
    if (long_names_list.total_size > 0) {
      member_base_off += sizeof_long_names;
    }
  }
  
  // second linker member
  if (emit_second_member) {
    U32 member_count32 = safe_cast_u32(lib->member_count);
    U32 symbol_count32 = safe_cast_u32(symbol_count);

    U32 *member_off32_arr = push_array_no_zero(scratch.arena, U32, lib->member_count);
    U16 *member_idx16_arr = push_array_no_zero(scratch.arena, U16, symbol_count);

    // write member offset array
    for (U64 member_idx = 0; member_idx < lib->member_count; member_idx += 1) {
      U64 member_off = member_base_off + member_off_arr[member_idx];
      U32 member_off32 = safe_cast_u32(member_off);
      member_off32_arr[member_idx] = member_off32; 
    }

    // write member offset indices for each symbol
    for (U64 symbol_idx = 0; symbol_idx < symbol_count; symbol_idx += 1) {
      // member offset indices are 1-based
      U64 member_idx = symbol_arr[symbol_idx].member_idx + 1;
      U16 member_idx16 = safe_cast_u16(member_idx);
      member_idx16_arr[symbol_idx] = member_idx16;
    }

    // layout second member data
    String8List second_member_data_list = {0};
    str8_list_push(scratch.arena, &second_member_data_list, str8_struct(&member_count32));
    str8_list_push(scratch.arena, &second_member_data_list, str8_array(member_off32_arr, lib->member_count));
    str8_list_push(scratch.arena, &second_member_data_list, str8_struct(&symbol_count32));
    str8_list_push(scratch.arena, &second_member_data_list, str8_array(member_idx16_arr, symbol_count));
    str8_list_push(scratch.arena, &second_member_data_list, str8(name_buffer, name_buffer_size));

    String8 member_data   = str8_list_join(arena, &second_member_data_list, 0);
    String8 member_header = lnk_build_lib_member_header(arena, str8_lit("/"), time_stamp, 0, 0, mode, member_data.size);
    
    U64 member_offset = member_data_list.total_size + member_data.size + member_header.size;
    str8_list_push_pad_front(arena, &member_data_list, member_offset, COFF_ARCHIVE_ALIGN);
    str8_list_push_front(arena, &member_data_list, member_data);
    str8_list_push_front(arena, &member_data_list, member_header);
  }
  
  // first linker member (obsolete, but kept for compatability reasons)
  {
    U32  symbol_count_be  = from_be_u32(symbol_count);
    U32 *member_off32_arr = push_array_no_zero(scratch.arena, U32, symbol_count);

    for (U64 symbol_idx = 0; symbol_idx < symbol_count; symbol_idx += 1) {
      LNK_LibSymbol *symbol = &symbol_arr[symbol_idx];

      // write big endian member offset
      U64 member_off = member_base_off + member_off_arr[symbol->member_idx];
      U32 member_off32 = from_be_u32(safe_cast_u32(member_off));
      member_off32_arr[symbol_idx] = member_off32;
    }

    // layout first member data
    String8List first_member_data_list = {0};
    str8_list_push(scratch.arena, &first_member_data_list, str8_struct(&symbol_count_be));
    str8_list_push(scratch.arena, &first_member_data_list, str8_array(member_off32_arr, symbol_count));
    str8_list_push(scratch.arena, &first_member_data_list, str8(name_buffer, name_buffer_size));

    String8 member_data   = str8_list_join(arena, &first_member_data_list, 0);
    String8 member_header = lnk_build_lib_member_header(arena, str8_lit("/"), time_stamp, 0, 0, mode, member_data.size);
    
    U64 member_offset = sizeof(g_coff_archive_sig) + member_header.size + member_data.size;
    str8_list_push_pad_front(arena, &member_data_list, member_offset, COFF_ARCHIVE_ALIGN);
    str8_list_push_front(arena, &member_data_list, member_data);
    str8_list_push_front(arena, &member_data_list, member_header);
  }
  
  // archive signature
  str8_list_push_front(arena, &member_data_list, str8_struct(&g_coff_archive_sig));
  
  scratch_end(scratch);
  ProfEnd();
  return member_data_list; 
}

////////////////////////////////

internal LNK_LibBuild
lnk_build_lib(Arena *arena, COFF_MachineType machine, COFF_TimeStamp time_stamp, String8 dll_name, LNK_ObjList obj_list, LNK_ExportTable *exptab)
{
  ProfBeginFunction();
  Temp scratch = scratch_begin(&arena, 1);

  LNK_LibWriter *writer = lnk_lib_writer_alloc();
  for (LNK_ObjNode *obj_node = obj_list.first; obj_node != 0; obj_node = obj_node->next) {
    lnk_lib_writer_push_obj(writer, &obj_node->data);
  }

  KeyValuePair *raw_export_arr = key_value_pairs_from_hash_table(scratch.arena, exptab->name_export_ht);
  for (U64 i = 0; i < exptab->name_export_ht->count; ++i) {
    LNK_Export *exp = raw_export_arr[i].value_raw;
    lnk_lib_writer_push_export(writer, machine, time_stamp, dll_name, exp);
  }
  LNK_LibBuild lib = lnk_lib_build_from_writer(arena, writer);
  lnk_lib_writer_release(&writer);

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

internal String8List
lnk_build_import_entry_obj(Arena *arena, String8 dll_name, COFF_MachineType machine)
{
  ProfBeginFunction();
  
  Assert(machine == COFF_MachineType_X64);
  Assert(str8_match(str8_lit("dll"), str8_skip_last_dot(dll_name), StringMatchFlag_CaseInsensitive|StringMatchFlag_RightSideSloppy));
  
  String8List list = {0};
  
  COFF_Header *coff_header = push_array(arena, COFF_Header, 1);
  coff_header->machine = machine;
  str8_list_push(arena, &list, str8_struct(coff_header));
  
  coff_header->section_count = 2;
  COFF_SectionHeader *coff_sect_header_array = push_array(arena, COFF_SectionHeader, coff_header->section_count);
  str8_list_push(arena, &list, str8_array(coff_sect_header_array, coff_header->section_count));
  
  PE_ImportEntry *import_entry = push_array(arena, PE_ImportEntry, 1);
  U64 import_entry_off = list.total_size;
  str8_list_push(arena, &list, str8_struct(import_entry));
  
  String8 dll_name_cstr = push_cstr(arena, dll_name);
  U64 dll_name_off = list.total_size;
  str8_list_push(arena, &list, dll_name_cstr);
  
  U32 import_entry_reloc_count = 3;
  COFF_Reloc *import_entry_reloc_array = push_array(arena, COFF_Reloc, import_entry_reloc_count);
  U64 import_entry_reloc_off = list.total_size;
  str8_list_push(arena, &list, str8_array(import_entry_reloc_array, import_entry_reloc_count));
  
  coff_header->symbol_count = 7;
  COFF_Symbol16 *symbol_array = push_array(arena, COFF_Symbol16, coff_header->symbol_count);
  coff_header->symbol_table_foff = safe_cast_u32(list.total_size);
  str8_list_push(arena, &list, str8_array(symbol_array, coff_header->symbol_count));
  
  U64 string_table_base = list.total_size;
  U32 *string_table_size_ptr = push_array(arena, U32, 1);
  str8_list_push(arena, &list, str8_struct(string_table_size_ptr));
  
  // PE_ImportEntry
  {
    COFF_SectionHeader *sect = &coff_sect_header_array[0];
    sect->name[0] = '.';
    sect->name[1] = 'i';
    sect->name[2] = 'd';
    sect->name[3] = 'a';
    sect->name[4] = 't';
    sect->name[5] = 'a';
    sect->name[6] = '$';
    sect->name[7] = '2';
    sect->fsize       = sizeof(PE_ImportEntry);
    sect->foff        = import_entry_off;
    sect->reloc_count = import_entry_reloc_count;
    sect->relocs_foff = import_entry_reloc_off;
    sect->flags       = COFF_SectionFlag_CNT_INITIALIZED_DATA|(COFF_SectionAlign_4BYTES << COFF_SectionFlag_ALIGN_SHIFT)|COFF_SectionFlag_MEM_READ|COFF_SectionFlag_MEM_WRITE;
  }
  {
    COFF_Reloc *lookup_table_voff_reloc = &import_entry_reloc_array[0];
    lookup_table_voff_reloc->apply_off  = OffsetOf(PE_ImportEntry, lookup_table_voff);
    lookup_table_voff_reloc->isymbol    = 3;
    lookup_table_voff_reloc->type       = COFF_RelocTypeX64_ADDR32NB;
    
    COFF_Reloc *name_voff_reloc = &import_entry_reloc_array[1];
    name_voff_reloc->apply_off  = OffsetOf(PE_ImportEntry, name_voff);
    name_voff_reloc->isymbol    = 2;
    name_voff_reloc->type       = COFF_RelocTypeX64_ADDR32NB;
    
    COFF_Reloc *import_addr_table_voff = &import_entry_reloc_array[2];
    import_addr_table_voff->apply_off  = OffsetOf(PE_ImportEntry, import_addr_table_voff);
    import_addr_table_voff->isymbol    = 4;
    import_addr_table_voff->type       = COFF_RelocTypeX64_ADDR32NB;
  }
  
  // dll name
  {
    COFF_SectionHeader *sect = &coff_sect_header_array[1];
    sect->name[0] = '.';
    sect->name[1] = 'i';
    sect->name[2] = 'd';
    sect->name[3] = 'a';
    sect->name[4] = 't';
    sect->name[5] = 'a';
    sect->name[6] = '$';
    sect->name[7] = '6';
    sect->fsize = dll_name_cstr.size;
    sect->foff  = dll_name_off;
    sect->flags = COFF_SectionFlag_CNT_INITIALIZED_DATA|(COFF_SectionAlign_2BYTES << COFF_SectionFlag_ALIGN_SHIFT)|COFF_SectionFlag_MEM_READ|COFF_SectionFlag_MEM_WRITE;
  }
  
  // import descriptor
  {
    String8 dll_name_no_ext = str8_substr(dll_name, r1u64(0, dll_name.size - 4));
    String8 symbol_name = push_str8f(arena, "__IMPORT_DESCRIPTOR_%S", dll_name_no_ext);
    
    U64 symbol_name_off = (list.total_size - string_table_base);
    str8_list_push(arena, &list, push_cstr(arena, symbol_name));
    
    COFF_Symbol16 *symbol                      = &symbol_array[0];
    symbol->name.long_name.zeroes              = 0;
    symbol->name.long_name.string_table_offset = symbol_name_off;
    symbol->section_number                     = 1;
    symbol->storage_class                      = COFF_SymStorageClass_EXTERNAL;
  }
  
  // .idata$2
  {
    COFF_Symbol16 *symbol = &symbol_array[1];
    symbol->name.short_name[0] = '.';
    symbol->name.short_name[1] = 'i';
    symbol->name.short_name[2] = 'd';
    symbol->name.short_name[3] = 'a';
    symbol->name.short_name[4] = 't';
    symbol->name.short_name[5] = 'a';
    symbol->name.short_name[6] = '$';
    symbol->name.short_name[7] = '2';
    symbol->section_number = 1;
    symbol->storage_class  = COFF_SymStorageClass_SECTION;
  }
  
  // .idata$6
  {
    COFF_Symbol16 *symbol = &symbol_array[2];
    symbol->name.short_name[0] = '.';
    symbol->name.short_name[1] = 'i';
    symbol->name.short_name[2] = 'd';
    symbol->name.short_name[3] = 'a';
    symbol->name.short_name[4] = 't';
    symbol->name.short_name[5] = 'a';
    symbol->name.short_name[6] = '$';
    symbol->name.short_name[7] = '6';
    symbol->section_number = 2;
    symbol->storage_class  = COFF_SymStorageClass_STATIC;
  }
  
  // .idata$4
  {
    COFF_Symbol16 *symbol = &symbol_array[3];
    symbol->name.short_name[0] = '.';
    symbol->name.short_name[1] = 'i';
    symbol->name.short_name[2] = 'd';
    symbol->name.short_name[3] = 'a';
    symbol->name.short_name[4] = 't';
    symbol->name.short_name[5] = 'a';
    symbol->name.short_name[6] = '$';
    symbol->name.short_name[7] = '4';
    symbol->section_number = COFF_SYMBOL_UNDEFINED_SECTION;
    symbol->storage_class  = COFF_SymStorageClass_SECTION;
  }
  
  // .idata$5
  {
    COFF_Symbol16 *symbol = &symbol_array[4];
    symbol->name.short_name[0] = '.';
    symbol->name.short_name[1] = 'i';
    symbol->name.short_name[2] = 'd';
    symbol->name.short_name[3] = 'a';
    symbol->name.short_name[4] = 't';
    symbol->name.short_name[5] = 'a';
    symbol->name.short_name[6] = '$';
    symbol->name.short_name[7] = '5';
    symbol->section_number = COFF_SYMBOL_UNDEFINED_SECTION;
    symbol->storage_class  = COFF_SymStorageClass_SECTION;
  }
  
  // __NULL_IMPORT_DESCRIPTOR
  {
    U64 symbol_name_off = (list.total_size - string_table_base);
    str8_list_push(arena, &list, push_cstr(arena, str8_lit("__NULL_IMPORT_DESCRIPTOR")));
    
    COFF_Symbol16 *symbol                      = &symbol_array[5];
    symbol->name.long_name.zeroes              = 0;
    symbol->name.long_name.string_table_offset = symbol_name_off;
    symbol->section_number                     = COFF_SYMBOL_UNDEFINED_SECTION;
    symbol->storage_class                      = COFF_SymStorageClass_EXTERNAL;
  }
  
  // NULL_THUNK_DATA
  {
    String8 dll_name_no_ext = str8_substr(dll_name, r1u64(0, dll_name.size - 4));
    String8 symbol_name = push_str8f(arena, "\x7f%S_NULL_THUNK_DATA", dll_name_no_ext);
    
    U64 symbol_name_off = (list.total_size - string_table_base);
    str8_list_push(arena, &list, push_cstr(arena, symbol_name));
    
    COFF_Symbol16 *symbol                      = &symbol_array[6];
    symbol->name.long_name.zeroes              = 0;
    symbol->name.long_name.string_table_offset = symbol_name_off;
    symbol->section_number                     = COFF_SYMBOL_UNDEFINED_SECTION;
    symbol->storage_class                      = COFF_SymStorageClass_EXTERNAL;
  }
  
  // update string table size
  *string_table_size_ptr = (list.total_size - string_table_base);
  
  ProfEnd();
  return list;
}

internal String8List
lnk_build_null_import_descriptor_obj(Arena *arena, COFF_MachineType machine)
{
  ProfBeginFunction();
  
  String8List list = {0};
  
  COFF_Header *coff_header = push_array(arena, COFF_Header, 1);
  coff_header->machine = machine;
  str8_list_push(arena, &list, str8_struct(coff_header));
  
  coff_header->section_count = 1;
  COFF_SectionHeader *coff_sect_header_array = push_array(arena, COFF_SectionHeader, coff_header->section_count);
  str8_list_push(arena, &list, str8_array(coff_sect_header_array, coff_header->section_count));
  
  U64 null_import_data_size = 20;
  U8 *null_import_data      = push_array(arena, U8, null_import_data_size);
  U64 null_import_data_off  = list.total_size;
  str8_list_push(arena, &list, str8(null_import_data, null_import_data_size));
  
  coff_header->symbol_count = 1;
  COFF_Symbol16 *symbol_array = push_array(arena, COFF_Symbol16, coff_header->symbol_count);
  coff_header->symbol_table_foff = safe_cast_u32(list.total_size);
  str8_list_push(arena, &list, str8_array(symbol_array, coff_header->symbol_count));
  
  U64  string_table_base     = list.total_size;
  U32 *string_table_size_ptr = push_array(arena, U32, 1);
  str8_list_push(arena, &list, str8_struct(string_table_size_ptr));
  
  {
    COFF_SectionHeader *sect = &coff_sect_header_array[0];
    sect->name[0] = '.';
    sect->name[1] = 'i';
    sect->name[2] = 'd';
    sect->name[3] = 'a';
    sect->name[4] = 't';
    sect->name[5] = 'a';
    sect->name[6] = '$';
    sect->name[7] = '3';
    sect->fsize = null_import_data_size;
    sect->foff  = null_import_data_off;
    sect->flags = COFF_SectionFlag_CNT_INITIALIZED_DATA|(COFF_SectionAlign_4BYTES << COFF_SectionFlag_ALIGN_SHIFT)|COFF_SectionFlag_MEM_READ|COFF_SectionFlag_MEM_WRITE;
  }
  
  {
    U64 symbol_name_off = list.total_size - string_table_base;
    str8_list_push(arena, &list, push_cstr(arena, str8_lit("__NULL_IMPORT_DESCRIPTOR")));
    
    COFF_Symbol16 *symbol                      = &symbol_array[0];
    symbol->name.long_name.zeroes              = 0;
    symbol->name.long_name.string_table_offset = symbol_name_off;
    symbol->section_number                     = 1;
    symbol->storage_class                      = COFF_SymStorageClass_EXTERNAL;
  }
  
  // update string table size
  *string_table_size_ptr = (list.total_size - string_table_base);
  
  ProfEnd();
  return list;
}

internal String8List
lnk_build_null_thunk_data_obj(Arena *arena, String8 dll_name, COFF_MachineType machine)
{
  ProfBeginFunction();
  
  Assert(str8_match(str8_lit("dll"), str8_skip_last_dot(dll_name), StringMatchFlag_CaseInsensitive|StringMatchFlag_RightSideSloppy));
  
  String8List list = {0};
  
  COFF_Header *coff_header = push_array(arena, COFF_Header, 1);
  coff_header->machine = machine;
  str8_list_push(arena, &list, str8_struct(coff_header));
  
  coff_header->section_count = 2;
  COFF_SectionHeader *coff_sect_header_array = push_array(arena, COFF_SectionHeader, coff_header->section_count);
  str8_list_push(arena, &list, str8_array(coff_sect_header_array, coff_header->section_count));
  
  U64 lookup_entry_data_size = 8;
  U8 *lookup_entry_data      = push_array(arena, U8, lookup_entry_data_size);
  U64 lookup_entry_data_off  = list.total_size;
  str8_list_push(arena, &list, str8(lookup_entry_data, lookup_entry_data_size));
  
  U64 null_thunk_data_size = 8;
  U8 *null_thunk_data      = push_array(arena, U8, null_thunk_data_size);
  U64 null_thunk_data_off  = list.total_size;
  str8_list_push(arena, &list, str8(null_thunk_data, null_thunk_data_size));
  
  coff_header->symbol_count = 1;
  COFF_Symbol16 *symbol_array = push_array(arena, COFF_Symbol16, coff_header->symbol_count);
  coff_header->symbol_table_foff = safe_cast_u32(list.total_size);
  str8_list_push(arena, &list, str8_array(symbol_array, coff_header->symbol_count));
  
  U64 string_table_base = list.total_size;
  U32 *string_table_size_ptr = push_array(arena, U32, 1);
  str8_list_push(arena, &list, str8_struct(string_table_size_ptr));
  
  {
    COFF_SectionHeader *sect = &coff_sect_header_array[0];
    sect->name[0] = '.';
    sect->name[1] = 'i';
    sect->name[2] = 'd';
    sect->name[3] = 'a';
    sect->name[4] = 't';
    sect->name[5] = 'a';
    sect->name[6] = '$';
    sect->name[7] = '5';
    sect->fsize = lookup_entry_data_size;
    sect->foff  = lookup_entry_data_off;
    sect->flags = COFF_SectionFlag_CNT_INITIALIZED_DATA|(COFF_SectionAlign_8BYTES << COFF_SectionFlag_ALIGN_SHIFT)|COFF_SectionFlag_MEM_READ|COFF_SectionFlag_MEM_WRITE;
  }
  
  {
    COFF_SectionHeader *sect = &coff_sect_header_array[1];
    sect->name[0] = '.';
    sect->name[1] = 'i';
    sect->name[2] = 'd';
    sect->name[3] = 'a';
    sect->name[4] = 't';
    sect->name[5] = 'a';
    sect->name[6] = '$';
    sect->name[7] = '4';
    sect->fsize = null_thunk_data_size;
    sect->foff  = null_thunk_data_off;
    sect->flags = COFF_SectionFlag_CNT_INITIALIZED_DATA|(COFF_SectionAlign_8BYTES << COFF_SectionFlag_ALIGN_SHIFT)|COFF_SectionFlag_MEM_READ|COFF_SectionFlag_MEM_WRITE;
  }
  
  {
    String8 dll_name_no_ext = str8_substr(dll_name, r1u64(0, dll_name.size - 4));
    String8 symbol_name = push_str8f(arena, "\x7f%S_NULL_THUNK_DATA", dll_name_no_ext);
    
    U64 symbol_name_off = list.total_size - string_table_base;
    str8_list_push(arena, &list, push_cstr(arena, symbol_name));
    
    COFF_Symbol16 *symbol                      = &symbol_array[0];
    symbol->name.long_name.zeroes              = 0;
    symbol->name.long_name.string_table_offset = symbol_name_off;
    symbol->section_number                     = 1;
    symbol->storage_class                      = COFF_SymStorageClass_EXTERNAL;
  }
  
  // update string table size
  *string_table_size_ptr = (list.total_size - string_table_base);
  
  ProfEnd();
  return list;
}

internal String8
lnk_build_lib_member_header(Arena *arena, String8 name, COFF_TimeStamp time_stamp, U16 user_id, U16 group_id, U16 mode, U32 size)
{
  ProfBeginFunction();
  
  Assert(name.size < 16);
  Assert(user_id < 10000);
  Assert(group_id < 10000);
  Assert(mode < 10000);
  Assert(size < 1000000000);
  
  //U64 sizeof_member_header = /* name */ 16 + /* time */ 12 + /* user_id */ 6 + /* group id */ 6 + /* mode */ 8 + /* size */ 10;
  
  Temp scratch = scratch_begin(&arena, 1);
  String8List list = {0};
  str8_list_pushf(scratch.arena, &list, "%-16.*s", str8_varg(name));
  str8_list_pushf(scratch.arena, &list, "%-12u", time_stamp);
  str8_list_pushf(scratch.arena, &list, "%-6u", user_id);
  str8_list_pushf(scratch.arena, &list, "%-6u", group_id);
  str8_list_pushf(scratch.arena, &list, "%-8u", mode);
  str8_list_pushf(scratch.arena, &list, "%-10u", size);
  str8_list_pushf(scratch.arena, &list, "`\n");
  String8 result = str8_list_join(arena, &list, 0);

  Assert(result.size == COFF_ARCHIVE_MEMBER_HEADER_SIZE);
  scratch_end(scratch);
  ProfEnd();
  return result;
}

internal String8List
lnk_build_import_lib(TP_Context *tp, TP_Arena *arena, COFF_MachineType machine, COFF_TimeStamp time_stamp, String8 lib_name, String8 dll_name, LNK_ExportTable *exptab)
{
  ProfBeginFunction();
  Temp scratch = scratch_begin(arena->v, arena->count);

  dll_name = str8_skip_last_slash(dll_name);
  
  // These objects appear in first three members of any lib that linker produces with /dll.
  // Objects are used by MSVC linker to build import table.
  String8List import_obj_array[3];
  import_obj_array[0] = lnk_build_import_entry_obj(scratch.arena, dll_name, machine);
  import_obj_array[1] = lnk_build_null_import_descriptor_obj(scratch.arena, machine);
  import_obj_array[2] = lnk_build_null_thunk_data_obj(scratch.arena, dll_name, machine);

  // build input list
  LNK_InputObjList input_obj_list = {0};
  for (U64 i = 0; i < ArrayCount(import_obj_array); ++i) {
    LNK_InputObj *input = lnk_input_obj_list_push(scratch.arena, &input_obj_list);
    input->data         = str8_list_join(scratch.arena, &import_obj_array[i], 0);
    input->path         = dll_name;
    input->lib_path     = lib_name;
  }

  LNK_InputObj     **inputs   = lnk_array_from_input_obj_list(scratch.arena, input_obj_list);
  LNK_SectionTable  *st       = lnk_section_table_alloc(0,0,0);
  LNK_ObjList        obj_list = {0};
  lnk_obj_list_push_parallel(tp, arena, &obj_list, st, 0, input_obj_list.count, inputs);
  
  LNK_LibBuild import_lib = lnk_build_lib(scratch.arena, machine, time_stamp, dll_name, obj_list, exptab);
  B32 emit_second_member = 0; // MSVC linker refuses to link with lib that has the second member.
  String8List coff_archive_data = lnk_coff_archive_from_lib_build(arena->v[0], &import_lib, emit_second_member, time_stamp, /* -rw-r--r-- */ 644);
  
  // cleanup memory
  lnk_section_table_release(&st);
  scratch_end(scratch);

  ProfEnd();
  return coff_archive_data;
}

