package crashlog

import (
	"bufio"
	"fmt"
	"regexp"
	"strings"

	"github.com/apex/log"
	"github.com/spf13/cast"
)

type Field struct {
	Title string
	Value any
}

func pad(in string, ammount int) string {
	if ammount >= len(in) {
		return in + strings.Repeat(" ", ammount-len(in))
	}
	return in
}

func (f *Field) String() string {
	if f == nil {
		return ""
	}
	switch v := f.Value.(type) {
	case string:
		return fmt.Sprintf("%s %s\n", pad(colorField(f.Title)+":", 36), v)
	case uint64:
		return fmt.Sprintf("%s %#x\n", pad(colorField(f.Title)+":", 36), v)
	case bool:
		return fmt.Sprintf("%s %t\n", pad(colorField(f.Title)+":", 36), v)
	default:
		return fmt.Sprintf("%s %v\n", pad(colorField(f.Title)+":", 36), f.Value)
	}
}

type EpochTimeSec struct {
	Sec  uint64
	Usec uint64
}

func (e EpochTimeSec) String() string {
	return fmt.Sprintf("%#08x %#08x", e.Sec, e.Usec)
}

type EpochTime struct {
	Boot     EpochTimeSec
	Sleep    EpochTimeSec
	Wake     EpochTimeSec
	Calendar EpochTimeSec
}

func (e EpochTime) String() string {
	return fmt.Sprintf(
		colorField("Epoch Time         sec       usec\n")+
			colorImage("  Boot    ")+": %s\n"+
			colorImage("  Sleep   ")+": %s\n"+
			colorImage("  Wake    ")+": %s\n"+
			colorImage("  Calendar")+": %s\n",
		e.Boot,
		e.Sleep,
		e.Wake,
		e.Calendar)
}

type Range struct {
	Start uint64
	End   uint64
}

func (r Range) String() string {
	return fmt.Sprintf("%#016x - %#016x", r.Start, r.End)
}

type ZoneInfo struct {
	found    bool
	ZoneMap  Range
	VM       Range
	RO       Range
	Gen0     Range
	Gen1     Range
	Gen2     Range
	Gen3     Range
	Data     Range
	Metadata Range
	Bitmaps  Range
	Extra    Range
}

func (z ZoneInfo) String() string {
	if !z.found {
		return ""
	}
	return fmt.Sprintf(
		colorField("Zone Info\n")+
			colorImage("  Zone Map")+": %s\n"+
			colorImage("  . VM    ")+": %s\n"+
			colorImage("  . RO    ")+": %s\n"+
			colorImage("  . GEN0  ")+": %s\n"+
			colorImage("  . GEN1  ")+": %s\n"+
			colorImage("  . GEN2  ")+": %s\n"+
			colorImage("  . GEN3  ")+": %s\n"+
			colorImage("  . DATA  ")+": %s\n"+
			colorImage("  Metadata")+": %s\n"+
			colorImage("  Bitmaps ")+": %s\n"+
			colorImage("  Extra   ")+": %s\n\n",
		z.ZoneMap,
		z.VM,
		z.RO,
		z.Gen0,
		z.Gen1,
		z.Gen2,
		z.Gen3,
		z.Data,
		z.Metadata,
		z.Bitmaps,
		z.Extra)
}

type TPIDRx_ELy map[string]uint64

func (t TPIDRx_ELy) String() string {
	if len(t) == 0 {
		return ""
	}
	var tpidrx string
	for key, val := range t {
		tpidrx += fmt.Sprintf(colorImage("    %-3s", key)+": %#016x\n", val)
	}
	return fmt.Sprintf(colorField("TPIDRx_ELy")+"\n%s\n", tpidrx)
}

type Core struct {
	Num int
	Msg string
	PC  uint64
	LR  uint64
	FP  uint64
}

func (c Core) String() string {
	if len(c.Msg) > 0 {
		return fmt.Sprintf(colorField("CORE")+" %d  %s", c.Num, c.Msg)
	}
	return fmt.Sprintf(colorField("CORE")+" %d"+colorField(": PC")+"=%#016x, "+colorField("LR")+"=%#016x, "+colorField("FP")+"=%#016x", c.Num, c.PC, c.LR, c.FP)
}

type PanickedTask struct {
	PID     int
	Name    string
	Address uint64
	Pages   int
	Threads int
}

func (p PanickedTask) String() string {
	return fmt.Sprintf("%s:   %#016x, %d %s, %d %s, %s %d: %s", colorField("Panicked Task"), p.Address, p.Pages, colorField("pages"), p.Threads, colorField("threads"), colorField("pid"), p.PID, colorImage(p.Name))
}

type State struct {
	LR uint64
	FP uint64
}

func (s State) String() string {
	return fmt.Sprintf(colorImage("lr")+": %#016x - "+colorImage("fp")+": %#016x", s.LR, s.FP)
}

type PanickedThread struct {
	TID       int
	Backtrace uint64
	Address   uint64
	CallStack []State
}

func (p PanickedThread) String() string {
	callstack := "\n"
	for _, state := range p.CallStack {
		callstack += fmt.Sprintf("    %s\n", state)
	}
	return fmt.Sprintf(colorField("Panicked Thread")+": %#016x, "+colorField("backtrace")+": %#016x, "+colorField("tid")+": %d%s", p.Address, p.Backtrace, p.TID, callstack)
}

type LastStartedKext struct {
	StartedAt uint64
	Name      string
	Version   string
	Address   uint64
	Size      uint64
}

func (l *LastStartedKext) String() string {
	if l == nil {
		return ""
	}
	return fmt.Sprintf(
		colorField("Last Started Kext at")+": %#x: %s %s (%s %#x, %s %d)\n",
		l.StartedAt,
		colorImage(l.Name),
		l.Version,
		colorField("addr"),
		l.Address,
		colorField("size"),
		l.Size)
}

type LoadedKexts []LoadedKext

func (l LoadedKexts) String() string {
	if len(l) == 0 {
		return ""
	}
	loadedKexts := ""
	for _, kext := range l {
		loadedKexts += fmt.Sprintf("    %s\n", kext)
	}
	return fmt.Sprintf(colorField("Loaded Kexts:")+"\n%s\n", loadedKexts)
}

type LoadedKext struct {
	Name    string
	Version string
}

func (l LoadedKext) String() string {
	return fmt.Sprintf("%s %s", colorImage(l.Name), l.Version)
}

type Panic210 struct {
	Panic                  string
	DebuggerMessage        *Field // string
	MemoryID               *Field // uint64
	OsReleaseType          *Field // string
	OsVersion              *Field // string
	KernelVersion          *Field // string
	FilesetKernelCacheUUID *Field // string
	KernelCacheUUID        *Field // string
	KernelUUID             *Field // string
	BootSessionUUID        *Field // string
	IBootVersion           *Field // string
	SecureBoot             *Field // bool
	RootsInstalled         *Field // bool
	PaniclogVersion        *Field // string
	KernelCacheSlide       *Field // uint64
	KernelCacheBase        *Field // uint64
	KernelSlide            *Field // uint64
	KernelTextBase         *Field // uint64
	KernelTextExecSlide    *Field // uint64
	KernelTextExecBase     *Field // uint64
	MachAbsoluteTime       *Field // uint64

	EpochTime EpochTime

	ZoneInfo ZoneInfo

	TPIDRx_ELy TPIDRx_ELy

	Cores []Core

	CompressorInfo *Field // string
	PanickedTask   PanickedTask
	PanickedThread PanickedThread

	LastStartedKext *LastStartedKext
	LoadedKexts     LoadedKexts

	lines []string
}

func parsePanicString210(in string) (*Panic210, error) {
	var err error

	crash := &Panic210{}

	scanner := bufio.NewScanner(strings.NewReader(in))
	for scanner.Scan() {
		crash.lines = append(crash.lines, scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		return nil, fmt.Errorf("failed to scan panic string: %v", err)
	}

	crash.Panic = crash.getPanicString()

	crash.DebuggerMessage, err = crash.getStrField("Debugger Message", "Debugger message: ")
	if err != nil {
		log.WithError(err).Error("failed to get debugger message")
	}
	crash.MemoryID, err = crash.getIntField("Memory ID", "Memory ID: ")
	if err != nil {
		log.WithError(err).Error("failed to get memory ID")
	}
	crash.OsReleaseType, err = crash.getStrField("OS Release Type", "OS release type: ")
	if err != nil {
		log.WithError(err).Error("failed to get OS release type")
	}
	crash.OsVersion, err = crash.getStrField("OS Version", "OS version: ")
	if err != nil {
		log.WithError(err).Error("failed to get OS version")
	}
	crash.KernelVersion, err = crash.getStrField("Kernel Version", "Kernel version: ")
	if err != nil {
		log.WithError(err).Error("failed to get kernel version")
	}
	crash.KernelCacheUUID, err = crash.getStrField("KernelCache UUID", "KernelCache UUID: ")
	if err != nil {
		log.WithError(err).Debug("failed to get kernelcache UUID")
	}
	crash.FilesetKernelCacheUUID, err = crash.getStrField("Fileset KernelCache UUID", "Fileset KernelCache UUID: ")
	if err != nil {
		log.WithError(err).Debug("failed to get fileset kernelcache UUID")
	}
	crash.KernelUUID, err = crash.getStrField("Kernel UUID", "Kernel UUID: ")
	if err != nil {
		log.WithError(err).Debug("failed to get kernel UUID")
	}
	crash.BootSessionUUID, err = crash.getStrField("Boot Session UUID", "Boot session UUID: ")
	if err != nil {
		log.WithError(err).Debug("failed to get boot session UUID")
	}
	crash.IBootVersion, err = crash.getStrField("iBoot Version", "iBoot version: ")
	if err != nil {
		log.WithError(err).Error("failed to get iBoot version")
	}
	crash.SecureBoot, err = crash.getBoolField("Secure Boot", "secure boot?: ")
	if err != nil {
		log.WithError(err).Debug("failed to get secure boot")
	}
	crash.RootsInstalled, err = crash.getBoolField("Roots Installed", "roots installed: ")
	if err != nil {
		log.WithError(err).Debug("failed to get roots installed")
	}
	crash.PaniclogVersion, err = crash.getStrField("Paniclog Version", "Paniclog version: ")
	if err != nil {
		log.WithError(err).Error("failed to get paniclog version")
	}
	crash.KernelCacheSlide, err = crash.getIntField("KernelCache Slide", "KernelCache slide:      ")
	if err != nil {
		log.WithError(err).Debug("failed to get kernelcache slide")
	}
	crash.KernelCacheBase, err = crash.getIntField("KernelCache Base", "KernelCache base:  ")
	if err != nil {
		log.WithError(err).Debug("failed to get kernelcache base")
	}
	crash.KernelSlide, err = crash.getIntField("Kernel Slide", "Kernel slide:      ")
	if err != nil {
		log.WithError(err).Error("failed to get kernel slide")
	}
	crash.KernelTextBase, err = crash.getIntField("Kernel Text Base", "Kernel text base:  ")
	if err != nil {
		log.WithError(err).Error("failed to get kernel text base")
	}
	crash.KernelTextExecSlide, err = crash.getIntField("Kernel Text Exec Slide", "Kernel text exec slide:      ")
	if err != nil {
		log.WithError(err).Debug("failed to get kernel text exec slide")
	}
	crash.KernelTextExecBase, err = crash.getIntField("Kernel Text Exec Base", "Kernel text exec base:  ")
	if err != nil {
		log.WithError(err).Debug("failed to get kernel text exec base")
	}
	crash.MachAbsoluteTime, err = crash.getIntField("Mach Absolute Time", "mach_absolute_time: ")
	if err != nil {
		log.WithError(err).Error("failed to get mach absolute time")
	}
	if err := crash.getEpochTime(); err != nil {
		log.WithError(err).Error("failed to get epoch time")
	}
	if err := crash.getZoneInfo(); err != nil {
		log.WithError(err).Error("failed to get zone info")
	}
	if err := crash.getTPIDRx_ELy(); err != nil {
		log.WithError(err).Debug("failed to get TPIDRx_ELy")
	}
	if err := crash.getCores(); err != nil {
		log.WithError(err).Error("failed to get cores")
	}
	crash.CompressorInfo, err = crash.getStrField("Compressor Info", "Compressor Info: ")
	if err != nil {
		log.WithError(err).Debug("failed to get compressor info")
	}
	if err := crash.getPanickedTask(); err != nil {
		log.WithError(err).Error("failed to get panicked task")
	}
	if err := crash.getPanickedThread(); err != nil {
		log.WithError(err).Error("failed to get panicked thread")
	}
	if err := crash.getLastStartedKext(); err != nil {
		log.WithError(err).Debug(err.Error())
	}
	if err := crash.getLoadedKexts(); err != nil {
		log.WithError(err).Debug(err.Error())
	}

	return crash, nil
}

func (p *Panic210) getBoolField(title, key string) (*Field, error) {
	for _, line := range p.lines {
		if strings.HasPrefix(line, key) {
			switch strings.ToLower(strings.TrimPrefix(line, key)) {
			case "yes", "1", "true":
				return &Field{Title: title, Value: true}, nil
			case "no", "0", "false":
				return &Field{Title: title, Value: false}, nil
			default:
				return nil, fmt.Errorf("failed to parse bool: %s", line)
			}
		}
	}
	return nil, fmt.Errorf("failed to find '%s'", key)
}
func (p *Panic210) getStrField(title, key string) (*Field, error) {
	for _, line := range p.lines {
		if strings.HasPrefix(line, key) {
			return &Field{Title: title, Value: strings.TrimPrefix(line, key)}, nil
		}
	}
	return nil, fmt.Errorf("failed to find '%s'", key)
}
func (p *Panic210) getIntField(title, key string) (*Field, error) {
	for _, line := range p.lines {
		if strings.HasPrefix(line, key) {
			return &Field{Title: title, Value: cast.ToUint64(strings.TrimPrefix(line, key))}, nil
		}
	}
	return nil, fmt.Errorf("failed to find '%s'", key)
}
func (p *Panic210) getPanicString() string {
	var lines []string
	for _, line := range p.lines {
		if strings.HasPrefix(line, "Debugger message:") {
			break
		}
		lines = append(lines, line)
	}
	return strings.Join(lines, "\n")
}
func (p *Panic210) getEpochTime() (err error) {
	found := false

	parse := func(line string) (uint64, uint64, error) {
		timeRE := regexp.MustCompile(`: (?P<sec>0x\w+) (?P<usec>0x\w+)`)
		matches := timeRE.FindStringSubmatch(line)
		if len(matches) < 3 {
			return 0, 0, fmt.Errorf("failed to parse time: %s", line)
		}
		return cast.ToUint64(matches[1]), cast.ToUint64(matches[2]), nil
	}

	for _, line := range p.lines {
		if strings.HasPrefix(line, "Epoch Time:        sec       usec") {
			found = true
			continue
		}
		if found {
			if strings.HasPrefix(line, "  Boot    : ") {
				p.EpochTime.Boot.Sec, p.EpochTime.Boot.Usec, err = parse(line)
				if err != nil {
					return err
				}
				continue
			}
			if strings.HasPrefix(line, "  Sleep   : ") {
				p.EpochTime.Sleep.Sec, p.EpochTime.Sleep.Usec, err = parse(line)
				if err != nil {
					return err
				}
				continue
			}
			if strings.HasPrefix(line, "  Wake    :  ") {
				p.EpochTime.Wake.Sec, p.EpochTime.Wake.Usec, err = parse(line)
				if err != nil {
					return err
				}
				continue
			}
			if strings.HasPrefix(line, "  Calendar: ") {
				p.EpochTime.Calendar.Sec, p.EpochTime.Calendar.Usec, err = parse(line)
				if err != nil {
					return err
				}
				continue
			}
		}
	}

	if !found {
		return fmt.Errorf("failed to find Epoch Time")
	}

	return nil
}
func (p *Panic210) getZoneInfo() (err error) {
	parse := func(line string) (uint64, uint64, error) {
		timeRE := regexp.MustCompile(`: (?P<start>\w+) - (?P<end>\w+)`)
		matches := timeRE.FindStringSubmatch(line)
		if len(matches) < 3 {
			return 0, 0, fmt.Errorf("failed to parse time: %s", line)
		}
		return cast.ToUint64(matches[1]), cast.ToUint64(matches[2]), nil
	}

	for _, line := range p.lines {
		if strings.HasPrefix(line, "Zone info:") {
			p.ZoneInfo.found = true
			continue
		}
		if p.ZoneInfo.found {
			if strings.HasPrefix(line, "  Zone map: ") {
				p.ZoneInfo.ZoneMap.Start, p.ZoneInfo.ZoneMap.End, err = parse(line)
				if err != nil {
					return err
				}
				continue
			}
			if strings.HasPrefix(line, "  . VM    : ") {
				p.ZoneInfo.VM.Start, p.ZoneInfo.VM.End, err = parse(line)
				if err != nil {
					return err
				}
				continue
			}
			if strings.HasPrefix(line, "  . RO    : ") {
				p.ZoneInfo.RO.Start, p.ZoneInfo.RO.End, err = parse(line)
				if err != nil {
					return err
				}
				continue
			}
			if strings.HasPrefix(line, "  . GEN0  : ") {
				p.ZoneInfo.Gen0.Start, p.ZoneInfo.Gen0.End, err = parse(line)
				if err != nil {
					return err
				}
				continue
			}
			if strings.HasPrefix(line, "  . GEN1  : ") {
				p.ZoneInfo.Gen1.Start, p.ZoneInfo.Gen1.End, err = parse(line)
				if err != nil {
					return err
				}
				continue
			}
			if strings.HasPrefix(line, "  . GEN2  : ") {
				p.ZoneInfo.Gen2.Start, p.ZoneInfo.Gen2.End, err = parse(line)
				if err != nil {
					return err
				}
				continue
			}
			if strings.HasPrefix(line, "  . GEN3  : ") {
				p.ZoneInfo.Gen3.Start, p.ZoneInfo.Gen3.End, err = parse(line)
				if err != nil {
					return err
				}
				continue
			}
			if strings.HasPrefix(line, "  . DATA  : ") {
				p.ZoneInfo.Data.Start, p.ZoneInfo.Data.End, err = parse(line)
				if err != nil {
					return err
				}
				continue
			}
			if strings.HasPrefix(line, "  Metadata: ") {
				p.ZoneInfo.Metadata.Start, p.ZoneInfo.Metadata.End, err = parse(line)
				if err != nil {
					return err
				}
				continue
			}
			if strings.HasPrefix(line, "  Bitmaps : ") {
				p.ZoneInfo.Bitmaps.Start, p.ZoneInfo.Bitmaps.End, err = parse(line)
				if err != nil {
					return err
				}
				continue
			}
			if strings.HasPrefix(line, "  Extra   : ") {
				p.ZoneInfo.Extra.Start, p.ZoneInfo.Extra.End, err = parse(line)
				if err != nil {
					return err
				}
				continue
			}
		}
	}

	if !p.ZoneInfo.found {
		return fmt.Errorf("failed to find Zone info")
	}

	return nil
}
func (p *Panic210) getTPIDRx_ELy() (err error) {
	re := regexp.MustCompile(`^TPIDRx_ELy`)
	p.TPIDRx_ELy = make(map[string]uint64)
	for _, line := range p.lines {
		if re.MatchString(line) {
			tpidRE := regexp.MustCompile(`(?:(?P<key>\w+): (?P<val>\w+)\s)+`)
			matches := tpidRE.FindAllStringSubmatch(line, -1)
			if len(matches) < 3 {
				continue
			}
			for _, match := range matches {
				p.TPIDRx_ELy[match[1]] = cast.ToUint64(match[2])
			}
			return nil
		}
	}
	return fmt.Errorf("failed to find TPIDRx_ELy")
}
func (p *Panic210) getCores() (err error) {
	re := regexp.MustCompile(`^CORE \d+`)
	for _, line := range p.lines {
		if re.MatchString(line) {
			coreRE := regexp.MustCompile(`^CORE (?P<core>\d+): PC=(?P<pc>0x\w+), LR=(?P<lr>0x\w+), FP=(?P<fp>0x\w+)`)
			matches := coreRE.FindStringSubmatch(line)
			if len(matches) < 5 {
				coreRE = regexp.MustCompile(`^CORE (?P<core>\d+) (?P<msg>.*)`)
				if matches = coreRE.FindStringSubmatch(line); len(matches) == 3 {
					core := Core{
						Num: cast.ToInt(matches[1]),
						Msg: matches[2],
					}
					p.Cores = append(p.Cores, core)
				}
				continue
			}
			core := Core{
				Num: cast.ToInt(matches[1]),
				PC:  cast.ToUint64(matches[2]),
				LR:  cast.ToUint64(matches[3]),
				FP:  cast.ToUint64(matches[4]),
			}
			p.Cores = append(p.Cores, core)
		}
	}
	return nil
}
func (p *Panic210) getPanickedTask() (err error) {
	re := regexp.MustCompile(`^Panicked task `)
	for _, line := range p.lines {
		if re.MatchString(line) {
			ptRE := regexp.MustCompile(`^Panicked task (?P<task>\w+): (?P<pages>\d+) pages, (?P<threads>\d+) threads: pid (?P<pid>\d+): (?P<name>.*)`)
			matches := ptRE.FindStringSubmatch(line)
			if len(matches) < 6 {
				continue
			}
			p.PanickedTask = PanickedTask{
				PID:     cast.ToInt(matches[4]),
				Name:    matches[5],
				Address: cast.ToUint64(matches[1]),
				Pages:   cast.ToInt(matches[2]),
				Threads: cast.ToInt(matches[3]),
			}
			return nil
		}
	}
	return fmt.Errorf("failed to find panicked task")
}
func (p *Panic210) getPanickedThread() (err error) {
	re := regexp.MustCompile(`^Panicked thread:`)
	found := false
	for _, line := range p.lines {
		if re.MatchString(line) {
			ptRE := regexp.MustCompile(`^Panicked thread: (?P<thread>\w+), backtrace: (?P<backtrace>\w+), tid: (?P<tid>\d+)`)
			matches := ptRE.FindStringSubmatch(line)
			if len(matches) < 4 {
				continue
			}
			p.PanickedThread = PanickedThread{
				Address:   cast.ToUint64(matches[1]),
				Backtrace: cast.ToUint64(matches[2]),
				TID:       cast.ToInt(matches[3]),
			}
			found = true
			continue
		}
		if found {
			csRE := regexp.MustCompile(`^\s+lr: (?P<lr>\w+)  fp: (?P<fp>\w+)`)
			matches := csRE.FindStringSubmatch(line)
			if len(matches) < 3 {
				continue
			}
			p.PanickedThread.CallStack = append(p.PanickedThread.CallStack, State{
				LR: cast.ToUint64(matches[1]),
				FP: cast.ToUint64(matches[2]),
			})
		}
	}
	if found {
		return nil
	}
	return fmt.Errorf("failed to find panicked thread")
}
func (p *Panic210) getLastStartedKext() (err error) {
	re := regexp.MustCompile(`^last started kext at`)
	found := false
	for _, line := range p.lines {
		if re.MatchString(line) {
			ptRE := regexp.MustCompile(`^last started kext at (?P<start>.+): (?P<name>.*)\s+(?P<version>[0-9.]+) \(addr (?P<addr>\w+), size (?P<size>\w+)\)`)
			matches := ptRE.FindStringSubmatch(line)
			if len(matches) < 4 {
				continue
			}
			p.LastStartedKext = &LastStartedKext{
				StartedAt: cast.ToUint64(matches[1]),
				Name:      matches[2],
				Version:   matches[3],
				Address:   cast.ToUint64(matches[4]),
				Size:      cast.ToUint64(matches[5]),
			}
			found = true
			continue
		}
	}
	if found {
		return nil
	}
	return fmt.Errorf("failed to find 'last started kext at'")
}
func (p *Panic210) getLoadedKexts() (err error) {
	re := regexp.MustCompile(`^loaded kexts:`)
	found := false
	for _, line := range p.lines {
		if re.MatchString(line) {
			found = true
			continue
		}
		if found {
			lkRE := regexp.MustCompile(`^(?P<name>\S+)\s+(?P<version>[0-9.]+)`)
			matches := lkRE.FindStringSubmatch(line)
			if len(matches) < 3 {
				break
			}
			p.LoadedKexts = append(p.LoadedKexts, LoadedKext{
				Name:    matches[1],
				Version: matches[2],
			})
		}
	}
	if found {
		return nil
	}
	return fmt.Errorf("failed to find 'loaded kexts:'")
}
func (p *Panic210) String() string {
	panic := p.Panic
	start, rest, ok := strings.Cut(p.Panic, ":")
	if ok {
		panic = colorField("Panic") + fmt.Sprintf("\n%s\n  %s", start, rest)
	}
	var cores string
	for _, core := range p.Cores {
		cores += fmt.Sprintf("%s\n", core)
	}
	return fmt.Sprintf(
		"%s\n\n"+ // panic
			"%s"+
			"%s"+
			"%s"+
			"%s"+
			"%s"+
			"%s"+
			"%s"+
			"%s"+
			"%s"+
			"%s"+
			"%s"+
			"%s"+
			"%s"+
			"%s"+
			"%s"+
			"%s"+
			"%s"+
			"%s"+
			"%s"+
			"\n%s\n"+ // EpochTime
			"%s"+ // ZoneInfo
			"%s"+ // TPIDRx_ELy
			"%s\n"+ // cores
			"%s"+ // CompressorInfo
			"%s\n"+ // PanickedTask
			"%s"+ // PanickedThread
			"%s"+ // LastStartedKext
			"%s", // LoadedKexts
		panic,
		p.DebuggerMessage,
		p.MemoryID,
		p.OsReleaseType,
		p.OsVersion,
		p.KernelVersion,
		p.KernelCacheUUID,
		p.KernelUUID,
		p.BootSessionUUID,
		p.IBootVersion,
		p.SecureBoot,
		p.RootsInstalled,
		p.PaniclogVersion,
		p.KernelCacheSlide,
		p.KernelCacheBase,
		p.KernelSlide,
		p.KernelTextBase,
		p.KernelTextExecSlide,
		p.KernelTextExecBase,
		p.MachAbsoluteTime,
		p.EpochTime,
		p.ZoneInfo,
		p.TPIDRx_ELy,
		cores,
		p.CompressorInfo,
		p.PanickedTask,
		p.PanickedThread,
		p.LastStartedKext,
		p.LoadedKexts,
	)

}
