// Copyright 2022 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 cformat

// This package provides apis for producing human-readable summaries
// of coverage data (e.g. a coverage percentage for a given package or
// set of packages) and for writing data in the legacy test format
// emitted by "go test -coverprofile=<outfile>".
//
// The model for using these apis is to create a Formatter object,
// then make a series of calls to SetPackage and AddUnit passing in
// data read from coverage meta-data and counter-data files. E.g.
//
//		myformatter := cformat.NewFormatter()
//		...
//		for each package P in meta-data file: {
//			myformatter.SetPackage(P)
//			for each function F in P: {
//				for each coverable unit U in F: {
//					myformatter.AddUnit(U)
//				}
//			}
//		}
//		myformatter.EmitPercent(os.Stdout, nil, "", true, true)
//		myformatter.EmitTextual(somefile)
//
// These apis are linked into tests that are built with "-cover", and
// called at the end of test execution to produce text output or
// emit coverage percentages.

import (
	
	
	
	
	
	
	
	
)

type Formatter struct {
	// Maps import path to package state.
	pm map[string]*pstate
	// Records current package being visited.
	pkg string
	// Pointer to current package state.
	p *pstate
	// Counter mode.
	cm coverage.CounterMode
}

// pstate records package-level coverage data state:
// - a table of functions (file/fname/literal)
// - a map recording the index/ID of each func encountered so far
// - a table storing execution count for the coverable units in each func
type pstate struct {
	// slice of unique functions
	funcs []fnfile
	// maps function to index in slice above (index acts as function ID)
	funcTable map[fnfile]uint32

	// A table storing coverage counts for each coverable unit.
	unitTable map[extcu]uint32
}

// extcu encapsulates a coverable unit within some function.
type extcu struct {
	fnfid uint32 // index into p.funcs slice
	coverage.CoverableUnit
}

// fnfile is a function-name/file-name tuple.
type fnfile struct {
	file  string
	fname string
	lit   bool
}

func ( coverage.CounterMode) *Formatter {
	return &Formatter{
		pm: make(map[string]*pstate),
		cm: ,
	}
}

// SetPackage tells the formatter that we're about to visit the
// coverage data for the package with the specified import path.
// Note that it's OK to call SetPackage more than once with the
// same import path; counter data values will be accumulated.
func ( *Formatter) ( string) {
	if  == .pkg {
		return
	}
	.pkg = 
	,  := .pm[]
	if ! {
		 = new(pstate)
		.pm[] = 
		.unitTable = make(map[extcu]uint32)
		.funcTable = make(map[fnfile]uint32)
	}
	.p = 
}

// AddUnit passes info on a single coverable unit (file, funcname,
// literal flag, range of lines, and counter value) to the formatter.
// Counter values will be accumulated where appropriate.
func ( *Formatter) ( string,  string,  bool,  coverage.CoverableUnit,  uint32) {
	if .p == nil {
		panic("AddUnit invoked before SetPackage")
	}
	 := fnfile{file: , fname: , lit: }
	,  := .p.funcTable[]
	if ! {
		 = uint32(len(.p.funcs))
		.p.funcs = append(.p.funcs, )
		.p.funcTable[] = 
	}
	 := extcu{fnfid: , CoverableUnit: }
	 := .p.unitTable[]
	var  uint32
	if .cm == coverage.CtrModeSet {
		if  != 0 ||  != 0 {
			 = 1
		}
	} else {
		// Use saturating arithmetic.
		, _ = cmerge.SaturatingAdd(, )
	}
	.p.unitTable[] = 
}

// sortUnits sorts a slice of extcu objects in a package according to
// source position information (e.g. file and line). Note that we don't
// include function name as part of the sorting criteria, the thinking
// being that is better to provide things in the original source order.
func ( *pstate) ( []extcu) {
	slices.SortFunc(, func(,  extcu) int {
		 := .funcs[.fnfid].file
		 := .funcs[.fnfid].file
		if  := strings.Compare(, );  != 0 {
			return 
		}
		// NB: not taking function literal flag into account here (no
		// need, since other fields are guaranteed to be distinct).
		if  := cmp.Compare(.StLine, .StLine);  != 0 {
			return 
		}
		if  := cmp.Compare(.EnLine, .EnLine);  != 0 {
			return 
		}
		if  := cmp.Compare(.StCol, .StCol);  != 0 {
			return 
		}
		if  := cmp.Compare(.EnCol, .EnCol);  != 0 {
			return 
		}
		return cmp.Compare(.NxStmts, .NxStmts)
	})
}

// EmitTextual writes the accumulated coverage data in the legacy
// cmd/cover text format to the writer 'w'. We sort the data items by
// importpath, source file, and line number before emitting (this sorting
// is not explicitly mandated by the format, but seems like a good idea
// for repeatable/deterministic dumps).
func ( *Formatter) ( io.Writer) error {
	if .cm == coverage.CtrModeInvalid {
		panic("internal error, counter mode unset")
	}
	if ,  := fmt.Fprintf(, "mode: %s\n", .cm.String());  != nil {
		return 
	}
	 := make([]string, 0, len(.pm))
	for  := range .pm {
		 = append(, )
	}
	slices.Sort()
	for ,  := range  {
		 := .pm[]
		 := make([]extcu, 0, len(.unitTable))
		for  := range .unitTable {
			 = append(, )
		}
		.sortUnits()
		for ,  := range  {
			 := .unitTable[]
			 := .funcs[.fnfid].file
			if ,  := fmt.Fprintf(, "%s:%d.%d,%d.%d %d %d\n",
				, .StLine, .StCol,
				.EnLine, .EnCol, .NxStmts, );  != nil {
				return 
			}
		}
	}
	return nil
}

// EmitPercent writes out a "percentage covered" string to the writer
// 'w', selecting the set of packages in 'pkgs' and suffixing the
// printed string with 'inpkgs'.
func ( *Formatter) ( io.Writer,  []string,  string,  bool,  bool) error {
	if len() == 0 {
		 = make([]string, 0, len(.pm))
		for  := range .pm {
			 = append(, )
		}
	}

	 := func(,  uint64) error {
		if  != 0 {
			if ,  := fmt.Fprintf(, "coverage: %.1f%% of statements%s\n",
				100.0*float64()/float64(), );  != nil {
				return 
			}
		} else if  {
			if ,  := fmt.Fprintf(, "coverage: [no statements]\n");  != nil {
				return 
			}
		}
		return nil
	}

	slices.Sort()
	var ,  uint64
	for ,  := range  {
		 := .pm[]
		if  == nil {
			continue
		}
		if ! {
			,  = 0, 0
		}
		for ,  := range .unitTable {
			 := uint64(.NxStmts)
			 += 
			if  != 0 {
				 += 
			}
		}
		if ! {
			if ,  := fmt.Fprintf(, "\t%s\t\t", );  != nil {
				return 
			}
			if  := (, );  != nil {
				return 
			}
		}
	}
	if  {
		if  := (, );  != nil {
			return 
		}
	}

	return nil
}

// EmitFuncs writes out a function-level summary to the writer 'w'. A
// note on handling function literals: although we collect coverage
// data for unnamed literals, it probably does not make sense to
// include them in the function summary since there isn't any good way
// to name them (this is also consistent with the legacy cmd/cover
// implementation). We do want to include their counts in the overall
// summary however.
func ( *Formatter) ( io.Writer) error {
	if .cm == coverage.CtrModeInvalid {
		panic("internal error, counter mode unset")
	}
	 := func(,  uint64) float64 {
		if  == 0 {
			 = 1
		}
		return 100.0 * float64() / float64()
	}
	 := tabwriter.NewWriter(, 1, 8, 1, '\t', 0)
	defer .Flush()
	 := uint64(0)
	 := uint64(0)

	 := make([]string, 0, len(.pm))
	for  := range .pm {
		 = append(, )
	}
	slices.Sort()

	// Emit functions for each package, sorted by import path.
	for ,  := range  {
		 := .pm[]
		if len(.unitTable) == 0 {
			continue
		}
		 := make([]extcu, 0, len(.unitTable))
		for  := range .unitTable {
			 = append(, )
		}

		// Within a package, sort the units, then walk through the
		// sorted array. Each time we hit a new function, emit the
		// summary entry for the previous function, then make one last
		// emit call at the end of the loop.
		.sortUnits()
		 := ""
		 := ""
		 := false
		var  uint32
		var ,  uint64
		 := func( extcu) {
			 = .funcs[.fnfid].fname
			 = .funcs[.fnfid].file
			 = .funcs[.fnfid].lit
			 = .StLine
		}
		 := func( extcu) error {
			// Don't emit entries for function literals (see discussion
			// in function header comment above).
			if ! {
				if ,  := fmt.Fprintf(, "%s:%d:\t%s\t%.1f%%\n",
					, , , (, ));  != nil {
					return 
				}
			}
			()
			 += 
			 += 
			 = 0
			 = 0
			return nil
		}
		for ,  := range  {
			if  == 0 {
				()
			} else {
				if  != .funcs[.fnfid].fname {
					// New function; emit entry for previous one.
					if  := ();  != nil {
						return 
					}
				}
			}
			 += uint64(.NxStmts)
			 := .unitTable[]
			if  != 0 {
				 += uint64(.NxStmts)
			}
		}
		if  := (extcu{});  != nil {
			return 
		}
	}
	if ,  := fmt.Fprintf(, "%s\t%s\t%.1f%%\n",
		"total", "(statements)", (, ));  != nil {
		return 
	}
	return nil
}