// Copyright (c) 2018-2022, Maxime Soulé
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.

package util_test

import (
	"bytes"
	"math"
	"reflect"
	"runtime"
	"strings"
	"testing"

	"github.com/maxatome/go-testdeep/internal/test"
	"github.com/maxatome/go-testdeep/internal/types"
	"github.com/maxatome/go-testdeep/internal/util"
)

type myTestDeepStringer struct {
	types.TestDeepStamp
}

func (m myTestDeepStringer) String() string {
	return "TesT!"
}

func TestToString(t *testing.T) {
	for _, curTest := range []struct {
		paramGot any
		expected string
	}{
		{paramGot: nil, expected: "nil"},
		{paramGot: "foobar", expected: `"foobar"`},
		{paramGot: "foo\rbar", expected: `(string) (len=7) "foo\rbar"`},
		{paramGot: "foo\u2028bar", expected: `(string) (len=9) "foo\u2028bar"`},
		{paramGot: `foo"bar`, expected: "`foo\"bar`"},
		{paramGot: "foo\n\"bar", expected: "`foo\n\"bar`"},
		{paramGot: "foo`\"\nbar", expected: "(string) (len=9) \"foo`\\\"\\nbar\""},
		{paramGot: "foo`\n\"bar", expected: "(string) (len=9) \"foo`\\n\\\"bar\""},
		{paramGot: "foo\n`\"bar", expected: "(string) (len=9) \"foo\\n`\\\"bar\""},
		{paramGot: "foo\n\"`bar", expected: "(string) (len=9) \"foo\\n\\\"`bar\""},
		{paramGot: reflect.ValueOf("foobar"), expected: `"foobar"`},
		{
			paramGot: reflect.ValueOf(reflect.ValueOf(42)),
			expected: `(reflect.Value) <int Value>`,
		},
		{
			paramGot: []reflect.Value{reflect.ValueOf("foo"), reflect.ValueOf("bar")},
			expected: `("foo",
 "bar")`,
		},
		{
			paramGot: reflect.ValueOf([]reflect.Value{}),
			expected: "([]reflect.Value) {\n}",
		},
		{paramGot: types.RawString("test"), expected: "test"},
		{paramGot: types.RawInt(42), expected: "42"},
		{paramGot: myTestDeepStringer{}, expected: "TesT!"},
		{paramGot: 42, expected: "42"},
		{paramGot: true, expected: "true"},
		{paramGot: false, expected: "false"},
		{paramGot: int64(42), expected: "(int64) 42"},
		{paramGot: float64(42), expected: "42.0"},
		{paramGot: float64(42.56), expected: "42.56"},
		{paramGot: float64(4e56), expected: "4e+56"},
		{paramGot: math.Inf(1), expected: "+Inf"},
		{paramGot: math.Inf(-1), expected: "-Inf"},
		{paramGot: math.NaN(), expected: "NaN"},
	} {
		test.EqualStr(t, util.ToString(curTest.paramGot), curTest.expected)
	}
}

func TestIndentString(t *testing.T) {
	for _, curTest := range []struct {
		ParamGot string
		Expected string
	}{
		{ParamGot: "", Expected: ""},
		{ParamGot: "pipo", Expected: "pipo"},
		{ParamGot: "pipo\nbingo\nzip", Expected: "pipo\n-bingo\n-zip"},
	} {
		test.EqualStr(t, util.IndentString(curTest.ParamGot, "-"), curTest.Expected)

		var buf bytes.Buffer
		util.IndentStringIn(&buf, curTest.ParamGot, "-")
		test.EqualStr(t, buf.String(), curTest.Expected)

		buf.Reset()
		util.IndentColorizeStringIn(&buf, curTest.ParamGot, "-", "", "")
		test.EqualStr(t, buf.String(), curTest.Expected)
	}

	for _, curTest := range []struct {
		ParamGot string
		Expected string
	}{
		{ParamGot: "", Expected: ""},
		{ParamGot: "pipo", Expected: "<<pipo>>"},
		{ParamGot: "pipo\nbingo\nzip", Expected: "<<pipo>>\n-<<bingo>>\n-<<zip>>"},
	} {
		var buf bytes.Buffer
		util.IndentColorizeStringIn(&buf, curTest.ParamGot, "-", "<<", ">>")
		test.EqualStr(t, buf.String(), curTest.Expected)
	}
}

func TestSliceToBuffer(t *testing.T) {
	for _, curTest := range []struct {
		BufInit  string
		Items    []any
		Expected string
	}{
		{BufInit: ">", Items: nil, Expected: ">()"},
		{BufInit: ">", Items: []any{"pipo"}, Expected: `>("pipo")`},
		{
			BufInit: ">",
			Items:   []any{"pipo", "bingo", "zip"},
			Expected: `>("pipo",
  "bingo",
  "zip")`,
		},
		{
			BufInit: "List\n  of\nitems:\n>",
			Items:   []any{"pipo", "bingo", "zip"},
			Expected: `List
  of
items:
>("pipo",
  "bingo",
  "zip")`,
		},
	} {
		var items []reflect.Value
		if curTest.Items != nil {
			items = make([]reflect.Value, len(curTest.Items))
			for i, val := range curTest.Items {
				items[i] = reflect.ValueOf(val)
			}
		}

		var buf strings.Builder
		buf.WriteString(curTest.BufInit)
		test.EqualStr(t, util.SliceToString(&buf, items).String(),
			curTest.Expected)
	}
}

func TestTypeFullName(t *testing.T) {
	// our full package name
	pc, _, _, _ := runtime.Caller(0)
	pkg := strings.TrimSuffix(runtime.FuncForPC(pc).Name(), ".TestTypeFullName")

	test.EqualStr(t, util.TypeFullName(reflect.TypeOf(123)), "int")
	test.EqualStr(t, util.TypeFullName(reflect.TypeOf([]int{})), "[]int")
	test.EqualStr(t, util.TypeFullName(reflect.TypeOf([3]int{})), "[3]int")
	test.EqualStr(t, util.TypeFullName(reflect.TypeOf((**float64)(nil))), "**float64")
	test.EqualStr(t, util.TypeFullName(reflect.TypeOf(map[int]float64{})), "map[int]float64")
	test.EqualStr(t, util.TypeFullName(reflect.TypeOf(struct{}{})), "struct {}")
	test.EqualStr(t, util.TypeFullName(reflect.TypeOf(struct {
		a int
		b bool
	}{})), "struct { a int; b bool }")
	test.EqualStr(t, util.TypeFullName(reflect.TypeOf(struct {
		s struct{ a []int }
		b bool
	}{})), "struct { s struct { a []int }; b bool }")

	type anon struct{ a []int } //nolint: unused
	test.EqualStr(t, util.TypeFullName(reflect.TypeOf(struct {
		anon
		b bool
	}{})), "struct { "+pkg+".anon; b bool }")

	test.EqualStr(t, util.TypeFullName(reflect.TypeOf(func() {})), "func()")
	test.EqualStr(t,
		util.TypeFullName(reflect.TypeOf(func(a int) {})),
		"func(int)")
	test.EqualStr(t,
		util.TypeFullName(reflect.TypeOf(func(a int, b ...bool) rune { return 0 })),
		"func(int, ...bool) int32")
	test.EqualStr(t,
		util.TypeFullName(reflect.TypeOf(func() (int, bool, int) { return 0, true, 0 })),
		"func() (int, bool, int)")

	test.EqualStr(t, util.TypeFullName(reflect.TypeOf(func() {})), "func()")
	test.EqualStr(t,
		util.TypeFullName(reflect.TypeOf(func(a int) {})),
		"func(int)")
	test.EqualStr(t,
		util.TypeFullName(reflect.TypeOf(func(a int, b ...bool) rune { return 0 })),
		"func(int, ...bool) int32")
	test.EqualStr(t,
		util.TypeFullName(reflect.TypeOf(func() (int, bool, int) { return 0, true, 0 })),
		"func() (int, bool, int)")

	test.EqualStr(t,
		util.TypeFullName(reflect.TypeOf((<-chan []int)(nil))),
		"<-chan []int")
	test.EqualStr(t,
		util.TypeFullName(reflect.TypeOf((chan<- []int)(nil))),
		"chan<- []int")
	test.EqualStr(t,
		util.TypeFullName(reflect.TypeOf((chan []int)(nil))),
		"chan []int")

	test.EqualStr(t,
		util.TypeFullName(reflect.TypeOf((*any)(nil))),
		"*interface {}")
}
