// +build ignore

package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"sort"
	"strings"
	"sync"

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

func main() {
	flag.Parse()

	file, err := perffile.NewPipe(os.Stdin)
	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)

	tracker := newRecorder()

	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

		tracker.record(rec)

		if t == perffile.RecordTypeSample {
			logSample(rec.(*perffile.RecordSample))
		}

		if true {
			continue
		}

		switch t {
		case perffile.RecordTypeMmap:
			// continue loop
			rec := rec.(*perffile.RecordMmap)
			logMmap(rec)
			// break loop
		case perffile.RecordTypeComm:
			// continue loop
			rec := rec.(*perffile.RecordComm)
			logComm(rec)
			// break loop
		case perffile.RecordTypeExit:
			// continue loop
			rec := rec.(*perffile.RecordExit)
			logExit(rec)
			// break loop

		case 0x43:
			rec := rec.(*perffile.RecordUnknown)
			logUnknown(rec)

		case perffile.RecordTypeThrottle:
		case perffile.RecordTypeFork:
		case perffile.RecordTypeSample:
			rec := rec.(*perffile.RecordSample)
			logSample(rec)

		default:
			continue loop
		}
	}

	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 cpuState struct {
	time    uint64
	backlog []perffile.Record
}

type procState struct {
	name string
}

type recorder struct {
	mu   sync.Mutex
	cpu  map[uint32]*cpuState
	time uint64
	proc map[int]*procState
}

func newRecorder() *recorder {
	return &recorder{
		cpu:  make(map[uint32]*cpuState),
		proc: make(map[int]*procState),
	}
}

func (r *recorder) setTime(cpu uint32, now uint64) uint64 {
	state := r.cpu[cpu]
	state.time = now
	for _, state := range r.cpu {
		if now < state.time {
			return r.time
		}
	}
	r.time = now
	return now
}

func (r *recorder) record(rec perffile.Record) {
	r.mu.Lock()
	defer r.mu.Unlock()

	if mask := perffile.SampleFormatCPU; rec.Common().Format&mask == mask {
		state, ok := r.cpu[rec.Common().CPU]
		if !ok {
			state = &cpuState{}
			r.cpu[rec.Common().CPU] = state
		}

		state.backlog = append(state.backlog, copyRecord(rec))
	}

	if mask := perffile.SampleFormatTime | perffile.SampleFormatCPU; rec.Common().Format&mask == mask {
		was := r.time
		now := r.setTime(rec.Common().CPU, rec.Common().Time)
		if was != now {
			var todo timedRecords
			for cpu, state := range r.cpu {
				var n int
				for i, entry := range state.backlog {
					var time uint64
					if mask := perffile.SampleFormatTime; entry.Common().Format&mask == mask {
						time = entry.Common().Time
					}
					if time >= now {
						// log.Printf("stopping i=%d backlog=%d time=%d now=%d", i, len(state.backlog), time, now)
						break
					}
					todo = append(todo, timedRecord{
						time:  time,
						cpu:   cpu,
						index: i,
						entry: entry,
					})
					n++
				}
				state.backlog = state.backlog[n:]
			}
			sort.Sort(todo)
			// log.Printf("todo=%d now=%d delta=%d ncpu=%d", len(todo), now, now-was, todo.NCPU())

			r.apply(todo)
		}
	}
}

func (r *recorder) apply(recs timedRecords) {
	for _, rec := range recs {
		switch rec := rec.entry.(type) {
		case *perffile.RecordComm:
			if rec.PID != rec.TID {
				continue
			}
			if proc, ok := r.proc[rec.PID]; ok {
				// TODO: improve understanding of pid reuse
				log.Printf("reuse pid=%d was=%q now=%q exec=%t time=%d", rec.PID, proc.name, rec.Comm, rec.Exec, rec.Time)
			} else {
				r.proc[rec.PID] = &procState{
					name: rec.Comm,
				}
				log.Printf("comm pid=%d name=%q exec=%t time=%d", rec.PID, rec.Comm, rec.Exec, rec.Time)
			}
		case *perffile.RecordExit:
			if proc, ok := r.proc[rec.PID]; ok {
				delete(r.proc, rec.PID)
				log.Printf("exit pid=%d name=%q time=%d", rec.PID, proc.name, rec.Time)
			}
		}
	}
}

func copyRecord(rec perffile.Record) perffile.Record {
	switch rec := rec.(type) {
	case *perffile.RecordMmap:
		rr := *rec
		return &rr
	case *perffile.RecordComm:
		rr := *rec
		return &rr
	case *perffile.RecordExit:
		rr := *rec
		return &rr
	case *perffile.RecordSample:
		rr := *rec
		return &rr
	case *perffile.RecordFork:
		rr := *rec
		return &rr
	case *perffile.RecordLost:
		rr := *rec
		return &rr
	default:
		log.Fatalf("unknown %T", rec)
	}

	if true {
		return nil
	}

	com := rec.Common()
	return &dummyRecord{
		com: &perffile.RecordCommon{
			Offset:    com.Offset,
			Format:    com.Format,
			EventAttr: com.EventAttr,
			PID:       com.PID,
			TID:       com.TID,
			Time:      com.Time,
			ID:        com.ID,
			StreamID:  com.StreamID,
			CPU:       com.CPU,
			Res:       com.Res,
		},
		typ: rec.Type(),
	}
}

type dummyRecord struct {
	com *perffile.RecordCommon
	typ perffile.RecordType
}

func (dr *dummyRecord) Common() *perffile.RecordCommon {
	return dr.com
}

func (dr *dummyRecord) Type() perffile.RecordType {
	return dr.typ
}

type timedRecord struct {
	time  uint64
	cpu   uint32
	index int
	entry perffile.Record
}

type timedRecords []timedRecord

func (tr timedRecords) Len() int      { return len(tr) }
func (tr timedRecords) Swap(i, j int) { tr[i], tr[j] = tr[j], tr[i] }
func (tr timedRecords) Less(i, j int) bool {
	if tr[i].time < tr[j].time {
		return true
	}
	if tr[i].cpu < tr[j].cpu {
		return true
	}
	if tr[i].index < tr[j].index {
		return true
	}
	return false
}

func (tr timedRecords) NCPU() int {
	cpus := make(map[uint32]struct{})
	for _, rec := range tr {
		cpus[rec.cpu] = struct{}{}
	}
	return len(cpus)
}

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.Record) {
	var msgs []string

	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)
		if rec.Common().Format&bit != 0 {
			msgs = append(msgs, fmt.Sprintf("flag=%s", name))
		}
	}
	sort.Strings(msgs)

	log.Printf("%s", strings.Join(msgs, " "))
}

func logCommon(rec perffile.Record) {
	log.Printf(" ")

	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 hextime=%#016x", c.Time, 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 addr=%#x len=%#x", rec.Filename, rec.Addr, rec.Len)
}

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)
		}
	}
}

func logUnknown(rec *perffile.RecordUnknown) {
	logCommon(rec)
	log.Printf("unknown type=%d data=% 02x", rec.Type(), rec.Data)
}
