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

#if 0
internal void
coff_print_archive_member_header(Arena *arena, String8List *out, String8 indent, COFF_ParsedArchiveMemberHeader header, String8 long_names)
{
  Temp scratch = scratch_begin(&arena, 1);
  String8 time_stamp = coff_string_from_time_stamp(scratch.arena, header.time_stamp);
  
  rd_printf("Name      : %S"             , header.name    );
  rd_printf("Time Stamp: (%#x) %S"       , header.time_stamp, time_stamp     );
  rd_printf("User ID   : %u"             , header.user_id );
  rd_printf("Group ID  : %u"             , header.group_id);
  rd_printf("Mode      : %S"             , header.mode    );
  rd_printf("Data      : [%#llx-%#llx)", header.data_range.min, header.data_range.max);
  
  scratch_end(scratch);
}

internal void
coff_print_section_table(Arena               *arena,
                         String8List        *out,
                         String8             indent,
                         String8             string_table,
                         COFF_Symbol32Array  symbol_table,
                         U64                 section_count,
                         COFF_SectionHeader *section_table)
{
  Temp scratch = scratch_begin(&arena, 1);
  
  String8 *symlinks = push_array(scratch.arena, String8, section_count);
  for (U64 i = 0; i < symbol_table.count; ++i) {
    COFF_Symbol32              *symbol = symbol_table.v+i;
    COFF_SymbolValueInterpType  interp = coff_interp_symbol(symbol->section_number, symbol->value, symbol->storage_class);
    if (interp == COFF_SymbolValueInterp_Regular &&
        symbol->aux_symbol_count == 0 &&
        (symbol->storage_class == COFF_SymStorageClass_External || symbol->storage_class == COFF_SymStorageClass_Static)) {
      if (symbol->section_number > 0 && symbol->section_number <= symbol_table.count) {
        COFF_SectionHeader *header = section_table+(symbol->section_number-1);
        if (header->flags & COFF_SectionFlag_LnkCOMDAT) {
          symlinks[symbol->section_number-1] = coff_read_symbol_name(string_table, &symbol->name);
        }
      }
    }
    i += symbol->aux_symbol_count;
  }
  
  if (section_count) {
    rd_printf("# Section Table");
    rd_indent();
    
    rd_printf("%-4s %-8s %-8s %-8s %-8s %-8s %-8s %-8s %-8s %-8s %-5s %-10s %s",
              "No.",
              "Name",
              "VirtSize",
              "VirtOff",
              "FileSize",
              "FileOff",
              "RelocOff",
              "LinesOff",
              "RelocCnt",
              "LineCnt",
              "Align",
              "Flags",
              "Symlink");
    
    for (U64 i = 0; i < section_count; ++i) {
      COFF_SectionHeader *header = section_table+i;
      
      String8 name      = str8_cstring_capped(header->name, header->name+sizeof(header->name));
      String8 full_name = coff_name_from_section_header(string_table, header);
      
      String8 align;
      {
        U64 align_size = coff_align_size_from_section_flags(header->flags);
        align = push_str8f(scratch.arena, "%u", align_size);
      }
      
      String8 flags;
      {
        String8List mem_flags = {0};
        if (header->flags & COFF_SectionFlag_MemRead) {
          str8_list_pushf(scratch.arena, &mem_flags, "r");
        }
        if (header->flags & COFF_SectionFlag_MemWrite) {
          str8_list_pushf(scratch.arena, &mem_flags, "w");
        }
        if (header->flags & COFF_SectionFlag_MemExecute) {
          str8_list_pushf(scratch.arena, &mem_flags, "x");
        }
        
        String8List cnt_flags = {0};
        if (header->flags & COFF_SectionFlag_CntCode) {
          str8_list_pushf(scratch.arena, &cnt_flags, "c");
        }
        if (header->flags & COFF_SectionFlag_CntInitializedData) {
          str8_list_pushf(scratch.arena, &cnt_flags, "d");
        }
        if (header->flags & COFF_SectionFlag_CntUninitializedData) {
          str8_list_pushf(scratch.arena, &cnt_flags, "u");
        }
        
        String8List mem_extra_flags = {0};
        if (header->flags & COFF_SectionFlag_MemShared) {
          str8_list_pushf(scratch.arena, &mem_flags, "s");
        }
        if (header->flags & COFF_SectionFlag_Mem16Bit) {
          str8_list_pushf(scratch.arena, &mem_extra_flags, "h");
        }
        if (header->flags & COFF_SectionFlag_MemLocked) {
          str8_list_pushf(scratch.arena, &mem_extra_flags, "l");
        }
        if (header->flags & COFF_SectionFlag_MemDiscardable) {
          str8_list_pushf(scratch.arena, &mem_extra_flags, "d");
        }
        if (header->flags & COFF_SectionFlag_MemNotCached) {
          str8_list_pushf(scratch.arena, &mem_extra_flags, "c");
        }
        if (header->flags & COFF_SectionFlag_MemNotPaged) {
          str8_list_pushf(scratch.arena, &mem_extra_flags, "p");
        }
        
        String8List lnk_flags = {0};
        if (header->flags & COFF_SectionFlag_LnkRemove) {
          str8_list_pushf(scratch.arena, &lnk_flags, "r");
        }
        if (header->flags & COFF_SectionFlag_LnkCOMDAT) {
          str8_list_pushf(scratch.arena, &lnk_flags, "c");
        }
        if (header->flags & COFF_SectionFlag_LnkOther) {
          str8_list_pushf(scratch.arena, &lnk_flags, "o");
        }
        if (header->flags & COFF_SectionFlag_LnkInfo) {
          str8_list_pushf(scratch.arena, &lnk_flags, "i");
        }
        if (header->flags & COFF_SectionFlag_LnkNRelocOvfl) {
          str8_list_pushf(scratch.arena, &lnk_flags, "f");
        }
        
        String8List other_flags = {0};
        if (header->flags & COFF_SectionFlag_TypeNoPad) {
          str8_list_pushf(scratch.arena, &other_flags, "n");
        }
        if (header->flags & COFF_SectionFlag_GpRel) {
          str8_list_pushf(scratch.arena, &other_flags, "g");
        }
        
        String8 mem = str8_list_join(scratch.arena, &mem_flags, 0);
        String8 cnt = str8_list_join(scratch.arena, &cnt_flags, 0);
        String8 lnk = str8_list_join(scratch.arena, &lnk_flags, 0);
        String8 ext = str8_list_join(scratch.arena, &mem_extra_flags, 0);
        String8 oth = str8_list_join(scratch.arena, &other_flags, 0);
        
        String8List f = {0};
        str8_list_push(scratch.arena, &f, mem);
        str8_list_push(scratch.arena, &f, cnt);
        str8_list_push(scratch.arena, &f, ext);
        str8_list_push(scratch.arena, &f, lnk);
        str8_list_push(scratch.arena, &f, oth);
        
        flags = str8_list_join(scratch.arena, &f, &(StringJoin){ .sep = str8_lit("-") });
        
        if (!flags.size) {
          flags = str8_lit("none");
        }
      }
      
      String8List l = {0};
      str8_list_pushf(scratch.arena, &l, "%-4x",  i+1                );
      str8_list_pushf(scratch.arena, &l, "%-8S",  name               );
      str8_list_pushf(scratch.arena, &l, "%08x",  header->vsize      );
      str8_list_pushf(scratch.arena, &l, "%08x",  header->voff       );
      str8_list_pushf(scratch.arena, &l, "%08x",  header->fsize      );
      str8_list_pushf(scratch.arena, &l, "%08x",  header->foff       );
      str8_list_pushf(scratch.arena, &l, "%08x",  header->relocs_foff);
      str8_list_pushf(scratch.arena, &l, "%08x",  header->lines_foff );
      str8_list_pushf(scratch.arena, &l, "%08x",  header->reloc_count);
      str8_list_pushf(scratch.arena, &l, "%08x",  header->line_count );
      str8_list_pushf(scratch.arena, &l, "%-5S",  align              );
      str8_list_pushf(scratch.arena, &l, "%-10S", flags              );
      if (symlinks[i].size > 0) {
        str8_list_pushf(scratch.arena, &l, "%S", symlinks[i]);
      } else {
        str8_list_pushf(scratch.arena, &l, "[no symlink]");
      }
      
      String8 line = str8_list_join(scratch.arena, &l, &(StringJoin){ .sep = str8_lit(" "), });
      rd_printf("%S", line);
      
      if (full_name.size != name.size) {
        rd_indent();
        rd_printf("Full Name: %S", full_name);
        rd_unindent();
      }
    }
    
    rd_newline();
    rd_printf("Flags:");
    rd_indent();
    rd_printf("r = MemRead    w = MemWrite        x = MemExecute");
    rd_printf("c = CntCode    d = InitializedData u = UninitializedData");
    rd_printf("s = MemShared  h = Mem16bit        l = MemLocked          d = MemDiscardable c = MemNotCached  p = MemNotPaged");
    rd_printf("r = LnkRemove  c = LnkComdat       o = LnkOther           i = LnkInfo        f = LnkNRelocOvfl");
    rd_printf("g = GpRel      n = TypeNoPad");
    rd_unindent();
    
    rd_unindent();
    rd_newline();
  }
  
  scratch_end(scratch);
}

internal void
coff_disasm_sections(Arena              *arena,
                     String8List        *out,
                     String8             indent,
                     String8             raw_data,
                     COFF_MachineType    machine,
                     U64                 image_base,
                     B32                 is_obj,
                     RD_MarkerArray     *section_markers,
                     U64                 section_count,
                     COFF_SectionHeader *sections)
{
  if (section_count) {
    for (U64 sect_idx = 0; sect_idx < section_count; ++sect_idx) {
      COFF_SectionHeader *sect = sections+sect_idx;
      if (sect->flags & COFF_SectionFlag_CntCode) {
        U64            sect_off    = is_obj ? sect->foff : sect->voff;
        U64            sect_size   = is_obj ? sect->fsize : sect->vsize;
        String8        raw_code    = str8_substr(raw_data, rng_1u64(sect->foff, sect->foff+sect_size));
        U64            sect_number = sect_idx+1;
        RD_MarkerArray markers     = section_markers[sect_number];
        
        rd_printf("# Disassembly [Section No. %#llx]", sect_number);
        rd_indent();
        rd_print_disasm(arena, out, indent, arch_from_coff_machine(machine), image_base, sect_off, markers.count, markers.v, raw_code);
        rd_unindent();
      }
    }
  }
}

internal void
coff_raw_data_sections(Arena              *arena,
                       String8List        *out,
                       String8             indent,
                       String8             raw_data,
                       B32                 is_obj,
                       RD_MarkerArray     *section_markers,
                       U64                 section_count,
                       COFF_SectionHeader *section_table)
{
  if (section_count) {
    for (U64 sect_idx = 0; sect_idx < section_count; ++sect_idx) {
      COFF_SectionHeader *sect = section_table+sect_idx;
      if (sect->fsize > 0) {
        U64         sect_size = is_obj ? sect->fsize : sect->vsize;
        String8     raw_sect  = str8_substr(raw_data, rng_1u64(sect->foff, sect->foff+sect_size));
        RD_MarkerArray markers   = section_markers[sect_idx];
        
        rd_printf("# Raw Data [Section No. %#llx]", (sect_idx+1));
        rd_indent();
        rd_print_raw_data(arena, out, indent, 32, markers.count, markers.v, raw_sect);
        rd_unindent();
        rd_newline();
      }
    }
  }
}

internal void
coff_print_relocs(Arena              *arena,
                  String8List        *out,
                  String8             indent,
                  String8             raw_data,
                  String8             string_table,
                  COFF_MachineType    machine,
                  U64                 sect_count,
                  COFF_SectionHeader *sect_headers,
                  COFF_Symbol32Array  symbols)
{
  Temp scratch = scratch_begin(&arena, 1);
  
  B32 print_header = 1;
  
  for (U64 sect_idx = 0; sect_idx < sect_count; ++sect_idx) {
    COFF_SectionHeader *sect_header = sect_headers+sect_idx;
    COFF_RelocInfo      reloc_info  = coff_reloc_info_from_section_header(raw_data, sect_header);
    
    if (reloc_info.count) {
      if (print_header) {
        print_header = 0;
        rd_printf("# Relocations");
        rd_indent();
      }
      
      rd_printf("## Section %llx", sect_idx);
      rd_indent();
      
      rd_printf("%-4s %-8s %-16s %-16s %-8s %-7s", "No.", "Offset", "Type", "ApplyTo", "SymIdx", "SymName");
      
      for (U64 reloc_idx = 0; reloc_idx < reloc_info.count; ++reloc_idx) {
        COFF_Reloc *reloc      = (COFF_Reloc*)(raw_data.str + reloc_info.array_off) + reloc_idx;
        String8     type       = coff_string_from_reloc(machine, reloc->type);
        U64         apply_size = coff_apply_size_from_reloc(machine, reloc->type);
        
        U64 apply_foff = sect_header->foff + reloc->apply_off;
        if (apply_foff + apply_size > raw_data.size) {
          rd_errorf("out of bounds apply file offset %#llx in relocation %#llx", apply_foff, reloc_idx);
          break;
        }
        
        U64 raw_apply;
        AssertAlways(apply_size <= sizeof(raw_apply));
        MemoryCopy(&raw_apply, raw_data.str + apply_foff, apply_size);
        S64 apply = extend_sign64(raw_apply, apply_size);
        
        if (reloc->isymbol > symbols.count) {
          rd_errorf("out of bounds symbol index %u in relocation %#llx", reloc->isymbol, reloc_idx);
          break;
        }
        
        COFF_Symbol32 *symbol      = symbols.v+reloc->isymbol;
        String8        symbol_name = coff_read_symbol_name(string_table, &symbol->name);
        
        String8List line = {0};
        str8_list_pushf(scratch.arena, &line, "%-4x",  reloc_idx       );
        str8_list_pushf(scratch.arena, &line, "%08x",  reloc->apply_off);
        str8_list_pushf(scratch.arena, &line, "%-16S", type            );
        str8_list_pushf(scratch.arena, &line, "%016x", apply           );
        str8_list_pushf(scratch.arena, &line, "%S",    symbol_name     );
        
        String8 l = str8_list_join(scratch.arena, &line, &(StringJoin){.sep=str8_lit(" ")});
        rd_printf("%S", l);
      }
      
      rd_unindent();
    }
  }
  
  if (!print_header) {
    rd_unindent();
  }
  rd_newline();
  
  scratch_end(scratch);
}

internal void
coff_print_symbol_table(Arena              *arena,
                        String8List        *out,
                        String8             indent,
                        String8             raw_data,
                        B32                 is_big_obj,
                        String8             string_table,
                        COFF_Symbol32Array  symbols)
{
  Temp scratch = scratch_begin(&arena, 1);
  
  if (symbols.count) {
    rd_printf("# Symbol Table");
    rd_indent();
    
    rd_printf("%-4s %-8s %-10s %-4s %-4s %-4s %-16s %-20s", 
              "No.", "Value", "SectNum", "Aux", "Msb", "Lsb", "Storage", "Name");
    
    for (U64 i = 0; i < symbols.count; ++i) {
      COFF_Symbol32 *symbol        = &symbols.v[i];
      String8        name          = coff_read_symbol_name(string_table, &symbol->name);
      String8        msb           = coff_string_from_sym_dtype(symbol->type.u.msb);
      String8        lsb           = coff_string_from_sym_type(symbol->type.u.lsb);
      String8        storage_class = coff_string_from_sym_storage_class(symbol->storage_class);
      String8        section_number;
      switch (symbol->section_number) {
        case COFF_Symbol_UndefinedSection: section_number = str8_lit("Undef"); break;
        case COFF_Symbol_AbsSection32:     section_number = str8_lit("Abs");   break;
        case COFF_Symbol_DebugSection32:   section_number = str8_lit("Debug"); break;
        default:                           section_number = push_str8f(scratch.arena, "%010x", symbol->section_number); break;
      }
      
      String8List line = {0};
      str8_list_pushf(scratch.arena, &line, "%-4x",  i                       );
      str8_list_pushf(scratch.arena, &line, "%08x",  symbol->value           );
      str8_list_pushf(scratch.arena, &line, "%-10S", section_number          );
      str8_list_pushf(scratch.arena, &line, "%-4u",  symbol->aux_symbol_count);
      str8_list_pushf(scratch.arena, &line, "%-4S",  msb                     );
      str8_list_pushf(scratch.arena, &line, "%-4S",  lsb                     );
      str8_list_pushf(scratch.arena, &line, "%-16S", storage_class           );
      str8_list_pushf(scratch.arena, &line, "%S",    name                    );
      
      String8 l = str8_list_join(scratch.arena, &line, &(StringJoin){.sep = str8_lit(" ")});
      rd_printf("%S", l);
      
      rd_indent();
      for (U64 k=i+1, c = i+symbol->aux_symbol_count; k <= c; ++k) {
        void *raw_aux = &symbols.v[k];
        switch (symbol->storage_class) {
          case COFF_SymStorageClass_External: {
            COFF_SymbolFuncDef *func_def = (COFF_SymbolFuncDef*)&symbols.v[k];
            rd_printf("Tag Index %#x, Total Size %#x, Line Numbers %#x, Next Function %#x", 
                      func_def->tag_index, func_def->total_size, func_def->ptr_to_ln, func_def->ptr_to_next_func);
          } break;
          case COFF_SymStorageClass_Function: {
            COFF_SymbolFunc *func = raw_aux;
            rd_printf("Ordinal Line Number %#x, Next Function %#x", func->ln, func->ptr_to_next_func);
          } break;
          case COFF_SymStorageClass_WeakExternal: {
            COFF_SymbolWeakExt *weak = raw_aux;
            String8             type = coff_string_from_weak_ext_type(weak->characteristics);
            rd_printf("Tag Index %#x, Characteristics %S", weak->tag_index, type);
          } break;
          case COFF_SymStorageClass_File: {
            COFF_SymbolFile *file = raw_aux;
            String8          name = str8_cstring_capped(file->name, file->name+sizeof(file->name));
            rd_printf("Name %S", name);
          } break;
          case COFF_SymStorageClass_Static: {
            COFF_SymbolSecDef *sd        = raw_aux;
            String8            selection = coff_string_from_comdat_select_type(sd->selection);
            U32 number = sd->number_lo;
            if (is_big_obj) {
              number |= (U32)sd->number_hi << 16;
            }
            if (number) {
              rd_printf("Length %x, Reloc Count %u, Line Count %u, Checksum %x, Section %x, Selection %S",
                        sd->length, sd->number_of_relocations, sd->number_of_ln, sd->check_sum, number, selection);
            } else {
              rd_printf("Length %x, Reloc Count %u, Line Count %u, Checksum %x",
                        sd->length, sd->number_of_relocations, sd->number_of_ln, sd->check_sum);
            }
          } break;
          default: {
            rd_printf("???");
          } break;
        }
      }
      
      i += symbol->aux_symbol_count;
      rd_unindent();
    }
    
    rd_unindent();
    rd_newline();
  }
  
  scratch_end(scratch);
}

internal void
coff_print_big_obj_header(Arena *arena, String8List *out, String8 indent, COFF_BigObjHeader *header)
{
  Temp scratch = scratch_begin(&arena, 1);
  
  String8 time_stamp = coff_string_from_time_stamp(scratch.arena, header->time_stamp);
  String8 machine    = coff_string_from_machine_type(header->machine);
  
  rd_printf("# Big Obj");
  rd_indent();
  rd_printf("Time Stamp   : %#x (%S)",  header->time_stamp, time_stamp);
  rd_printf("Machine      : %#x (%S)",  header->machine, machine      );
  rd_printf("Section Count: %u",  header->section_count    );
  rd_printf("Symbol Table : %#x", header->symbol_table_foff);
  rd_printf("Symbol Count : %u",  header->symbol_count     );
  rd_unindent();
  
  scratch_end(scratch);
}

internal void
coff_print_file_header(Arena *arena, String8List *out, String8 indent, COFF_FileHeader *header)
{
  Temp scratch = scratch_begin(&arena, 1);
  
  String8 time_stamp = coff_string_from_time_stamp(scratch.arena, header->time_stamp);
  String8 machine    = coff_string_from_machine_type(header->machine);
  String8 flags      = coff_string_from_flags(scratch.arena, header->flags);
  
  rd_printf("# COFF File Header");
  rd_indent();
  rd_printf("Time Stamp          : %#x (%S)", header->time_stamp, time_stamp                            );
  rd_printf("Machine             : %#x %S",   header->machine, machine                                  );
  rd_printf("Section Count       : %u",       header->section_count                                     );
  rd_printf("Symbol Table        : %#x",      header->symbol_table_foff                                 );
  rd_printf("Symbol Count        : %u",       header->symbol_count                                      );
  rd_printf("Optional Header Size: %#x (%m)", header->optional_header_size, header->optional_header_size);
  rd_printf("Flags               : %#x (%S)", header->flags, flags                                      );
  rd_unindent();
  
  scratch_end(scratch);
}

internal void
coff_print_import(Arena *arena, String8List *out, String8 indent, COFF_ParsedArchiveImportHeader *header)
{
  Temp scratch = scratch_begin(&arena, 1);
  
  String8 machine    = coff_string_from_machine_type(header->machine);
  String8 time_stamp = coff_string_from_time_stamp(scratch.arena, header->time_stamp);
  
  rd_printf("# Import");
  rd_indent();
  rd_printf("Version   : %u", header->version        );
  rd_printf("Machine   : %S", machine                );
  rd_printf("Time Stamp: %#x (%S)", header->time_stamp, time_stamp      );
  rd_printf("Data Size : %#x (%m)", header->data_size, header->data_size);
  rd_printf("Hint      : %u", header->hint_or_ordinal);
  rd_printf("Type      : %u", header->type           );
  rd_printf("Import By : %u", header->import_by      );
  rd_printf("Function  : %S", header->func_name      );
  rd_printf("DLL       : %S", header->dll_name       );
  rd_unindent();
  
  scratch_end(scratch);
}

internal void
coff_print_big_obj(Arena *arena, String8List *out, String8 indent, String8 raw_data, RD_Option opts)
{
  Temp scratch = scratch_begin(&arena, 1);
  
  COFF_FileHeaderInfo header_info = coff_file_header_info_from_data(raw_data);
  
  String8 raw_header        = str8_substr(raw_data, header_info.header_range);
  String8 raw_section_table = str8_substr(raw_data, header_info.section_table_range);
  String8 raw_string_table  = str8_substr(raw_data, header_info.string_table_range);
  
  COFF_BigObjHeader  *big_obj       = (COFF_BigObjHeader *)raw_header.str;
  COFF_SectionHeader *section_table = (COFF_SectionHeader *)raw_section_table.str;
  COFF_Symbol32Array  symbol_table  = coff_symbol_array_from_data_32(scratch.arena, raw_data, header_info.symbol_table_range.min, big_obj->symbol_count);
  
  if (opts & RD_Option_Headers) {
    coff_print_big_obj_header(arena, out, indent, big_obj);
    rd_newline();
  }
  
  if (opts & RD_Option_Sections) {
    Rng1U64 sect_headers_range = rng_1u64(sizeof(*big_obj), sizeof(*big_obj) + sizeof(COFF_SectionHeader)*big_obj->section_count);
    Rng1U64 symbols_range      = rng_1u64(big_obj->symbol_table_foff, big_obj->symbol_table_foff + sizeof(COFF_Symbol32)*big_obj->symbol_count);
    
    if (sect_headers_range.max > raw_data.size) {
      rd_errorf("not enough bytes to read big obj section headers");
      goto exit;
    }
    if (big_obj->symbol_count) {
      if (symbols_range.max > raw_data.size) {
        rd_errorf("not enough bytes to read big obj symbol table");
        goto exit;
      }
      if (contains_1u64(symbols_range, sect_headers_range.min) ||
          contains_1u64(symbols_range, sect_headers_range.max)) {
        rd_errorf("section headers and symbol table ranges overlap");
        goto exit;
      }
    }
    
    coff_print_section_table(arena, out, indent, raw_string_table, symbol_table, big_obj->section_count, section_table);
    rd_newline();
  }
  
  if (opts & RD_Option_Relocs) {
    coff_print_relocs(arena, out, indent, raw_data, raw_string_table, big_obj->machine, big_obj->section_count, section_table, symbol_table);
    rd_newline();
  }
  
  if (opts & RD_Option_Symbols) {
    coff_print_symbol_table(arena, out, indent, raw_data, 1, raw_string_table, symbol_table);
    rd_newline();
  }
  
  exit:;
  scratch_end(scratch);
}

internal void
coff_print_obj(Arena *arena, String8List *out, String8 indent, String8 raw_data, RD_Option opts)
{
  Temp scratch = scratch_begin(&arena, 1);
  
  COFF_FileHeaderInfo header_info = coff_file_header_info_from_data(raw_data);
  
  String8 raw_header        = str8_substr(raw_data, header_info.header_range);
  String8 raw_section_table = str8_substr(raw_data, header_info.section_table_range);
  String8 raw_string_table  = str8_substr(raw_data, header_info.string_table_range);
  
  COFF_FileHeader    *header        = (COFF_FileHeader *)raw_header.str;
  COFF_SectionHeader *section_table = (COFF_SectionHeader *)raw_section_table.str;
  COFF_Symbol32Array  symbol_table  = coff_symbol_array_from_data_16(scratch.arena, raw_data, header_info.symbol_table_range.min, header->symbol_count);
  Arch                arch          = arch_from_coff_machine(header->machine);
  
  if (opts & RD_Option_Headers) {
    coff_print_file_header(arena, out, indent, header);
    rd_newline();
  }
  
  if (opts & RD_Option_Sections) {
    Rng1U64 sect_headers_range = rng_1u64(sizeof(*header), sizeof(*header) + sizeof(COFF_SectionHeader)*header->section_count);
    Rng1U64 symbols_range      = rng_1u64(header->symbol_table_foff, header->symbol_table_foff + sizeof(COFF_Symbol16)*header->symbol_count);
    
    if (sect_headers_range.max > raw_data.size) {
      rd_errorf("not enough bytes to read obj section headers");
      goto exit;
    }
    if (header->symbol_count) {
      if (symbols_range.max > raw_data.size) {
        rd_errorf("not enough bytes to read obj symbol table");
        goto exit;
      }
      if (contains_1u64(symbols_range, sect_headers_range.min) ||
          contains_1u64(symbols_range, sect_headers_range.max)) {
        rd_errorf("section headers and symbol table ranges overlap");
        goto exit;
      }
    }
    
    coff_print_section_table(arena, out, indent, raw_string_table, symbol_table, header->section_count, section_table);
    rd_newline();
  }
  
  if (opts & RD_Option_Relocs) {
    coff_print_relocs(arena, out, indent, raw_data, raw_string_table, header->machine, header->section_count, section_table, symbol_table);
    rd_newline();
  }
  
  if (opts & RD_Option_Symbols) {
    coff_print_symbol_table(arena, out, indent, raw_data, 0, raw_string_table, symbol_table);
    rd_newline();
  }
  
  RD_MarkerArray *section_markers = 0;
  if (opts & (RD_Option_Disasm|RD_Option_Rawdata)) {
    section_markers = rd_section_markers_from_coff_symbol_table(scratch.arena, raw_string_table, header->section_count, symbol_table);
  }
  
  if (opts & RD_Option_Rawdata) {
    coff_raw_data_sections(arena, out, indent, raw_data, 1, section_markers, header->section_count, section_table);
  }
  
  if (opts & RD_Option_Disasm) {
    coff_disasm_sections(arena, out, indent, raw_data, header->machine, 0, 1, section_markers, header->section_count, section_table);
    rd_newline();
  }
  
  if (opts & RD_Option_Codeview) {
    cv_format_debug_sections(arena, out, indent, raw_data, raw_string_table, header->section_count, section_table);
  }
  
  if (opts & RD_Option_Dwarf) {
    DW_Input dwarf_input = dw_input_from_coff_section_table(scratch.arena, raw_data, raw_string_table, header->section_count, section_table);
    dw_format(arena, out, indent, opts, &dwarf_input, arch, ExecutableImageKind_CoffPe);
  }
  
  exit:;
  scratch_end(scratch);
}

internal void
coff_print_archive(Arena *arena, String8List *out, String8 indent, String8 raw_archive, RD_Option opts)
{
  Temp scratch = scratch_begin(&arena, 1);
  
  COFF_ArchiveParse archive_parse = coff_archive_parse_from_data(raw_archive);
  
  if (archive_parse.error.size) {
    rd_errorf("%S", archive_parse.error);
    return;
  }
  
  COFF_ArchiveFirstMember first_member = archive_parse.first_member;
  {
    rd_printf("# First Header");
    rd_indent();
    
    rd_printf("Symbol Count     : %u",         first_member.symbol_count);
    rd_printf("String Table Size: %#llx (%M)", first_member.string_table.size, first_member.string_table.size);
    
    rd_printf("Members:");
    rd_indent();
    
    String8List string_table = str8_split_by_string_chars(scratch.arena, first_member.string_table, str8_lit("\0"), 0);
    
    if (string_table.node_count == first_member.member_offset_count) {
      String8Node *string_n = string_table.first;
      
      for (U64 i = 0; i < string_table.node_count; ++i, string_n = string_n->next) {
        U32 offset = from_be_u32(first_member.member_offsets[i]);
        rd_printf("[%4u] %#08x %S", i, offset, string_n->string);
      }
    } else {
      rd_errorf("Member offset count (%llu) doesn't match string table count (%llu)", first_member.member_offset_count);
    }
    
    rd_unindent();
    rd_unindent();
    rd_newline();
  }
  
  if (archive_parse.has_second_header) {
    COFF_ArchiveSecondMember second_member = archive_parse.second_member;
    
    rd_printf("# Second Header");
    rd_indent();
    
    rd_printf("Member Count     : %u",         second_member.member_count);
    rd_printf("Symbol Count     : %u",         second_member.symbol_count);
    rd_printf("String Table Size: %#llx (%M)", second_member.string_table.size, second_member.string_table.size);
    
    String8List string_table = str8_split_by_string_chars(scratch.arena, second_member.string_table, str8_lit("\0"), 0);
    
    rd_printf("Members:");
    rd_indent();
    if (second_member.symbol_index_count == second_member.symbol_count) {
      String8Node *string_n = string_table.first;
      for (U64 i = 0; i < second_member.symbol_count; ++i, string_n = string_n->next) {
        U16 symbol_number = second_member.symbol_indices[i];
        if (symbol_number > 0 && symbol_number <= second_member.member_offset_count) {
          U16 symbol_idx    = symbol_number - 1;
          U32 member_offset = second_member.member_offsets[i];
          rd_printf("[%4u] %#08x %S", i, member_offset, string_n->string);
        } else {
          rd_errorf("[%4u] Out of bounds symbol number %u", i, symbol_number);
          break;
        }
      }
    } else {
      rd_errorf("Symbol index count %u doesn't match symbol count %u",
                second_member.symbol_index_count, second_member.symbol_count);
    }
    rd_unindent();
    
    rd_unindent();
    rd_newline();
  }
  
  if (archive_parse.has_long_names && opts & RD_Option_LongNames) {
    rd_printf("# Long Names");
    rd_indent();
    
    String8List long_names = str8_split_by_string_chars(scratch.arena, archive_parse.long_names, str8_lit("\0"), 0);
    U64 name_idx = 0;
    for (String8Node *name_n = long_names.first; name_n != 0; name_n = name_n->next, ++name_idx) {
      U64 offset = (U64)(name_n->string.str - archive_parse.long_names.str);
      rd_printf("[%-4u] %#08x %S", name_idx, offset, name_n->string);
    }
    
    rd_unindent();
    rd_newline();
  }
  
  U64  member_offset_count = 0;
  U32 *member_offsets      = 0;
  if (archive_parse.has_second_header) {
    member_offset_count = archive_parse.second_member.member_offset_count;
    member_offsets      = archive_parse.second_member.member_offsets;
  } else {
    HashTable *ht = hash_table_init(scratch.arena, 0x1000);
    for (U64 i = 0; i < archive_parse.first_member.member_offset_count; ++i) {
      U32 member_offset = from_be_u32(archive_parse.first_member.member_offsets[i]);
      if (!hash_table_search_u32(ht, member_offset)) {
        hash_table_push_u32_raw(scratch.arena, ht, member_offset, 0);
      }
    }
    member_offset_count = ht->count;
    member_offsets      = keys_from_hash_table_u32(scratch.arena, ht);
    radsort(member_offsets, member_offset_count, u32_is_before);
  }
  
  rd_printf("# Members");
  rd_indent();
  
  for (U64 i = 0; i < member_offset_count; ++i) {
    U64                next_member_offset = i+1 < member_offset_count ? member_offsets[i+1] : raw_archive.size;
    U64                member_offset      = member_offsets[i];
    String8            raw_member         = str8_substr(raw_archive, rng_1u64(member_offset, next_member_offset));
    COFF_ArchiveMember member             = coff_archive_member_from_data(raw_member);
    COFF_DataType      member_type        = coff_data_type_from_data(member.data);
    
    rd_printf("Member @ %#llx", member_offset);
    rd_indent();
    
    if (opts & RD_Option_Headers) {
      coff_print_archive_member_header(arena, out, indent, member.header, archive_parse.long_names);
      rd_newline();
    }
    
    switch (member_type) {
      case COFF_DataType_Obj: {
        coff_print_obj(arena, out, indent, member.data, opts);
      } break;
      case COFF_DataType_BigObj: {
        coff_print_big_obj(arena, out, indent, member.data, opts);
      } break;
      case COFF_DataType_Import: {
        if (opts & RD_Option_Headers) {
          COFF_ParsedArchiveImportHeader header = {0};
          U64 parse_size = coff_parse_import(member.data, 0, &header);
          if (parse_size) {
            coff_print_import(arena, out, indent, &header);
          } else {
            rd_errorf("not enough bytes to parse import header");
          }
        }
      } break;
      case COFF_DataType_Null: {
        rd_errorf("unknown member format", member_offset);
      } break;
    }
    
    rd_unindent();
    rd_newline();
  }
  
  rd_unindent();
  
  scratch_end(scratch);
}
#endif
