// +build ignore

package main

import (
	"bytes"
	"encoding/binary"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"strings"

	"github.com/aclements/go-perf/perffile"
)

func main() {
	var (
		input = flag.String("input", "perf.data", "Name of data file to process")
	)
	flag.Parse()

	f, err := os.Open(*input)
	if err != nil {
		log.Fatalf("err=%q", err)
	}
	defer f.Close()

	buf := make([]byte, 16)
	_, err = io.ReadFull(f, buf)
	if err != nil {
		log.Fatalf("err=%q", err)
	}

	var file *perffile.File
	switch headerSize := binary.LittleEndian.Uint64(buf[8:16]); headerSize {
	case 0x68:
		file, err = perffile.New(f)
	case 0x10:
		file, err = perffile.NewPipe(io.MultiReader(bytes.NewReader(buf), f))
	default:
		err = fmt.Errorf("invalid header size=%#02x", headerSize)
	}
	if err != nil {
		log.Fatalf("err=%q", err)
	}

	type entry struct {
		count int
		name  string
	}
	m := make(map[perffile.RecordType]entry)

	mode := make(map[uint64]int)

	recs := file.Records(perffile.RecordsFileOrder)
loop:
	for recs.Next() {
		rec := recs.Record

		t := rec.Type()
		e, ok := m[t]
		if !ok {
			e.name = fmt.Sprintf("%T", rec)
		}
		e.count++
		m[t] = e

		switch t {
		case perffile.RecordTypeMmap:
			// if rec.Common().PID <= 0 {
			// 	continue loop
			// }
			// switch rec.Common().PID {
			// case 1, 851, 963, 971, 1292, 1360:
			// 	continue loop
			// case 107557:
			// default:
			// 	continue loop
			// }
			// continue loop
			rec := rec.(*perffile.RecordMmap)
			logMmap(rec)
			// break loop
		case perffile.RecordTypeComm:
			// continue loop
			// if rec.Common().PID != rec.Common().TID {
			// 	continue loop
			// }
			// switch rec.Common().PID {
			// case 1703, 1851, 4150, 4164, 4810, 4950, 4983:
			// 	// continue loop
			// case 107557:
			// 	// continue loop
			// }
			rec := rec.(*perffile.RecordComm)
			logComm(rec)
			// break loop
		case perffile.RecordTypeExit:
			// continue loop
			rec := rec.(*perffile.RecordExit)
			logExit(rec)
			// break loop
		case perffile.RecordTypeThrottle:
			// continue loop
			rec := rec.(*perffile.RecordThrottle)
			logThrottle(rec)
			// break loop
		case perffile.RecordTypeFork:
			// continue loop
			rec := rec.(*perffile.RecordFork)
			logFork(rec)
			// break loop
		case perffile.RecordTypeSample:
			// continue loop
			rec := rec.(*perffile.RecordSample)
			logSample(rec)
			// break loop

			for _, ip := range rec.Callchain {
				switch ip {
				case perffile.CallchainHypervisor:
				case perffile.CallchainKernel:
				case perffile.CallchainUser:
					logSample(rec)
					break loop
				case perffile.CallchainGuest:
				case perffile.CallchainGuestKernel:
				case perffile.CallchainGuestUser:
				default:
					continue
				}
				mode[ip]++
			}
		default:
		}
	}

	log.Printf("m %#v", m)
	for m, n := range mode {
		log.Printf("mode=%#x n=%d", m, n)
	}

	err = recs.Err()
	if err != nil {
		log.Fatalf("err=%q", err)
	}
}

type pipe struct {
	io.ReaderAt
	i int
}

func (p *pipe) ReadAt(b []byte, off int64) (int, error) {
	n, err := p.ReaderAt.ReadAt(b, off)
	log.Printf("off=%d n=%d end=%d", off, n, off+int64(n))
	p.i++
	if p.i == 40 {
		panic("")
	}
	return n, err
}

func logFlags(rec *perffile.RecordCommon) {
	for name, bit := range map[string]perffile.SampleFormat{
		"Addr":        perffile.SampleFormatAddr,
		"CPU":         perffile.SampleFormatCPU,
		"Callchain":   perffile.SampleFormatCallchain,
		"ID":          perffile.SampleFormatID,
		"IP":          perffile.SampleFormatIP,
		"Identifier":  perffile.SampleFormatIdentifier,
		"Period":      perffile.SampleFormatPeriod,
		"Raw":         perffile.SampleFormatRaw,
		"Read":        perffile.SampleFormatRead,
		"TID":         perffile.SampleFormatTID,
		"Time":        perffile.SampleFormatTime,
		"Transaction": perffile.SampleFormatTransaction,
		"Weight":      perffile.SampleFormatWeight,
		"BranchStack": perffile.SampleFormatBranchStack,
		"DataSrc":     perffile.SampleFormatDataSrc,
		"RegsIntr":    perffile.SampleFormatRegsIntr,
		"RegsUser":    perffile.SampleFormatRegsUser,
		"StackUser":   perffile.SampleFormatStackUser,
		"StreamID":    perffile.SampleFormatStreamID,
	} {
		log.Printf("set=%-5t name=%q", rec.Format&bit != 0, name)
	}
}

func logCommon(rec perffile.Record) {
	c := rec.Common()

	// logFlags(rec)

	var msgs []string

	if c.Format&perffile.SampleFormatTID != 0 {
		msgs = append(msgs, fmt.Sprintf("PID=%d/%d", c.PID, c.TID))
	}
	if c.Format&perffile.SampleFormatTime != 0 {
		msgs = append(msgs, fmt.Sprintf("Time=%d", c.Time))
	}
	if c.Format&(perffile.SampleFormatID|perffile.SampleFormatIdentifier) != 0 {
		msgs = append(msgs, fmt.Sprintf("ID=%d", c.ID))
	}
	if c.Format&perffile.SampleFormatStreamID != 0 {
		msgs = append(msgs, fmt.Sprintf("stream=%d", c.StreamID))
	}
	if c.Format&perffile.SampleFormatCPU != 0 {
		msgs = append(msgs, fmt.Sprintf("CPU=%d res=%d", c.CPU, c.Res))
	}

	log.Printf("type=%q %s", rec.Type(), strings.Join(msgs, " "))
}

func logMmap(rec *perffile.RecordMmap) {
	logCommon(rec)

	log.Printf("Filename=%q", rec.Filename)
}

func logComm(rec *perffile.RecordComm) {
	logCommon(rec)

	log.Printf("pid=%d exec=%-5t comm=%q", rec.PID, rec.Exec, rec.Comm)
}

func logExit(rec *perffile.RecordExit) {
	logCommon(rec)

	log.Printf("PPID=%d/%d", rec.PPID, rec.PTID)
}

func logThrottle(rec *perffile.RecordThrottle) {
	logCommon(rec)

	log.Printf("enable=%-5t", rec.Enable)
}

func logFork(rec *perffile.RecordFork) {
	logCommon(rec)

	log.Printf("PPID=%d/%d", rec.PPID, rec.PTID)
}

func logSample(rec *perffile.RecordSample) {
	logCommon(rec)

	if rec.RecordCommon.Format&perffile.SampleFormatPeriod != 0 {
		log.Printf("Period=%d", rec.Period)
	}
	if rec.RecordCommon.Format&perffile.SampleFormatIP != 0 {
		log.Printf("IP=%#x", rec.IP)
	}
	if rec.RecordCommon.Format&perffile.SampleFormatCallchain != 0 {
		for _, ip := range rec.Callchain {
			log.Printf("call=%#x", ip)
		}
	}
}
