////////////////////////////////
// Elf Parse Functions

MR4TH_SYMBOL ELF_Parse*
elfp_begin(Arena *arena, String8 data){
  // magic check
  B32          elf_magic = 0;
  ELF_Class    elf_class = ELF_Class_NONE;
  ELF_Encoding elf_encoding = ELF_Encoding_NONE;
  U32          elf_version = 0;
  ELF_OsAbiExtension elf_ext = ELF_OsAbiExtension_NONE;
  B32 good_headers = 0;
  if (data.size >= ELF_NUM_IDENT){
    elf_class    = data.str[ELF_IdentificationIdx_CLASS];
    elf_encoding = data.str[ELF_IdentificationIdx_DATA];
    elf_version  = data.str[ELF_IdentificationIdx_VERSION];
    elf_ext      = data.str[ELF_IdentificationIdx_OSABI];
    if (data.str[0] == ELF_Magic_Byte0 &&
        data.str[1] == ELF_Magic_Byte1 &&
        data.str[2] == ELF_Magic_Byte2 &&
        data.str[3] == ELF_Magic_Byte3 &&
        elf_version == 1 &&
        (elf_class == ELF_Class_32 || elf_class == ELF_Class_64) &&
        (elf_encoding == ELF_Encoding_2LSB ||
         elf_encoding == ELF_Encoding_2MSB)){
      switch (elf_class){
        case ELF_Class_32: {
          if (data.size >= sizeof(ELF_Header32)){ good_headers = 1; } }break;
        case ELF_Class_64: {
          if (data.size >= sizeof(ELF_Header64)){ good_headers = 1; } }break;
      }
    }
  }
  
  // header
  void *header = 0;
  U64 section_table_foff = 0;
  U16 section_count = 0;
  U16 section_size  = 0;
  U64 segment_table_foff = 0;
  U16 segment_count = 0;
  U16 segment_size  = 0;
  U16 string_section_index = 0;
  
  if (good_headers){
    switch (elf_class){
      case ELF_Class_32: {
        if (sizeof(ELF_Header32) <= data.size){
          header = data.str;
          ELF_Header32 *h = (ELF_Header32*)header;
          section_table_foff = h->section_table_foff;
          section_count = h->section_count;
          section_size  = h->section_size;
          segment_table_foff = h->segment_table_foff;
          segment_count = h->segment_count;
          segment_size  = h->segment_size;
          string_section_index = h->string_section_index;
        }
      }break;
      case ELF_Class_64: {
        if (sizeof(ELF_Header64) <= data.size){
          header = data.str;
          ELF_Header64 *h = (ELF_Header64*)header;
          section_table_foff = h->section_table_foff;
          section_count = h->section_count;
          section_size  = h->section_size;
          segment_table_foff = h->segment_table_foff;
          segment_count = h->segment_count;
          segment_size  = h->segment_size;
          string_section_index = h->string_section_index;
        }
      }break;
    }
  }
  
  // sections
  void *sections = 0;
  if (section_count > 0 && section_table_foff + section_count*section_size <= data.size){
    sections = data.str + section_table_foff;
  }
  
  // segments
  void *segments = 0;
  if (segment_count > 0 && segment_table_foff + segment_count*segment_size <= data.size){
    segments = data.str + segment_table_foff;
  }
  
  // string table
  U8 *section_strtable_first = 0;
  U8 *section_strtable_opl = 0;
  if (sections != 0){
    U32 index = 0;
    if (string_section_index != ELF_SectionIndex_UNDEF &&
        string_section_index != ELF_SectionIndex_XINDEX){
      index = string_section_index;
    }
    else if (string_section_index == ELF_SectionIndex_XINDEX){
      switch (elf_class){
        case ELF_Class_32: index = ((ELF_Section32*)sections)->link; break;
        case ELF_Class_64: index = ((ELF_Section64*)sections)->link; break;
      }
    }
    if (index > 0){
      U64 offset = 0;
      U64 size = 0;
      switch (elf_class){
        case ELF_Class_32:
        {
          offset = ((ELF_Section32*)sections)[index].offset;
          size = ((ELF_Section32*)sections)[index].size;
        }break;
        case ELF_Class_64:
        {
          offset = ((ELF_Section64*)sections)[index].offset;
          size = ((ELF_Section64*)sections)[index].size;
        }break;
      }
      if (offset < data.size && offset + size < data.size){
        section_strtable_first = data.str + offset;
        section_strtable_opl = data.str + offset + size;
      }
    }
  }
  
  // find symtab & dynsym sections
  void *symbols[ELFP_SYMBOL_TABLE_COUNT] = {0};
  U32   symbol_count[ELFP_SYMBOL_TABLE_COUNT] = {0};
  U8   *symbol_strtable_first[ELFP_SYMBOL_TABLE_COUNT] = {0};
  U8   *symbol_strtable_opl[ELFP_SYMBOL_TABLE_COUNT] = {0};
  if (sections != 0){
    for (U32 i = 0; i < section_count; i += 1){
      ELF_SectionType type = 0;
      U64 foff = 0;
      U64 opl  = 0;
      switch (elf_class){
        case ELF_Class_32: {
          ELF_Section32 *sec32 = (ELF_Section32*)sections + i;
          type = sec32->type;
          foff = sec32->offset;
          opl  = foff + sec32->size;
        }break;
        case ELF_Class_64: {
          ELF_Section64 *sec64 = (ELF_Section64*)sections + i;
          type = sec64->type;
          foff = sec64->offset;
          opl  = foff + sec64->size;
        }break;
      }
      U32 idx = ELFP_SYMBOL_TABLE_COUNT;
      switch (type){
        case ELF_SectionType_SYMTAB: idx = ELFP_SYMBOL_TABLE_symtab; break;
        case ELF_SectionType_DYNSYM: idx = ELFP_SYMBOL_TABLE_dynsym; break;
      }
      if (idx < ELFP_SYMBOL_TABLE_COUNT &&
          foff < data.size && opl < data.size){
        U32 link = 0;
        //U32 info = 0;
        U32 count = 0;
        switch (elf_class){
          case ELF_Class_32: {
            ELF_Section32 *sec32 = (ELF_Section32*)sections + i;
            link = sec32->link;
            //info = sec32->info;
            count = sec32->size/sizeof(ELF_Symbol32);
          }break;
          case ELF_Class_64: {
            ELF_Section64 *sec64 = (ELF_Section64*)sections + i;
            link = sec64->link;
            //info = sec64->info;
            count = sec64->size/sizeof(ELF_Symbol64);
          }break;
        }
        symbols[idx] = data.str + foff;
        symbol_count[idx] = count;
        if (link < section_count){
          U64 strtable_off = 0;
          U64 strtable_size = 0;
          switch (elf_class){
            case ELF_Class_32: {
              ELF_Section32 *sec32 = (ELF_Section32*)sections + link;
              strtable_off = sec32->offset;
              strtable_size = sec32->size;
            }break;
            case ELF_Class_64: {
              ELF_Section64 *sec64 = (ELF_Section64*)sections + link;
              strtable_off = sec64->offset;
              strtable_size = sec64->size;
            }break;
          }
          if (strtable_off < data.size &&
              strtable_off + strtable_size < data.size){
            symbol_strtable_first[idx] = data.str + strtable_off;
            symbol_strtable_opl[idx] = data.str + strtable_off + strtable_size;
          }
        }
      }
    }
  }
  
  // fill result
  ELF_Parse *elf = 0;
  if (header != 0){
    elf = push_array(arena, ELF_Parse, 1);
    elf->elf_class = elf_class;
    elf->encoding = elf_encoding;
    elf->section_count = section_count;
    elf->segment_count = segment_count;
    elf->header = header;
    elf->size = data.size;
    elf->sections = sections;
    elf->segments = segments;
    elf->section_strtable_first = section_strtable_first;
    elf->section_strtable_opl = section_strtable_opl;
    MemoryCopyArray(elf->symbols, symbols);
    MemoryCopyArray(elf->symbol_count, symbol_count);
    MemoryCopyArray(elf->symbol_strtable_first, symbol_strtable_first);
    MemoryCopyArray(elf->symbol_strtable_opl, symbol_strtable_opl);
  }
  
  return(elf);
}

MR4TH_SYMBOL void
elfp_header_read(ELF_Parse *elf, ELF_Header64 *out){
  if (elf->header != 0){
    switch (elf->elf_class){
      case ELF_Class_32: {
        elf_header64_from_header32(out, (ELF_Header32*)elf->header, elf->encoding);
      }break;
      case ELF_Class_64: {
        elf_header64_from_header64(out, (ELF_Header64*)elf->header, elf->encoding);
      }break;
    }
  }
}

MR4TH_SYMBOL U32
elfp_section_count(ELF_Parse *elf){
  U32 result = elf->section_count;
  return(result);
}

MR4TH_SYMBOL void
elfp_section_read(ELF_Parse *elf, U32 idx, ELF_Section64 *out){
  if (elf->sections != 0 && idx < elf->section_count){
    switch (elf->elf_class){
      case ELF_Class_32: {
        elf_section64_from_section32(out, (ELF_Section32*)elf->sections + idx, elf->encoding);
      }break;
      case ELF_Class_64: {
        elf_section64_from_section64(out, (ELF_Section64*)elf->sections + idx, elf->encoding);
      }break;
    }
  }
}

MR4TH_SYMBOL String8
elfp_section_name(ELF_Parse *elf, U32 idx){
  String8 result = {0};
  if (elf->sections != 0 && idx < elf->section_count){
    U32 name = 0;
    switch (elf->elf_class){
      case ELF_Class_32: name = ((ELF_Section32*)elf->sections)[idx].name; break;
      case ELF_Class_64: name = ((ELF_Section64*)elf->sections)[idx].name; break;
    }
    result = str8_cstring_capped(elf->section_strtable_first + name,
                                 elf->section_strtable_opl);
  }
  return(result);
}

MR4TH_SYMBOL U32
elfp_segment_count(ELF_Parse *elf){
  U32 result = elf->segment_count;
  return(result);
}

MR4TH_SYMBOL void
elfp_segment_read(ELF_Parse *elf, U32 idx, ELF_Segment64 *out){
  if (elf->segments != 0 && idx < elf->segment_count){
    switch (elf->elf_class){
      case ELF_Class_32: {
        elf_segment64_from_segment32(out, (ELF_Segment32*)elf->segments + idx, elf->encoding);
      }break;
      case ELF_Class_64: {
        elf_segment64_from_segment64(out, (ELF_Segment64*)elf->segments + idx, elf->encoding);
      }break;
    }
  }
}

MR4TH_SYMBOL U32
elfp_symbol_count(ELF_Parse *elf, U32 table){
  U32 result = 0;
  if (table < ELFP_SYMBOL_TABLE_COUNT){
    result = elf->symbol_count[table];
  }
  return(result);
}

MR4TH_SYMBOL void
elfp_symbol_read(ELF_Parse *elf, U32 table, U32 idx, ELF_Symbol64 *out){
  if (table < ELFP_SYMBOL_TABLE_COUNT){
    void *symbols = elf->symbols[table];
    switch (elf->elf_class){
      case ELF_Class_32: {
        elf_symbol64_from_symbol32(out, (ELF_Symbol32*)symbols + idx, elf->encoding);
      }break;
      case ELF_Class_64: {
        elf_symbol64_from_symbol64(out, (ELF_Symbol64*)symbols + idx, elf->encoding);
      }break;
    }
  }
}

MR4TH_SYMBOL String8
elfp_symbol_name(ELF_Parse *elf, U32 table, U32 idx){
  String8 result = {0};
  if (table < ELFP_SYMBOL_TABLE_COUNT){
    void *s = elf->symbols[table];
    U8 *string_table_first = elf->symbol_strtable_first[table];
    U8 *string_table_opl = elf->symbol_strtable_opl[table];
    U32 name = 0;
    switch (elf->elf_class){
      case ELF_Class_32: name = ((ELF_Symbol32*)s)[idx].name; break;
      case ELF_Class_64: name = ((ELF_Symbol64*)s)[idx].name; break;
    }
    if (string_table_first != 0){
      result = str8_cstring_capped(string_table_first + name,
                                   string_table_opl);
    }
  }
  return(result);
}

MR4TH_SYMBOL U32
elfp_relocs_count(ELF_Parse *elf, U32 secidx){
  U32 result = 0;
  if (elf->sections != 0 && secidx < elf->section_count){
    switch (elf->elf_class){
      case ELF_Class_32: {
        ELF_Section32 *sec32 = ((ELF_Section32*)elf->sections) + secidx;
        switch (sec32->type){
          case ELF_SectionType_REL:
          result = sec32->size/sizeof(ELF_Relocation32); break;
          case ELF_SectionType_RELA:
          result = sec32->size/sizeof(ELF_RelocationAdd32); break;
        }
      }break;
      case ELF_Class_64: {
        ELF_Section64 *sec64 = ((ELF_Section64*)elf->sections) + secidx;
        switch (sec64->type){
          case ELF_SectionType_REL:
          result = sec64->size/sizeof(ELF_Relocation64); break;
          case ELF_SectionType_RELA:
          result = sec64->size/sizeof(ELF_RelocationAdd64); break;
        }
      }break;
    }
  }
  return(result);
}

MR4TH_SYMBOL void
elfp_relocs_read(ELF_Parse *elf, U32 secidx, U32 relocidx, ELF_RelocationAdd64 *out){
  if (elf->sections != 0 && secidx < elf->section_count){
    switch (elf->elf_class){
      case ELF_Class_32: {
        ELF_Section32 *sec32 = ((ELF_Section32*)elf->sections) + secidx;
        U64 opl  = ClampTop(sec32->offset + sec32->size, elf->size);
        U64 size = opl - sec32->offset;
        switch (sec32->type){
          case ELF_SectionType_REL: {
            ELF_Relocation32 *relocs = (ELF_Relocation32*)((U8*)elf->header + sec32->offset);
            U32 count = sec32->size/sizeof(*relocs);
            if (relocidx < count){
              elf_reloca64_from_reloc32(out, relocs + relocidx, elf->encoding);
            }
          }break;
          case ELF_SectionType_RELA: {
            ELF_RelocationAdd32 *relocs = (ELF_RelocationAdd32*)((U8*)elf->header + sec32->offset);
            U32 count = sec32->size/sizeof(*relocs);
            if (relocidx < count){
              elf_reloca64_from_reloca32(out, relocs + relocidx, elf->encoding);
            }
          }break;
        }
      }break;
      case ELF_Class_64: {
        ELF_Section64 *sec64 = ((ELF_Section64*)elf->sections) + secidx;
        U64 opl  = ClampTop(sec64->offset + sec64->size, elf->size);
        U64 size = opl - sec64->offset;
        switch (sec64->type){
          case ELF_SectionType_REL: {
            ELF_Relocation64 *relocs = (ELF_Relocation64*)((U8*)elf->header + sec64->offset);
            U32 count = sec64->size/sizeof(*relocs);
            if (relocidx < count){
              elf_reloca64_from_reloc64(out, relocs + relocidx, elf->encoding);
            }
          }break;
          case ELF_SectionType_RELA: {
            ELF_RelocationAdd64 *relocs = (ELF_RelocationAdd64*)((U8*)elf->header + sec64->offset);
            U32 count = sec64->size/sizeof(*relocs);
            if (relocidx < count){
              elf_reloca64_from_reloca64(out, relocs + relocidx, elf->encoding);
            }
          }break;
        }
      }break;
    }
  }
}
