// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package trace

import (
	
	
	
	
	
	
	
	

	
	
)

// generation contains all the trace data for a single
// trace generation. It is purely data: it does not
// track any parse state nor does it contain a cursor
// into the generation.
type generation struct {
	gen        uint64
	batches    map[ThreadID][]batch
	cpuSamples []cpuSample
	*evTable
}

// spilledBatch represents a batch that was read out for the next generation,
// while reading the previous one. It's passed on when parsing the next
// generation.
type spilledBatch struct {
	gen uint64
	*batch
}

// readGeneration buffers and decodes the structural elements of a trace generation
// out of r. spill is the first batch of the new generation (already buffered and
// parsed from reading the last generation). Returns the generation and the first
// batch read of the next generation, if any.
func readGeneration( *bufio.Reader,  *spilledBatch) (*generation, *spilledBatch, error) {
	 := &generation{
		evTable: new(evTable),
		batches: make(map[ThreadID][]batch),
	}
	// Process the spilled batch.
	if  != nil {
		.gen = .gen
		if  := processBatch(, *.batch);  != nil {
			return nil, nil, 
		}
		 = nil
	}
	// Read batches one at a time until we either hit EOF or
	// the next generation.
	for {
		, ,  := readBatch()
		if  == io.EOF {
			break
		}
		if  != nil {
			return nil, nil, 
		}
		if  == 0 {
			// 0 is a sentinel used by the runtime, so we'll never see it.
			return nil, nil, fmt.Errorf("invalid generation number %d", )
		}
		if .gen == 0 {
			// Initialize gen.
			.gen = 
		}
		if  == .gen+1 { // TODO: advance this the same way the runtime does.
			 = &spilledBatch{gen: , batch: &}
			break
		}
		if  != .gen {
			// N.B. Fail as fast as possible if we see this. At first it
			// may seem prudent to be fault-tolerant and assume we have a
			// complete generation, parsing and returning that first. However,
			// if the batches are mixed across generations then it's likely
			// we won't be able to parse this generation correctly at all.
			// Rather than return a cryptic error in that case, indicate the
			// problem as soon as we see it.
			return nil, nil, fmt.Errorf("generations out of order")
		}
		if  := processBatch(, );  != nil {
			return nil, nil, 
		}
	}

	// Check some invariants.
	if .freq == 0 {
		return nil, nil, fmt.Errorf("no frequency event found")
	}
	// N.B. Trust that the batch order is correct. We can't validate the batch order
	// by timestamp because the timestamps could just be plain wrong. The source of
	// truth is the order things appear in the trace and the partial order sequence
	// numbers on certain events. If it turns out the batch order is actually incorrect
	// we'll very likely fail to advance a partial order from the frontier.

	// Compactify stacks and strings for better lookup performance later.
	.stacks.compactify()
	.strings.compactify()

	// Validate stacks.
	if  := validateStackStrings(&.stacks, &.strings);  != nil {
		return nil, nil, 
	}

	// Fix up the CPU sample timestamps, now that we have freq.
	for  := range .cpuSamples {
		 := &.cpuSamples[]
		.time = .freq.mul(timestamp(.time))
	}
	// Sort the CPU samples.
	slices.SortFunc(.cpuSamples, func(,  cpuSample) int {
		return cmp.Compare(.time, .time)
	})
	return , , nil
}

// processBatch adds the batch to the generation.
func processBatch( *generation,  batch) error {
	switch {
	case .isStringsBatch():
		if  := addStrings(&.strings, );  != nil {
			return 
		}
	case .isStacksBatch():
		if  := addStacks(&.stacks, );  != nil {
			return 
		}
	case .isCPUSamplesBatch():
		,  := addCPUSamples(.cpuSamples, )
		if  != nil {
			return 
		}
		.cpuSamples = 
	case .isFreqBatch():
		,  := parseFreq()
		if  != nil {
			return 
		}
		if .freq != 0 {
			return fmt.Errorf("found multiple frequency events")
		}
		.freq = 
	default:
		.batches[.m] = append(.batches[.m], )
	}
	return nil
}

// validateStackStrings makes sure all the string references in
// the stack table are present in the string table.
func validateStackStrings( *dataTable[stackID, stack],  *dataTable[stringID, string]) error {
	var  error
	.forEach(func( stackID,  stack) bool {
		for ,  := range .frames {
			,  := .get(.funcID)
			if ! {
				 = fmt.Errorf("found invalid func string ID %d for stack %d", .funcID, )
				return false
			}
			_,  = .get(.fileID)
			if ! {
				 = fmt.Errorf("found invalid file string ID %d for stack %d", .fileID, )
				return false
			}
		}
		return true
	})
	return 
}

// addStrings takes a batch whose first byte is an EvStrings event
// (indicating that the batch contains only strings) and adds each
// string contained therein to the provided strings map.
func addStrings( *dataTable[stringID, string],  batch) error {
	if !.isStringsBatch() {
		return fmt.Errorf("internal error: addStrings called on non-string batch")
	}
	 := bytes.NewReader(.data)
	,  := .ReadByte() // Consume the EvStrings byte.
	if  != nil || event.Type() != go122.EvStrings {
		return fmt.Errorf("missing strings batch header")
	}

	var  strings.Builder
	for .Len() != 0 {
		// Read the header.
		,  := .ReadByte()
		if  != nil {
			return 
		}
		if event.Type() != go122.EvString {
			return fmt.Errorf("expected string event, got %d", )
		}

		// Read the string's ID.
		,  := binary.ReadUvarint()
		if  != nil {
			return 
		}

		// Read the string's length.
		,  := binary.ReadUvarint()
		if  != nil {
			return 
		}
		if  > go122.MaxStringSize {
			return fmt.Errorf("invalid string size %d, maximum is %d", , go122.MaxStringSize)
		}

		// Copy out the string.
		,  := io.CopyN(&, , int64())
		if  != int64() {
			return fmt.Errorf("failed to read full string: read %d but wanted %d", , )
		}
		if  != nil {
			return fmt.Errorf("copying string data: %w", )
		}

		// Add the string to the map.
		 := .String()
		.Reset()
		if  := .insert(stringID(), );  != nil {
			return 
		}
	}
	return nil
}

// addStacks takes a batch whose first byte is an EvStacks event
// (indicating that the batch contains only stacks) and adds each
// string contained therein to the provided stacks map.
func addStacks( *dataTable[stackID, stack],  batch) error {
	if !.isStacksBatch() {
		return fmt.Errorf("internal error: addStacks called on non-stacks batch")
	}
	 := bytes.NewReader(.data)
	,  := .ReadByte() // Consume the EvStacks byte.
	if  != nil || event.Type() != go122.EvStacks {
		return fmt.Errorf("missing stacks batch header")
	}

	for .Len() != 0 {
		// Read the header.
		,  := .ReadByte()
		if  != nil {
			return 
		}
		if event.Type() != go122.EvStack {
			return fmt.Errorf("expected stack event, got %d", )
		}

		// Read the stack's ID.
		,  := binary.ReadUvarint()
		if  != nil {
			return 
		}

		// Read how many frames are in each stack.
		,  := binary.ReadUvarint()
		if  != nil {
			return 
		}
		if  > go122.MaxFramesPerStack {
			return fmt.Errorf("invalid stack size %d, maximum is %d", , go122.MaxFramesPerStack)
		}

		// Each frame consists of 4 fields: pc, funcID (string), fileID (string), line.
		 := make([]frame, 0, )
		for  := uint64(0);  < ; ++ {
			// Read the frame data.
			,  := binary.ReadUvarint()
			if  != nil {
				return fmt.Errorf("reading frame %d's PC for stack %d: %w", +1, , )
			}
			,  := binary.ReadUvarint()
			if  != nil {
				return fmt.Errorf("reading frame %d's funcID for stack %d: %w", +1, , )
			}
			,  := binary.ReadUvarint()
			if  != nil {
				return fmt.Errorf("reading frame %d's fileID for stack %d: %w", +1, , )
			}
			,  := binary.ReadUvarint()
			if  != nil {
				return fmt.Errorf("reading frame %d's line for stack %d: %w", +1, , )
			}
			 = append(, frame{
				pc:     ,
				funcID: stringID(),
				fileID: stringID(),
				line:   ,
			})
		}

		// Add the stack to the map.
		if  := .insert(stackID(), stack{frames: });  != nil {
			return 
		}
	}
	return nil
}

// addCPUSamples takes a batch whose first byte is an EvCPUSamples event
// (indicating that the batch contains only CPU samples) and adds each
// sample contained therein to the provided samples list.
func addCPUSamples( []cpuSample,  batch) ([]cpuSample, error) {
	if !.isCPUSamplesBatch() {
		return nil, fmt.Errorf("internal error: addStrings called on non-string batch")
	}
	 := bytes.NewReader(.data)
	,  := .ReadByte() // Consume the EvCPUSamples byte.
	if  != nil || event.Type() != go122.EvCPUSamples {
		return nil, fmt.Errorf("missing CPU samples batch header")
	}

	for .Len() != 0 {
		// Read the header.
		,  := .ReadByte()
		if  != nil {
			return nil, 
		}
		if event.Type() != go122.EvCPUSample {
			return nil, fmt.Errorf("expected CPU sample event, got %d", )
		}

		// Read the sample's timestamp.
		,  := binary.ReadUvarint()
		if  != nil {
			return nil, 
		}

		// Read the sample's M.
		,  := binary.ReadUvarint()
		if  != nil {
			return nil, 
		}
		 := ThreadID()

		// Read the sample's P.
		,  := binary.ReadUvarint()
		if  != nil {
			return nil, 
		}
		 := ProcID()

		// Read the sample's G.
		,  := binary.ReadUvarint()
		if  != nil {
			return nil, 
		}
		 := GoID()
		if  == 0 {
			 = NoGoroutine
		}

		// Read the sample's stack.
		,  := binary.ReadUvarint()
		if  != nil {
			return nil, 
		}

		// Add the sample to the slice.
		 = append(, cpuSample{
			schedCtx: schedCtx{
				M: ,
				P: ,
				G: ,
			},
			time:  Time(), // N.B. this is really a "timestamp," not a Time.
			stack: stackID(),
		})
	}
	return , nil
}

// parseFreq parses out a lone EvFrequency from a batch.
func parseFreq( batch) (frequency, error) {
	if !.isFreqBatch() {
		return 0, fmt.Errorf("internal error: parseFreq called on non-frequency batch")
	}
	 := bytes.NewReader(.data)
	.ReadByte() // Consume the EvFrequency byte.

	// Read the frequency. It'll come out as timestamp units per second.
	,  := binary.ReadUvarint()
	if  != nil {
		return 0, 
	}
	// Convert to nanoseconds per timestamp unit.
	return frequency(1.0 / (float64() / 1e9)), nil
}