// Copyright 2021 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 test

import (
	"fmt"
	"internal/coverage"
	"internal/coverage/decodecounter"
	"internal/coverage/encodecounter"
	"io"
	"os"
	"path/filepath"
	"testing"
)

type ctrVis struct {
	funcs []decodecounter.FuncPayload
}

func (v *ctrVis) VisitFuncs(f encodecounter.CounterVisitorFn) error {
	for _, fn := range v.funcs {
		if err := f(fn.PkgIdx, fn.FuncIdx, fn.Counters); err != nil {
			return err
		}
	}
	return nil
}

func mkfunc(p uint32, f uint32, c []uint32) decodecounter.FuncPayload {
	return decodecounter.FuncPayload{
		PkgIdx:   p,
		FuncIdx:  f,
		Counters: c,
	}
}

func TestCounterDataWriterReader(t *testing.T) {
	flavors := []coverage.CounterFlavor{
		coverage.CtrRaw,
		coverage.CtrULeb128,
	}

	isDead := func(fp decodecounter.FuncPayload) bool {
		for _, v := range fp.Counters {
			if v != 0 {
				return false
			}
		}
		return true
	}

	funcs := []decodecounter.FuncPayload{
		mkfunc(0, 0, []uint32{13, 14, 15}),
		mkfunc(0, 1, []uint32{16, 17}),
		mkfunc(1, 0, []uint32{18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 976543, 7}),
	}
	writeVisitor := &ctrVis{funcs: funcs}

	for kf, flav := range flavors {

		t.Logf("testing flavor %d\n", flav)

		// Open a counter data file in preparation for emitting data.
		d := t.TempDir()
		cfpath := filepath.Join(d, fmt.Sprintf("covcounters.hash.0.%d", kf))
		of, err := os.OpenFile(cfpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
		if err != nil {
			t.Fatalf("opening covcounters: %v", err)
		}

		// Perform the encode and write.
		cdfw := encodecounter.NewCoverageDataWriter(of, flav)
		if cdfw == nil {
			t.Fatalf("NewCoverageDataWriter failed")
		}
		finalHash := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0}
		args := map[string]string{"argc": "3", "argv0": "arg0", "argv1": "arg1", "argv2": "arg_________2"}
		if err := cdfw.Write(finalHash, args, writeVisitor); err != nil {
			t.Fatalf("counter file Write failed: %v", err)
		}
		if err := of.Close(); err != nil {
			t.Fatalf("closing covcounters: %v", err)
		}
		cdfw = nil

		// Decode the same file.
		var cdr *decodecounter.CounterDataReader
		inf, err := os.Open(cfpath)
		defer func() {
			if err := inf.Close(); err != nil {
				t.Fatalf("close failed with: %v", err)
			}
		}()

		if err != nil {
			t.Fatalf("reopening covcounters file: %v", err)
		}
		if cdr, err = decodecounter.NewCounterDataReader(cfpath, inf); err != nil {
			t.Fatalf("opening covcounters for read: %v", err)
		}
		decodedArgs := cdr.OsArgs()
		aWant := "[arg0 arg1 arg_________2]"
		aGot := fmt.Sprintf("%+v", decodedArgs)
		if aWant != aGot {
			t.Errorf("reading decoded args, got %s want %s", aGot, aWant)
		}
		for i := range funcs {
			if isDead(funcs[i]) {
				continue
			}
			var fp decodecounter.FuncPayload
			if ok, err := cdr.NextFunc(&fp); err != nil {
				t.Fatalf("reading func %d: %v", i, err)
			} else if !ok {
				t.Fatalf("reading func %d: bad return", i)
			}
			got := fmt.Sprintf("%+v", fp)
			want := fmt.Sprintf("%+v", funcs[i])
			if got != want {
				t.Errorf("cdr.NextFunc iter %d\ngot  %+v\nwant %+v", i, got, want)
			}
		}
		var dummy decodecounter.FuncPayload
		if ok, err := cdr.NextFunc(&dummy); err != nil {
			t.Fatalf("reading func after loop: %v", err)
		} else if ok {
			t.Fatalf("reading func after loop: expected EOF")
		}
	}
}

func TestCounterDataAppendSegment(t *testing.T) {
	d := t.TempDir()
	cfpath := filepath.Join(d, "covcounters.hash2.0")
	of, err := os.OpenFile(cfpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
	if err != nil {
		t.Fatalf("opening covcounters: %v", err)
	}

	const numSegments = 2

	// Write a counter with with multiple segments.
	args := map[string]string{"argc": "1", "argv0": "prog.exe"}
	allfuncs := [][]decodecounter.FuncPayload{}
	ctrs := []uint32{}
	q := uint32(0)
	var cdfw *encodecounter.CoverageDataWriter
	for idx := 0; idx < numSegments; idx++ {
		args[fmt.Sprintf("seg%d", idx)] = "x"
		q += 7
		ctrs = append(ctrs, q)
		funcs := []decodecounter.FuncPayload{}
		for k := 0; k < idx+1; k++ {
			c := make([]uint32, len(ctrs))
			copy(c, ctrs)
			funcs = append(funcs, mkfunc(uint32(idx), uint32(k), c))
		}
		allfuncs = append(allfuncs, funcs)

		writeVisitor := &ctrVis{funcs: funcs}

		if idx == 0 {
			// Perform the encode and write.
			cdfw = encodecounter.NewCoverageDataWriter(of, coverage.CtrRaw)
			if cdfw == nil {
				t.Fatalf("NewCoverageDataWriter failed")
			}
			finalHash := [16]byte{1, 2}
			if err := cdfw.Write(finalHash, args, writeVisitor); err != nil {
				t.Fatalf("counter file Write failed: %v", err)
			}
		} else {
			if err := cdfw.AppendSegment(args, writeVisitor); err != nil {
				t.Fatalf("counter file AppendSegment failed: %v", err)
			}
		}
	}
	if err := of.Close(); err != nil {
		t.Fatalf("closing covcounters: %v", err)
	}

	// Read the result file.
	var cdr *decodecounter.CounterDataReader
	inf, err := os.Open(cfpath)
	defer func() {
		if err := inf.Close(); err != nil {
			t.Fatalf("close failed with: %v", err)
		}
	}()

	if err != nil {
		t.Fatalf("reopening covcounters file: %v", err)
	}
	if cdr, err = decodecounter.NewCounterDataReader(cfpath, inf); err != nil {
		t.Fatalf("opening covcounters for read: %v", err)
	}
	ns := cdr.NumSegments()
	if ns != numSegments {
		t.Fatalf("got %d segments want %d", ns, numSegments)
	}
	if len(allfuncs) != numSegments {
		t.Fatalf("expected %d got %d", numSegments, len(allfuncs))
	}

	for sidx := 0; sidx < int(ns); sidx++ {
		if off, err := inf.Seek(0, io.SeekCurrent); err != nil {
			t.Fatalf("Seek failed: %v", err)
		} else {
			t.Logf("sidx=%d off=%d\n", sidx, off)
		}

		if sidx != 0 {
			if ok, err := cdr.BeginNextSegment(); err != nil {
				t.Fatalf("BeginNextSegment failed: %v", err)
			} else if !ok {
				t.Fatalf("BeginNextSegment return %v on iter %d",
					ok, sidx)
			}
		}
		funcs := allfuncs[sidx]
		for i := range funcs {
			var fp decodecounter.FuncPayload
			if ok, err := cdr.NextFunc(&fp); err != nil {
				t.Fatalf("reading func %d: %v", i, err)
			} else if !ok {
				t.Fatalf("reading func %d: bad return", i)
			}
			got := fmt.Sprintf("%+v", fp)
			want := fmt.Sprintf("%+v", funcs[i])
			if got != want {
				t.Errorf("cdr.NextFunc iter %d\ngot  %+v\nwant %+v", i, got, want)
			}
		}
	}
}