package dyld

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"sort"
	"strings"

	"github.com/blacktop/go-macho/types"
	"github.com/blacktop/ipsw/pkg/table"
	"github.com/dustin/go-humanize"
)

func (dch CacheHeader) String() string {
	var magicBytes []byte = dch.Magic[:]

	return fmt.Sprintf(
		"Magic               = %s\n"+
			"MappingOffset       = %08X\n"+
			"MappingCount        = %d\n"+
			"ImagesOffset        = %08X\n"+
			"ImagesCount         = %d\n"+
			"DyldBaseAddress     = %08X\n"+
			"CodeSignatureOffset = %08X\n"+
			"CodeSignatureSize   = %08X\n"+
			"SlideInfoOffset     = %08X\n"+
			"SlideInfoSize       = %08X\n"+
			"LocalSymbolsOffset  = %08X\n"+
			"LocalSymbolsSize    = %08X\n"+
			"UUID                = %s\n"+
			"Platform            = %s\n"+
			"Format              = %d\n",
		bytes.Trim(magicBytes, "\x00"),
		dch.MappingOffset,
		dch.MappingCount,
		dch.ImagesOffsetOld,
		dch.ImagesCountOld,
		dch.DyldBaseAddress,
		dch.CodeSignatureOffset,
		dch.CodeSignatureSize,
		dch.SlideInfoOffsetUnused,
		dch.SlideInfoSizeUnused,
		dch.LocalSymbolsOffset,
		dch.LocalSymbolsSize,
		dch.UUID.String(),
		dch.Platform.String(),
		dch.FormatVersion.Version(),
	)
}

func (m *CacheMapping) String() string {
	return fmt.Sprintf(
		"Name       = %s\n"+
			"Address    = %#x\n"+
			"Size       = %s\n"+
			"FileOffset = %#x\n"+
			"MaxProt    = %s\n"+
			"InitProt   = %s\n",
		m.Name,
		m.Address,
		humanize.Bytes(m.Size),
		m.FileOffset,
		m.MaxProt.String(),
		m.InitProt.String(),
	)
}

func (c CacheImageInfo) String() string {
	return fmt.Sprintf(
		"Address        = %016X\n"+
			"ModTime        = %016X\n"+
			"Inode          = %d\n"+
			"PathFileOffset = %08X\n",
		c.Address,
		c.ModTime,
		c.Inode,
		c.PathFileOffset,
	)
}

func (dch CacheHeader) Print() {
	fmt.Println("Header")
	fmt.Println("======")
	fmt.Println(dch.String())
	fmt.Printf("Slide Info:     %4dKB,  file offset: 0x%09X -> 0x%09X\n", dch.SlideInfoSizeUnused/1024, dch.SlideInfoOffsetUnused, dch.SlideInfoOffsetUnused+dch.SlideInfoSizeUnused)
	fmt.Printf("Local Symbols:  %3dMB,  file offset: 0x%09X -> 0x%09X\n", dch.LocalSymbolsSize/(1024*1024), dch.LocalSymbolsOffset, dch.LocalSymbolsOffset+dch.LocalSymbolsSize)
	fmt.Printf("Accelerate Tab: %3dKB,  address: 0x%09X -> 0x%09X\n", dch.AccelerateInfoSizeUnusedOrDyldStartFuncAddr/1024, dch.AccelerateInfoAddrUnusedOrDyldAddr, dch.AccelerateInfoAddrUnusedOrDyldAddr+dch.AccelerateInfoSizeUnusedOrDyldStartFuncAddr)
	fmt.Println()
}

func (l *localSymbolInfo) Print() {
	fmt.Printf("Local symbols nlist array:  %3dMB,  file offset: 0x%09X -> 0x%09X\n", l.NListByteSize/(1024*1024), l.NListFileOffset, l.NListFileOffset+l.NListByteSize)
	fmt.Printf("Local symbols string pool:  %3dMB,  file offset: 0x%09X -> 0x%09X\n", l.StringsSize/(1024*1024), l.StringsFileOffset, l.StringsFileOffset+l.StringsSize)
}

func (mappings cacheMappings) String() string {
	tableString := &strings.Builder{}

	mdata := [][]string{}
	for _, mapping := range mappings {
		mdata = append(mdata, []string{
			mapping.Name,
			mapping.InitProt.String(),
			mapping.MaxProt.String(),
			fmt.Sprintf("%d MB", mapping.Size/(1024*1024)),
			// humanize.Bytes(mapping.Size),
			fmt.Sprintf("%08X -> %08X", mapping.Address, mapping.Address+mapping.Size),
			fmt.Sprintf("%08X -> %08X", mapping.FileOffset, mapping.FileOffset+mapping.Size),
		})
	}
	tbl := table.NewStringBuilderTableWriter(tableString)
	tbl.SetHeader([]string{"Seg", "InitProt", "MaxProt", "Size", "Address", "File Offset"})
	tbl.SetBorders(nil)
	tbl.SetCenterSeparator("|")
	tbl.AppendBulk(mdata)
	tbl.SetAlignment(1) // ALIGN_LEFT
	tbl.Render()

	return tableString.String()
}

func (mapping CacheMappingWithSlideInfo) String() string {
	tableString := &strings.Builder{}

	mdata := [][]string{}

	slideInfoSize := fmt.Sprintf("%#08x -> %#08x", mapping.SlideInfoOffset, mapping.SlideInfoOffset+mapping.SlideInfoSize)
	if mapping.SlideInfoSize == 0 {
		slideInfoSize = fmt.Sprintf("%#08x", mapping.SlideInfoOffset)
	}

	mdata = append(mdata, []string{
		mapping.Name,
		mapping.InitProt.String(),
		mapping.MaxProt.String(),
		fmt.Sprintf("%#08x (%s)", mapping.Size, humanize.Bytes(mapping.Size)),
		fmt.Sprintf("%#08x", mapping.Address),
		fmt.Sprintf("%#08x", mapping.FileOffset),
		slideInfoSize,
		fmt.Sprintf("%d", mapping.Flags),
	})

	tbl := table.NewStringBuilderTableWriter(tableString)
	tbl.SetHeader([]string{"Seg", "InitProt", "MaxProt", "Size", "Address", "File Offset", "Slide Info Offset", "Flags"})
	tbl.SetBorders(nil)
	tbl.SetCenterSeparator("|")
	tbl.AppendBulk(mdata)
	tbl.SetAlignment(1)
	tbl.Render()

	return tableString.String()
}

func (mappings cacheMappingsWithSlideInfo) String(slideVersion uint32, verbose bool) string {
	tableString := &strings.Builder{}

	mdata := [][]string{}
	for _, mapping := range mappings {
		mappingAddr := fmt.Sprintf("%#08x", mapping.Address)
		mappingOff := fmt.Sprintf("%#08x", mapping.FileOffset)
		mappingInitProt := mapping.InitProt.String()
		mappingMaxProt := mapping.MaxProt.String()
		mappingFlags := mapping.Flags.String()
		if verbose {
			mappingAddr = fmt.Sprintf("%#08x -> %#08x", mapping.Address, mapping.Address+mapping.Size)
			mappingOff = fmt.Sprintf("%#08x -> %#08x", mapping.FileOffset, mapping.FileOffset+mapping.Size)
			mappingInitProt += fmt.Sprintf(" (%d)", mapping.InitProt)
			mappingMaxProt += fmt.Sprintf(" (%d)", mapping.MaxProt)
			mappingFlags += fmt.Sprintf(" (%d)", mapping.Flags)
		}
		var slideInfoSize string
		if mapping.SlideInfoSize > 0 {
			slideInfoSize = fmt.Sprintf("%#08x -> %#08x", mapping.SlideInfoOffset, mapping.SlideInfoOffset+mapping.SlideInfoSize)
		}
		mdata = append(mdata, []string{
			mapping.Name,
			mappingInitProt,
			mappingMaxProt,
			fmt.Sprintf("%#08x (%s)", mapping.Size, humanize.Bytes(mapping.Size)),
			mappingAddr,
			mappingOff,
			slideInfoSize,
			mappingFlags,
		})
	}
	tbl := table.NewStringBuilderTableWriter(tableString)
	tbl.SetHeader([]string{"Seg", "InitProt", "MaxProt", "Size", "Address", "File Offset", fmt.Sprintf("Slide Info (v%d) Offset", slideVersion), "Flags"})
	tbl.SetBorders(nil)
	tbl.SetCenterSeparator("|")
	tbl.AppendBulk(mdata)
	tbl.SetAlignment(1)
	tbl.Render()

	return tableString.String()
}

func (images cacheImages) Print() {
	fmt.Println("Images")
	fmt.Println("======")
	for idx, image := range images {
		fmt.Printf("%d:\t%08x %s\n", idx+1, image.Info.Address, image.Name)
	}
}

func (f *File) String(verbose bool) string {
	var slideVersion uint32

	if f.SlideInfo != nil {
		slideVersion = f.SlideInfo.GetVersion()
	}

	return fmt.Sprintf(
		"Header\n"+
			"======\n"+
			"Magic          = \"%s\"\n"+
			"Platform       = %s\n"+
			"%s"+ // alt platform
			"%s"+ // os version
			"%s"+ // alt os version
			"%s"+ // cache cpu
			"%s"+ // format
			"%s"+ // max slide
			"%s"+ // image count
			"%s\n"+ // sub cache info
			"%s"+ // shared region
			"%s"+ // local symbols
			"%s"+ // image text info
			"%s"+ // slideInfo
			"%s"+ // branchPools
			"%s"+ // accelerateInfo
			"%s"+ // patch info
			"%s"+ // progClosures
			"%s"+ // progClosuresTrie
			"%s"+ // prebuiltLoaderSet
			"%s"+ // prebuiltLoaderSet pool
			"%s"+ // dynamic config
			"%s"+ // objcOpts
			"%s"+ // swiftOpts
			"%s"+ // dyld/_dyld_start addrs
			"%s", // mappings
		f.Headers[f.UUID].Magic.String(),
		f.Headers[f.UUID].Platform,
		f.getAltPlatform(f.UUID),
		f.getOSVersion(f.UUID),
		f.getAltOSVersion(f.UUID),
		f.getCacheCPU(f.UUID),
		f.getFormatVersion(f.UUID),
		f.getMaxSlide(f.UUID),
		f.getImageCount(f.UUID),
		f.getSubCacheCount(),
		f.getSharedRegion(f.UUID),
		f.getLocalSymbols(),
		f.getImagesTextInfo(f.UUID),
		f.getSlideInfo(f.UUID, slideVersion),
		f.getBranchPools(f.UUID),
		f.getAccelerateInfo(f.UUID),
		f.getPatchInfo(f.UUID),
		f.getProgClosures(f.UUID),
		f.getProgClosuresTrie(f.UUID),
		f.getPrebuiltLoaderSet(f.UUID),
		f.getPrebuiltLoaderSetPool(f.UUID),
		f.getDynamicConfig(f.UUID),
		f.getObjcOpts(f.UUID),
		f.getSwiftOpts(f.UUID),
		f.getDyldInfo(f.UUID),
		f.getMappings(slideVersion, verbose),
	)
}

func (f *File) getFormatVersion(uuid types.UUID) string {
	var output string
	if f.Headers[uuid].FormatVersion > 0 {
		output = fmt.Sprintf("Format         = %s\n", f.Headers[uuid].FormatVersion.String())
	}
	return output
}

func (f *File) getOSVersion(uuid types.UUID) string {
	var output string
	if f.IsDyld4 && f.Headers[uuid].OsVersion > 0 {
		output = fmt.Sprintf("OS Version     = %s\n", f.Headers[uuid].OsVersion.String())
	}
	return output
}
func (f *File) getAltOSVersion(uuid types.UUID) string {
	var output string
	if f.IsDyld4 && f.Headers[uuid].AltOsVersion > 0 {
		output = fmt.Sprintf("Alt OS Version = %s\n", f.Headers[uuid].AltOsVersion.String())
	}
	return output
}
func (f *File) getAltPlatform(uuid types.UUID) string {
	var output string
	if f.IsDyld4 && f.Headers[uuid].AltPlatform > 0 {
		output = fmt.Sprintf("Alt Platform   = %s\n", f.Headers[uuid].AltPlatform.String())
	}
	return output
}

func (f *File) getCacheCPU(uuid types.UUID) string {
	var output string
	if hdr := f.Headers[uuid]; hdr.HasCacheCPUFields() {
		output = fmt.Sprintf("Cache CPU      = %s (type: %#08x, subtype: %#08x, reserved: %#x)\n",
			hdr.CacheCPUString(),
			uint32(hdr.CacheCPUType),
			uint32(hdr.CacheCPUSubtype),
			hdr.CacheCPUReserved)
	}
	return output
}

func (f *File) getMaxSlide(uuid types.UUID) string {
	var output string
	if f.Headers[uuid].MaxSlide > 0 {
		output = fmt.Sprintf("Max Slide      = %s\n", f.Headers[uuid].MaxSlide)
	}
	return output
}

func (f *File) getImageCount(uuid types.UUID) string {
	var output string
	if f.Headers[uuid].ImagesCountOld > 0 {
		output = fmt.Sprintf("Num Images     = %d\n", f.Headers[uuid].ImagesCountOld)
	} else {
		output = fmt.Sprintf("Num Images     = %d\n", f.Headers[uuid].ImagesCount)
	}
	return output
}

func (f *File) getSubCacheCount() string {
	var output string
	if f.IsDyld4 {
		output = fmt.Sprintf("Num SubCaches  = %d\n", f.Headers[f.UUID].SubCacheArrayCount)
	}
	return output
}

func (f *File) getLocalSymbols() string {
	var output string
	if f.LocalSymInfo.NListFileOffset > 0 && f.LocalSymInfo.StringsFileOffset > 0 {
		output = fmt.Sprintf(
			"Local Symbols (nlist array): %3dMB, offset:  0x%09X -> 0x%09X\n"+
				"Local Symbols (string pool): %3dMB, offset:  0x%09X -> 0x%09X\n",
			f.LocalSymInfo.NListByteSize/(1024*1024),
			f.LocalSymInfo.NListFileOffset,
			f.LocalSymInfo.NListFileOffset+f.LocalSymInfo.NListByteSize,
			f.LocalSymInfo.StringsSize/(1024*1024),
			f.LocalSymInfo.StringsFileOffset,
			f.LocalSymInfo.StringsFileOffset+f.LocalSymInfo.StringsSize,
		)
	}
	return output
}

func (f *File) getImagesTextInfo(uuid types.UUID) string {
	var output string
	imagesCount := f.Headers[uuid].ImagesCountOld
	imageHumanSize := int(f.Headers[uuid].ImagesCountOld) * binary.Size(CacheImageInfo{}) / 1024
	imagesOffset := int(f.Headers[uuid].ImagesOffsetOld)
	imagesSize := int(imagesCount) * binary.Size(CacheImageInfo{})

	if f.Headers[uuid].ImagesOffsetOld == 0 && f.Headers[uuid].ImagesCountOld == 0 {
		imagesCount = f.Headers[uuid].ImagesCount
		imageHumanSize = int(f.Headers[uuid].ImagesCount) * binary.Size(CacheImageInfo{}) / 1024
		imagesOffset = int(f.Headers[uuid].ImagesOffset)
		imagesSize = int(f.Headers[uuid].ImagesCount) * binary.Size(CacheImageInfo{})
	}

	if imagesCount > 0 || imagesOffset > 0 || imagesSize > 0 {
		output = fmt.Sprintf("ImagesText Info:             %3dKB, offset:  0x%09X -> 0x%09X\n", imageHumanSize, imagesOffset, imagesOffset+imagesSize)
	}

	return output
}

func (f *File) getSlideInfo(uuid types.UUID, slideVersion uint32) string {
	var output string
	if f.Headers[uuid].SlideInfoOffsetUnused > 0 {
		output = fmt.Sprintf("Slide Info (v%d):            %4dKB, offset:  0x%09X -> 0x%09X\n",
			slideVersion,
			f.Headers[uuid].SlideInfoSizeUnused/1024,
			f.Headers[uuid].SlideInfoOffsetUnused,
			f.Headers[uuid].SlideInfoOffsetUnused+f.Headers[uuid].SlideInfoSizeUnused)
	}
	return output
}

func (f *File) getBranchPools(uuid types.UUID) string {
	var output string
	if f.Headers[uuid].BranchPoolsOffset > 0 {
		output = fmt.Sprintf("Branch Pool:                 %3dMB, offset:  0x%09X -> 0x%09X\n",
			binary.Size(f.BranchPools),
			f.Headers[uuid].BranchPoolsOffset,
			int(f.Headers[uuid].BranchPoolsOffset)+binary.Size(f.BranchPools))
	}
	return output
}

func (f *File) getAccelerateInfo(uuid types.UUID) string {
	var output string
	if !f.IsDyld4 {
		if f.Headers[uuid].AccelerateInfoAddrUnusedOrDyldAddr > 0 {
			output = fmt.Sprintf("Accelerate Tab:              %3dKB, address: 0x%09X -> 0x%09X\n",
				f.Headers[uuid].AccelerateInfoSizeUnusedOrDyldStartFuncAddr/1024,
				f.Headers[uuid].AccelerateInfoAddrUnusedOrDyldAddr,
				f.Headers[uuid].AccelerateInfoAddrUnusedOrDyldAddr+f.Headers[uuid].AccelerateInfoSizeUnusedOrDyldStartFuncAddr)
		}
	}
	return output
}

func (f *File) getDyldInfo(uuid types.UUID) string {
	var output string
	if f.IsDyld4 {
		output = fmt.Sprintf("\ndyld MachO:    address: 0x%09X\n", f.Headers[uuid].AccelerateInfoAddrUnusedOrDyldAddr)
		output += fmt.Sprintf("_dyld_start:   address: 0x%09X\n", f.Headers[uuid].AccelerateInfoSizeUnusedOrDyldStartFuncAddr)
	}
	return output
}

func (f *File) getPatchInfo(uuid types.UUID) string {
	var output string
	if f.Headers[uuid].PatchInfoAddr > 0 || f.Headers[uuid].PatchInfoSize > 0 {
		output = fmt.Sprintf("Patch Info:                  %3dMB, address: 0x%09X -> 0x%09X\n",
			f.Headers[uuid].PatchInfoSize/(1024*1024),
			f.Headers[uuid].PatchInfoAddr, f.Headers[uuid].PatchInfoAddr+f.Headers[uuid].PatchInfoSize)
	}
	return output
}

func (f *File) getProgClosures(uuid types.UUID) string {
	var output string
	if f.Headers[uuid].ProgClosuresAddr > 0 {
		output = fmt.Sprintf("Closures:                    %3dMB, address: 0x%09X -> 0x%09X\n",
			f.Headers[uuid].ProgClosuresSize/(1024*1024),
			f.Headers[uuid].ProgClosuresAddr,
			f.Headers[uuid].ProgClosuresAddr+f.Headers[uuid].ProgClosuresSize)
	}
	return output
}

func (f *File) getProgClosuresTrie(uuid types.UUID) string {
	var output string
	if f.Headers[uuid].ProgClosuresTrieAddr > 0 {
		output = fmt.Sprintf("Closures Trie:               %3dKB, address: 0x%09X -> 0x%09X\n",
			f.Headers[uuid].ProgClosuresTrieSize/1024,
			f.Headers[uuid].ProgClosuresTrieAddr,
			f.Headers[uuid].ProgClosuresTrieAddr+f.Headers[uuid].ProgClosuresTrieSize)
	}
	return output
}

func (f *File) getPrebuiltLoaderSet(uuid types.UUID) string {
	var output string
	if f.IsDyld4 && f.Headers[uuid].DylibsPblSetAddr > 0 {
		output = fmt.Sprintf("Prebuilt Loader Set:         %3dMB, address: 0x%09X -> 0x%09X\n",
			(f.Headers[uuid].ProgramsPblSetPoolAddr-f.Headers[uuid].DylibsPblSetAddr)/1024/1024,
			f.Headers[uuid].DylibsPblSetAddr,
			f.Headers[uuid].ProgramsPblSetPoolAddr-f.Headers[uuid].DylibsPblSetAddr)
	}
	return output
}

func (f *File) getPrebuiltLoaderSetPool(uuid types.UUID) string {
	var output string
	if f.IsDyld4 && f.Headers[uuid].ProgramsPblSetPoolAddr > 0 {
		output = fmt.Sprintf("Prebuilt Loader Pool:        %3dMB, address: 0x%09X -> 0x%09X\n",
			f.Headers[uuid].ProgramsPblSetPoolSize/(1024*1024),
			f.Headers[uuid].ProgramsPblSetPoolAddr,
			f.Headers[uuid].ProgramsPblSetPoolAddr+f.Headers[uuid].ProgramsPblSetPoolSize)
	}
	return output
}

func (f *File) getObjcOpts(uuid types.UUID) string {
	var output string
	if f.IsDyld4 && f.Headers[uuid].ObjcOptsOffset > 0 {
		output = fmt.Sprintf("ObjC Opts:                    %3dB, offset:  0x%09X -> 0x%09X\n",
			f.Headers[uuid].ObjcOptsSize,
			f.Headers[uuid].ObjcOptsOffset,
			f.Headers[uuid].ObjcOptsOffset+f.Headers[uuid].ObjcOptsSize)
	}
	return output
}

func (f *File) getSwiftOpts(uuid types.UUID) string {
	var output string
	if f.IsDyld4 && f.Headers[uuid].SwiftOptsOffset > 0 {
		output = fmt.Sprintf("Swift Opts:                  %3dkB, offset:  0x%09X -> 0x%09X\n",
			f.Headers[uuid].SwiftOptsSize/(1024),
			f.Headers[uuid].SwiftOptsOffset,
			f.Headers[uuid].SwiftOptsOffset+f.Headers[uuid].SwiftOptsSize)
	}
	return output
}

func (f *File) getDynamicConfig(uuid types.UUID) string {
	var output string
	if f.IsDyld4 && f.Headers[uuid].SwiftOptsOffset > 0 {
		output = fmt.Sprintf("Dynamic Config:              %3dkB, address: 0x%09X -> 0x%09X\n",
			f.Headers[uuid].DynamicDataMaxSize/(1024),
			f.Headers[uuid].SharedRegionStart+f.Headers[uuid].DynamicDataOffset,
			f.Headers[uuid].SharedRegionStart+f.Headers[uuid].DynamicDataOffset+f.Headers[uuid].DynamicDataMaxSize)
	}
	return output
}

func (f *File) getSharedRegion(uuid types.UUID) string {
	var output string
	if f.Headers[uuid].SharedRegionSize > 0 {
		output = fmt.Sprintf("Shared Region:               %3dGB, address: 0x%09X -> 0x%09X\n",
			f.Headers[uuid].SharedRegionSize/(1024*1024*1024),
			f.Headers[uuid].SharedRegionStart,
			f.Headers[uuid].SharedRegionStart+f.Headers[uuid].SharedRegionSize)
	}
	return output
}

func (f *File) getCodeSignature(uuid types.UUID) string {
	var output string
	if f.Headers[uuid].CodeSignatureSize > 0 {
		output = fmt.Sprintf("Code Signature: %3dMB, offset:  0x%09X -> 0x%09X\n",
			f.Headers[uuid].CodeSignatureSize/(1024*1024),
			f.Headers[uuid].CodeSignatureOffset,
			f.Headers[uuid].CodeSignatureOffset+f.Headers[uuid].CodeSignatureSize)
	}
	return output
}

func (f *File) getRosetta(uuid types.UUID) string {
	var output string
	if f.Headers[uuid].RosettaReadOnlySize > 0 {
		output += fmt.Sprintf("Rosetta RO:    %s, address: 0x%09X -> 0x%09X\n",
			humanize.Bytes(f.Headers[uuid].RosettaReadOnlySize),
			f.Headers[uuid].RosettaReadOnlyAddr,
			f.Headers[uuid].RosettaReadOnlyAddr+f.Headers[uuid].RosettaReadOnlySize)
	}
	if f.Headers[uuid].RosettaReadWriteSize > 0 {
		output += fmt.Sprintf("Rosetta RW:    %s, address: 0x%09X -> 0x%09X\n",
			humanize.Bytes(f.Headers[uuid].RosettaReadWriteSize),
			f.Headers[uuid].RosettaReadWriteAddr,
			f.Headers[uuid].RosettaReadWriteAddr+f.Headers[uuid].RosettaReadWriteSize)
	}
	return output
}

func (f *File) getMappings(slideVersion uint32, verbose bool) string {
	var output strings.Builder
	if f.Headers[f.UUID].SlideInfoOffsetUnused > 0 {
		for uuid, cacheMappings := range f.Mappings {
			output.WriteString(fmt.Sprintf("\n> SubCache %s", uuid))
			output.WriteString(cacheMappings.String())
		}
	} else {
		// sort mappings map by address
		uuids := make([]types.UUID, 0, len(f.MappingsWithSlideInfo))
		for u := range f.MappingsWithSlideInfo {
			uuids = append(uuids, u)
		}
		sort.SliceStable(uuids, func(i, j int) bool {
			return f.MappingsWithSlideInfo[uuids[i]][0].Address < f.MappingsWithSlideInfo[uuids[j]][0].Address
		})
		for _, uuid := range uuids {
			if uuid == f.symUUID {
				output.WriteString(fmt.Sprintf("\n> Cache (.symbols) UUID: %s\n\n", uuid))
			} else {
				ext, err := f.GetSubCacheExtensionFromUUID(uuid)
				if err != nil {
					output.WriteString(fmt.Sprintf("\n> Cache UUID: %s\n\n", uuid))
				} else {
					output.WriteString(fmt.Sprintf("\n> Cache (%s) UUID: %s\n\n", ext, uuid))
				}
			}
			output.WriteString("Mappings\n--------\n\n")
			output.WriteString(f.MappingsWithSlideInfo[uuid].String(slideVersion, verbose))
			output.WriteString(fmt.Sprintln())
			output.WriteString(f.getCodeSignature(uuid))
			output.WriteString(f.getRosetta(uuid))
		}
	}
	return output.String()
}
