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

internal LNK_Reloc *
lnk_reloc_list_reserve(Arena *arena, LNK_RelocList *list, U64 count)
{
  LNK_Reloc *arr = NULL;
  if (count) {
    arr = push_array(arena, LNK_Reloc, count);
    for (LNK_Reloc *ptr = arr, *opl = arr + count; ptr < opl; ++ptr) {
      SLLQueuePush(list->first, list->last, ptr);
    }
    list->count += count;
  }
  return arr;
}

internal LNK_Reloc *
lnk_reloc_list_push(Arena *arena, LNK_RelocList *list)
{
  LNK_Reloc *node = push_array(arena, LNK_Reloc, 1);
  SLLQueuePush(list->first, list->last, node);
  list->count += 1;
  return node;
}

internal LNK_RelocList
lnk_reloc_list_copy(Arena *arena, LNK_RelocList *list)
{
  LNK_RelocList result = {0};
  for (LNK_Reloc *n = list->first; n != NULL; n = n->next) {
    LNK_Reloc *r = lnk_reloc_list_push(arena, &result);
    r->chunk = n->chunk;
    r->type = n->type;
    r->apply_off = n->apply_off;
    r->symbol = n->symbol;
  }
  return result;
}

internal void
lnk_reloc_list_concat_in_place(LNK_RelocList *list, LNK_RelocList *to_concat)
{
  SLLConcatInPlace(list, to_concat);
}

internal void
lnk_reloc_list_concat_in_place_arr(LNK_RelocList *list, LNK_RelocList *arr, U64 count)
{
  SLLConcatInPlaceArray(list, arr, count);
}

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

internal LNK_RelocList
lnk_reloc_list_from_coff_reloc_array(Arena *arena, COFF_MachineType machine, LNK_Chunk *chunk, LNK_SymbolArray symbol_array, COFF_Reloc *reloc_v, U64 reloc_count)
{
  LNK_RelocList reloc_list = {0};

  LNK_Reloc  *reloc_arr      = lnk_reloc_list_reserve(arena, &reloc_list, reloc_count);
  LNK_Reloc  *reloc_ptr      = reloc_arr;
  LNK_Reloc  *reloc_opl      = reloc_arr + reloc_count;
  COFF_Reloc *coff_reloc_ptr = reloc_v;

  for (; reloc_ptr < reloc_opl; ++reloc_ptr, ++coff_reloc_ptr) {
    LNK_RelocType  type        = lnk_ext_reloc_type_from_coff(machine, coff_reloc_ptr->type);
    LNK_Chunk     *reloc_chunk = chunk;
    U64            apply_off   = coff_reloc_ptr->apply_off;
    LNK_Symbol    *symbol      = symbol_array.v + coff_reloc_ptr->isymbol;

    if (chunk->type == LNK_Chunk_List) {
      U64 cursor = 0;
      for (LNK_ChunkNode *c = chunk->u.list->first; c != 0; c = c->next) {
        Assert(c->data->type == LNK_Chunk_Leaf);
        if (coff_reloc_ptr->apply_off < cursor + c->data->u.leaf.size) {
          reloc_chunk = c->data;
          apply_off   = coff_reloc_ptr->apply_off - cursor;
          break;
        }
        cursor += c->data->u.leaf.size;
      }
    }

    Assert(coff_reloc_ptr->isymbol < symbol_array.count);
    reloc_ptr->chunk     = reloc_chunk;
    reloc_ptr->type      = type;
    reloc_ptr->apply_off = apply_off;
    reloc_ptr->symbol    = symbol;
  }
  return reloc_list;
}

internal LNK_Reloc **
lnk_reloc_array_from_list(Arena *arena, LNK_RelocList list)
{
  LNK_Reloc **arr = push_array_no_zero(arena, LNK_Reloc *, list.count);
  U64 count = 0;
  for (LNK_Reloc *node = list.first; node != 0; node = node->next) {
    Assert(count < list.count);
    arr[count++] = node;
  }
  return arr;
}

internal LNK_RelocType
lnk_ext_reloc_type_from_coff(COFF_MachineType machine, U32 type)
{
  LNK_RelocType result = LNK_Reloc_NULL;
  switch (machine) {
  case COFF_MachineType_UNKNOWN: break;
  case COFF_MachineType_X64: {
    switch (type) {
    case COFF_RelocTypeX64_ABS:       result = LNK_Reloc_NULL;        break;
    case COFF_RelocTypeX64_ADDR64:    result = LNK_Reloc_ADDR_64;     break;
    case COFF_RelocTypeX64_ADDR32:    result = LNK_Reloc_ADDR_32;     break;
    case COFF_RelocTypeX64_ADDR32NB:  result = LNK_Reloc_VIRT_OFF_32; break;
    case COFF_RelocTypeX64_REL32:     result = LNK_Reloc_REL32;       break;
    case COFF_RelocTypeX64_REL32_1:   result = LNK_Reloc_REL32_1;     break;
    case COFF_RelocTypeX64_REL32_2:   result = LNK_Reloc_REL32_2;     break;
    case COFF_RelocTypeX64_REL32_3:   result = LNK_Reloc_REL32_3;     break;
    case COFF_RelocTypeX64_REL32_4:   result = LNK_Reloc_REL32_4;     break;
    case COFF_RelocTypeX64_REL32_5:   result = LNK_Reloc_REL32_5;     break;
    case COFF_RelocTypeX64_SECTION:   result = LNK_Reloc_SECT_IDX;    break;
    case COFF_RelocTypeX64_SECREL:    result = LNK_Reloc_SECT_REL;    break;
    case COFF_RelocTypeX64_SECREL7:   lnk_not_implemented("TODO: COFF_RelocTypeX64_SECREL7"); break;
    case COFF_RelocTypeX64_TOKEN:     lnk_not_implemented("TODO: COFF_RelocTypeX64_TOKEN");   break;
    case COFF_RelocTypeX64_SREL32:    lnk_not_implemented("TODO: COFF_RelocTypeX64_SREL32");  break;
    case COFF_RelocTypeX64_PAIR:      lnk_not_implemented("TODO: COFF_RelocTypeX64_PAIR");    break;
    case COFF_RelocTypeX64_SSPAN32:   lnk_not_implemented("TODO: COFF_RelocTypeX64_SSPAN32"); break;
    default: lnk_invalid_path("unknown relocation type 0x%X", type);
    }
  } break;
  default: lnk_not_implemented("TODO: define remap for coff reloc types"); break;
  }
  return result;
}

internal U32
lnk_ext_reloc_type_to_coff(COFF_MachineType machine, LNK_RelocType type)
{
  U32 result = 0;
  switch (machine) {
  case COFF_MachineType_X64: {
    switch (type) {
    case LNK_Reloc_NULL:        result = COFF_RelocTypeX64_ABS;      break;
    case LNK_Reloc_ADDR_64:     result = COFF_RelocTypeX64_ADDR64;   break;
    case LNK_Reloc_ADDR_32:     result = COFF_RelocTypeX64_ADDR32;   break;
    case LNK_Reloc_VIRT_OFF_32: result = COFF_RelocTypeX64_ADDR32NB; break;
    case LNK_Reloc_REL32:       result = COFF_RelocTypeX64_REL32;    break;
    case LNK_Reloc_REL32_1:     result = COFF_RelocTypeX64_REL32_1;  break;
    case LNK_Reloc_REL32_2:     result = COFF_RelocTypeX64_REL32_2;  break;
    case LNK_Reloc_REL32_3:     result = COFF_RelocTypeX64_REL32_3;  break;
    case LNK_Reloc_REL32_4:     result = COFF_RelocTypeX64_REL32_4;  break;
    case LNK_Reloc_REL32_5:     result = COFF_RelocTypeX64_REL32_5;  break;
    case LNK_Reloc_SECT_IDX:    result = COFF_RelocTypeX64_SECTION;  break;
    case LNK_Reloc_SECT_REL:    result = COFF_RelocTypeX64_SECREL;   break;
    default: InvalidPath;
    }
  } break;
  default: lnk_not_implemented("TODO: support for machine 0x%X", machine); break;
  }
  return result;
}


