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

#pragma once

typedef enum
{
  LNK_SymbolScopeIndex_Defined,
  LNK_SymbolScopeIndex_Internal, // symbols defined by linker
  LNK_SymbolScopeIndex_Weak,
  LNK_SymbolScopeIndex_Lib,
  LNK_SymbolScopeIndex_Count
} LNK_SymbolScopeIndex;

enum
{
  LNK_SymbolScopeFlag_Defined  = 1,
  LNK_SymbolScopeFlag_Internal = 2,
  LNK_SymbolScopeFlag_Weak     = 4,
  LNK_SymbolScopeFlag_Lib      = 8,
  
  LNK_SymbolScopeFlag_Main = LNK_SymbolScopeFlag_Defined | LNK_SymbolScopeFlag_Weak,
  LNK_SymbolScopeFlag_All  = LNK_SymbolScopeFlag_Defined | LNK_SymbolScopeFlag_Weak | LNK_SymbolScopeFlag_Lib | LNK_SymbolScopeFlag_Internal
};
typedef U64 LNK_SymbolScopeFlags;

typedef enum
{
  LNK_DefinedSymbolVisibility_Static,
  LNK_DefinedSymbolVisibility_Extern,
  LNK_DefinedSymbolVisibility_Internal,
} LNK_DefinedSymbolVisibility;

enum
{
  LNK_DefinedSymbolFlag_IsFunc  = (1 << 0),
  LNK_DefinedSymbolFlag_IsThunk = (1 << 1),
};
typedef U64 LNK_DefinedSymbolFlags;

typedef enum
{
  LNK_DefinedSymbolValue_Null,
  LNK_DefinedSymbolValue_Chunk,
  LNK_DefinedSymbolValue_VA
} LNK_DefinedSymbolValueType;

typedef struct LNK_DefinedSymbol
{
  LNK_DefinedSymbolFlags     flags;
  LNK_DefinedSymbolValueType value_type;
  union {
    struct {
      LNK_Chunk            *chunk;
      U64                   chunk_offset;
      U32                   check_sum;
      COFF_ComdatSelectType selection;
    };
    U64 va;
  } u;
} LNK_DefinedSymbol;

typedef struct LNK_WeakSymbol
{
  LNK_SymbolScopeFlags scope_flags;
  COFF_WeakExtType     lookup_type;
  struct LNK_Symbol   *fallback_symbol;
} LNK_WeakSymbol;

typedef struct LNK_UndefinedSymbol
{
  LNK_SymbolScopeFlags scope_flags;
} LNK_UndefinedSymbol;

typedef struct LNK_LazySymbol
{
  struct LNK_Lib *lib;
  U64             member_offset;
} LNK_LazySymbol;

#define LNK_Symbol_IsDefined(type) ((type) == LNK_Symbol_DefinedStatic || (type) == LNK_Symbol_DefinedExtern || (type) == LNK_Symbol_DefinedInternal)
typedef enum 
{
  LNK_Symbol_Null,
  LNK_Symbol_DefinedStatic,
  LNK_Symbol_DefinedExtern,
  LNK_Symbol_DefinedInternal,
  LNK_Symbol_Weak,
  LNK_Symbol_Lazy,
  LNK_Symbol_Undefined,
} LNK_SymbolType;

typedef struct LNK_Symbol
{
  String8         name;
  LNK_SymbolType  type;
  struct LNK_Obj *obj;
  union {
    LNK_DefinedSymbol   defined;
    LNK_WeakSymbol      weak;
    LNK_UndefinedSymbol undefined;
    LNK_LazySymbol      lazy;
  } u;
} LNK_Symbol;

typedef struct LNK_SymbolNode
{
  struct LNK_SymbolNode *next;
  LNK_Symbol            *data;
} LNK_SymbolNode;

typedef struct LNK_SymbolList
{
  U64             count;
  LNK_SymbolNode *first;
  LNK_SymbolNode *last;
} LNK_SymbolList;

typedef struct LNK_SymbolNodeArray
{
  U64              count;
  LNK_SymbolNode **v;
} LNK_SymbolNodeArray;

typedef struct LNK_SymbolArray
{
  U64         count;
  LNK_Symbol *v;
} LNK_SymbolArray;

typedef struct LNK_SymbolHashTrie
{
  String8                   *name;
  LNK_Symbol                *symbol;
  struct LNK_SymbolHashTrie *child[4];
} LNK_SymbolHashTrie;

typedef struct LNK_SymbolHashTrieChunk
{
  struct LNK_SymbolHashTrieChunk *next;
  U64                             count;
  U64                             cap;
  LNK_SymbolHashTrie             *v;
} LNK_SymbolHashTrieChunk;

typedef struct LNK_SymbolHashTrieChunkList
{
  U64                      count;
  LNK_SymbolHashTrieChunk *first;
  LNK_SymbolHashTrieChunk *last;
} LNK_SymbolHashTrieChunkList;

typedef struct LNK_SymbolTable
{
  TP_Arena                    *arena;
  LNK_SymbolHashTrie          *scopes[LNK_SymbolScopeIndex_Count];
  LNK_SymbolHashTrieChunkList *chunk_lists[LNK_SymbolScopeIndex_Count];
} LNK_SymbolTable;

////////////////////////////////
// parallel for wrappers

typedef struct
{
  LNK_SymbolTable *symtab;
  Rng1U64         *ranges;
  LNK_Symbol      *arr;
} LNK_LazySymbolInserter;

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

global read_only LNK_Symbol   g_null_symbol     = { str8_lit_comp("NULL"), LNK_Symbol_DefinedStatic };
global read_only LNK_Symbol  *g_null_symbol_ptr = &g_null_symbol;

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

internal void lnk_init_symbol(LNK_Symbol *symbol, String8 name, LNK_SymbolType type);
internal void lnk_init_defined_symbol(LNK_Symbol *symbol, String8 name, LNK_DefinedSymbolVisibility visibility, LNK_DefinedSymbolFlags flags);
internal void lnk_init_defined_symbol_chunk(LNK_Symbol *symbol, String8 name, LNK_DefinedSymbolVisibility visibility, LNK_DefinedSymbolFlags flags, LNK_Chunk *chunk, U64 offset, COFF_ComdatSelectType selection, U32 check_sum);
internal void lnk_init_defined_symbol_va(LNK_Symbol *symbol, String8 name, LNK_DefinedSymbolVisibility visibility, LNK_DefinedSymbolFlags flags, U64 va);
internal void lnk_init_undefined_symbol(LNK_Symbol *symbol, String8 name, LNK_SymbolScopeFlags scope_flags);
internal void lnk_init_weak_symbol(LNK_Symbol *symbol, String8 name, COFF_WeakExtType lookup, LNK_Symbol *fallback);

internal LNK_Symbol * lnk_make_defined_symbol(Arena *arena, String8 name, LNK_DefinedSymbolVisibility visibility, LNK_DefinedSymbolFlags flags);
internal LNK_Symbol * lnk_make_defined_symbol_chunk(Arena *arena, String8 name, LNK_DefinedSymbolVisibility visibility, LNK_DefinedSymbolFlags flags, LNK_Chunk *chunk, U64 offset, COFF_ComdatSelectType selection, U32 check_sum);
internal LNK_Symbol * lnk_make_defined_symbol_va(Arena *arena, String8 name, LNK_DefinedSymbolVisibility visibility, LNK_DefinedSymbolFlags flags, U64 va);
internal LNK_Symbol * lnk_make_undefined_symbol(Arena *arena, String8 name, LNK_SymbolScopeFlags scope_flags);
internal LNK_Symbol * lnk_make_weak_symbol(Arena *arena, String8 name, COFF_WeakExtType lookup, LNK_Symbol *fallback);
internal LNK_Symbol * lnk_make_lazy_symbol(Arena *arena, String8 name, struct LNK_Lib *lib, U64 member_offset);

internal LNK_Chunk * lnk_chunk_from_symbol(LNK_Symbol *symbol);

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

internal void                lnk_symbol_list_push_node(LNK_SymbolList *list, LNK_SymbolNode *node);
internal LNK_SymbolNode *    lnk_symbol_list_push(Arena *arena, LNK_SymbolList *list, LNK_Symbol *symbol);
internal void                lnk_symbol_list_concat_in_place(LNK_SymbolList *list, LNK_SymbolList *to_concat);
internal LNK_SymbolList      lnk_symbol_list_from_array(Arena *arena, LNK_SymbolArray arr);
internal LNK_SymbolNodeArray lnk_symbol_node_array_from_list(Arena *arena, LNK_SymbolList list);
internal LNK_SymbolArray     lnk_symbol_array_from_list(Arena *arena, LNK_SymbolList list);

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

internal void                 lnk_symbol_hash_trie_insert_or_replace(Arena *arena, LNK_SymbolHashTrieChunkList *chunk_list, LNK_SymbolHashTrie **trie, U64 hash, LNK_Symbol *new_symbol);
internal LNK_SymbolHashTrie * lnk_symbol_hash_trie_search(LNK_SymbolHashTrie *trie, U64 hash, String8 name);
internal void                 lnk_symbol_hash_trie_remove(LNK_SymbolHashTrie *trie);

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

internal U64  lnk_symbol_hash(String8 string);

internal LNK_SymbolTable * lnk_symbol_table_init(TP_Arena *arena);
internal LNK_Symbol *      lnk_symbol_table_search_hash(LNK_SymbolTable *symtab, LNK_SymbolScopeFlags scope, U64 hash, String8 name);
internal LNK_Symbol *      lnk_symbol_table_search(LNK_SymbolTable *symtab, LNK_SymbolScopeFlags scope, String8 name);
internal LNK_Symbol *      lnk_symbol_table_searchf(LNK_SymbolTable *symtab, LNK_SymbolScopeFlags scope, char *fmt, ...);
internal void              lnk_symbol_table_push_hash(LNK_SymbolTable *symtab, U64 hash, LNK_Symbol *symbol);
internal void              lnk_symbol_table_push(LNK_SymbolTable *symtab, LNK_Symbol *symbol);
internal void              lnk_symbol_table_remove(LNK_SymbolTable *symtab, LNK_SymbolScopeIndex scope, String8 name);

internal LNK_Symbol * lnk_resolve_symbol(LNK_SymbolTable *symtab, LNK_Symbol *resolve_symbol);

