// This file was AUTOMATICALLY GENERATED by terst-import (smuggol) from github.com/robertkrimen/terst

/*
Package terst is a terse (terst = test + terse), easy-to-use testing library for Go.

terst is compatible with (and works via) the standard testing package: http://golang.org/pkg/testing

	var is = terst.Is

	func Test(t *testing.T) {
	    terst.Terst(t, func() {
	        is("abc", "abc")

	        is(1, ">", 0)

	        var abc []int
	        is(abc, nil)
	    }
	}

Do not import terst directly, instead use `terst-import` to copy it into your testing environment:

https://github.com/robertkrimen/terst/tree/master/terst-import

	$ go get github.com/robertkrimen/terst/terst-import

	$ terst-import
*/
package terst

import (
	"bytes"
	"errors"
	"fmt"
	"math/big"
	"reflect"
	"regexp"
	goruntime "runtime"
	"strings"
	"sync"
	"testing"
	"time"
)

// Is compares two values (got & expect) and returns true if the comparison is true,
// false otherwise. In addition, if the comparison is false, Is will report the error
// in a manner similar to testing.T.Error(...). Is also takes an optional argument,
// a comparator, that changes how the comparison is made.  The following
// comparators are available:
//
//	==      # got == expect (default)
//	!=      # got != expect
//
//	>       # got > expect (float32, uint, uint16, int, int64, ...)
//	>=      # got >= expect
//	<       # got < expect
//	<=      # got <= expect
//
//	=~      # regexp.MustCompile(expect).Match{String}(got)
//	!~      # !regexp.MustCompile(expect).Match{String}(got)
//
// Basic usage with the default comparator (==):
//
//	Is(<got>, <expect>)
//
// Specifying a different comparator:
//
//	Is(<got>, <comparator>, <expect>)
//
// A simple comparison:
//
//	Is(2 + 2, 4)
//
// A bit trickier:
//
//	Is(1, ">", 0)
//	Is(2 + 2, "!=", 5)
//	Is("Nothing happens.", "=~", `ing(\s+)happens\.$`)
//
// Is should only be called under a Terst(t, ...) call. For a standalone version,
// use IsErr. If no scope is found and the comparison is false, then Is will panic the error.
func Is(arguments ...interface{}) bool {
	err := IsErr(arguments...)
	if err != nil {
		call := Caller()
		if call == nil {
			panic(err)
		}
		call.Error(err)
		return false
	}
	return true
}

type (
	// ErrFail indicates a comparison failure (e.g. 0 > 1).
	ErrFail error

	// ErrInvalid indicates an invalid comparison (e.g. bool == string).
	ErrInvalid error
)

var errInvalid = errors.New("invalid")

var registry = struct {
	table map[uintptr]*_scope
	lock  sync.RWMutex
}{
	table: map[uintptr]*_scope{},
}

func registerScope(pc uintptr, scope *_scope) {
	registry.lock.Lock()
	defer registry.lock.Unlock()
	registry.table[pc] = scope
}

func getScope() *_scope {
	s, _ := findScope()
	return s
}

func floatCompare(a float64, b float64) int {
	if a > b {
		return 1
	} else if a < b {
		return -1
	}
	// NaN == NaN
	return 0
}

func bigIntCompare(a *big.Int, b *big.Int) int {
	return a.Cmp(b)
}

func bigInt(value int64) *big.Int {
	return big.NewInt(value)
}

func bigUint(value uint64) *big.Int {
	return big.NewInt(0).SetUint64(value)
}

func toString(value interface{}) (string, error) {
	switch value := value.(type) {
	case string:
		return value, nil
	case fmt.Stringer:
		return value.String(), nil
	case error:
		return value.Error(), nil
	}
	return "", errInvalid
}

func matchString(got string, expect *regexp.Regexp) (int, error) {
	if expect.MatchString(got) {
		return 0, nil
	}
	return -1, nil
}

func match(got []byte, expect *regexp.Regexp) (int, error) {
	if expect.Match(got) {
		return 0, nil
	}
	return -1, nil
}

func compareMatch(got, expect interface{}) (int, error) {
	switch got := got.(type) {
	case []byte:
		switch expect := expect.(type) {
		case string:
			matcher, err := regexp.Compile(expect)
			if err != nil {
				return 0, err
			}
			return match(got, matcher)
		case *regexp.Regexp:
			return match(got, expect)
		}
	default:
		if got, err := toString(got); err == nil {
			switch expect := expect.(type) {
			case string:
				matcher, err := regexp.Compile(expect)
				if err != nil {
					return 0, err
				}
				return matchString(got, matcher)
			case *regexp.Regexp:
				return matchString(got, expect)
			}
		} else {
			return 0, err
		}
	}
	return 0, errInvalid
}

func floatPromote(value reflect.Value) (float64, error) {
	kind := value.Kind()
	if reflect.Int <= kind && kind <= reflect.Int64 {
		return float64(value.Int()), nil
	}
	if reflect.Uint <= kind && kind <= reflect.Uint64 {
		return float64(value.Uint()), nil
	}
	if reflect.Float32 <= kind && kind <= reflect.Float64 {
		return value.Float(), nil
	}
	return 0, errInvalid
}

func bigIntPromote(value reflect.Value) (*big.Int, error) {
	kind := value.Kind()
	if reflect.Int <= kind && kind <= reflect.Int64 {
		return bigInt(value.Int()), nil
	}
	if reflect.Uint <= kind && kind <= reflect.Uint64 {
		return bigUint(value.Uint()), nil
	}
	return nil, errInvalid
}

func compareOther(got, expect interface{}) (int, error) {
	switch expect.(type) {
	case float32, float64:
		return compareNumber(got, expect)
	case uint, uint8, uint16, uint32, uint64:
		return compareNumber(got, expect)
	case int, int8, int16, int32, int64:
		return compareNumber(got, expect)
	case string:
		var err error
		got, err = toString(got)
		if err != nil {
			return 0, err
		}
	case nil:
		got := reflect.ValueOf(got)
		switch got.Kind() {
		case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.Interface:
			if got.IsNil() {
				return 0, nil
			}
			return -1, nil
		case reflect.Invalid: // reflect.Invalid: var abc interface{} = nil
			return 0, nil
		}
		return 0, errInvalid
	}

	if reflect.ValueOf(got).Type() != reflect.ValueOf(expect).Type() {
		return 0, errInvalid
	}

	if reflect.DeepEqual(got, expect) {
		return 0, nil
	}
	return -1, nil
}

func compareNumber(got, expect interface{}) (int, error) {
	gotRv := reflect.ValueOf(got)
	gotKind := gotRv.Kind()
	expectRv := reflect.ValueOf(expect)
	expectKind := expectRv.Kind()
	if reflect.Float32 <= gotKind && gotKind <= reflect.Float64 ||
		reflect.Float32 <= expectKind && expectKind <= reflect.Float64 {
		got, err := floatPromote(gotRv)
		if err != nil {
			return 0, err
		}
		expect, err := floatPromote(expectRv)
		if err != nil {
			return 0, err
		}
		return floatCompare(got, expect), nil
	}

	goBigInt, err := bigIntPromote(gotRv)
	if err != nil {
		return 0, err
	}
	expectBigInt, err := bigIntPromote(expectRv)
	if err != nil {
		return 0, err
	}
	return goBigInt.Cmp(expectBigInt), nil
}

// IsErr compares two values (got & expect) and returns nil if the comparison is true, an ErrFail if
// the comparison is false, or an ErrInvalid if the comparison is invalid. IsErr also
// takes an optional argument, a comparator, that changes how the comparison is made.
//
// Is & IsErr are similar but different:
//
//	Is(...)     // Should only be called within a Terst(...) call
//	IsErr(...)  // A standalone comparator, the same as Is, just without the automatic reporting
func IsErr(arguments ...interface{}) error {
	var got, expect interface{}
	comparator := "=="
	switch len(arguments) {
	case 0, 1:
		return fmt.Errorf("invalid number of arguments to IsErr: %d", len(arguments))
	case 2:
		got, expect = arguments[0], arguments[1]
	default:
		if value, ok := arguments[1].(string); ok {
			comparator = value
		} else {
			return fmt.Errorf("invalid comparator: %v", arguments[1])
		}
		got, expect = arguments[0], arguments[2]
	}

	var result int
	var err error

	switch comparator {
	case "<", "<=", ">", ">=":
		result, err = compareNumber(got, expect)
	case "=~", "!~":
		result, err = compareMatch(got, expect)
	case "==", "!=":
		result, err = compareOther(got, expect)
	default:
		return fmt.Errorf("invalid comparator: %s", comparator)
	}

	if err == errInvalid {
		return ErrInvalid(fmt.Errorf(
			"\nINVALID (%s):\n        got: %v (%T)\n   expected: %v (%T)",
			comparator,
			got, got,
			expect, expect,
		))
	} else if err != nil {
		return err
	}

	equality, pass := false, false

	switch comparator {
	case "==", "=~":
		equality = true
		pass = result == 0
	case "!=", "!~":
		equality = true
		pass = result != 0
	case "<":
		pass = result < 0
	case "<=":
		pass = result <= 0
	case ">":
		pass = result > 0
	case ">=":
		pass = result >= 0
	}

	if !pass {
		if equality {
			if comparator[1] == '~' {
				if value, ok := got.([]byte); ok {
					return ErrFail(fmt.Errorf(
						"\nFAIL (%s)\n     got: %s %v%s\nexpected: %v%s",
						comparator,
						value, got, typeKindString(got),
						expect, typeKindString(expect),
					))
				}
			}
			return ErrFail(fmt.Errorf(
				"\nFAIL (%s)\n     got: %v%s\nexpected: %v%s",
				comparator,
				got, typeKindString(got),
				expect, typeKindString(expect),
			))
		}
		return ErrFail(fmt.Errorf(
			"\nFAIL (%s)\n     got: %v%s\nexpected: %s %v%s",
			comparator,
			got, typeKindString(got),
			comparator, expect, typeKindString(expect),
		))
	}

	return nil
}

func typeKindString(value interface{}) string {
	reflectValue := reflect.ValueOf(value)
	kind := reflectValue.Kind().String()
	result := fmt.Sprintf("%T", value)
	if kind == result {
		if kind == "string" {
			return ""
		}
		return fmt.Sprintf(" (%T)", value)
	}
	return fmt.Sprintf(" (%T=%s)", value, kind)
}

func (s *_scope) reset() {
	s.name = ""
	s.output = s.output[:0]
	s.start = time.Time{}
	s.duration = 0
}

// Terst creates a testing scope, where Is can be called and errors will be reported
// according to the top-level location of the comparison, and not where the Is call
// actually takes place. For example:
//
//	func test(value int) {
//	    Is(value, 5) // <--- This failure is reported below.
//	}
//
//	Terst(t, func(){
//
//	    Is(2, ">", 3) // <--- An error is reported here.
//
//	    test(5) // <--- An error is reported here.
//
//	})
func Terst(t *testing.T, arguments ...func()) {
	curScope := &_scope{
		t: t,
	}

	pc, _, _, ok := goruntime.Caller(1) // TODO Associate with the Test... func
	if !ok {
		panic("Here be dragons.")
	}

	_, curScope.testFunc = findTestFunc()

	registerScope(pc, curScope)

	for _, fn := range arguments {
		func() {
			curScope.reset()
			name := curScope.testFunc.Name()
			index := strings.LastIndex(curScope.testFunc.Name(), ".")
			if index >= 0 {
				name = name[index+1:] + "(Terst)"
			} else {
				name = "(Terst)"
			}
			name = "(Terst)"
			curScope.name = name
			curScope.start = time.Now()
			defer func() {
				curScope.duration = time.Now().Sub(curScope.start)
				if err := recover(); err != nil {
					curScope.t.Fail()
					curScope.report()
					panic(err)
				}
				curScope.report()
			}()
			fn()
		}()
	}
}

// From "testing"
func (s *_scope) report() {
	format := "~~~ %s: (Terst)\n%s"
	if s.t.Failed() {
		fmt.Printf(format, "FAIL", s.output)
	} else if testing.Verbose() && len(s.output) > 0 {
		fmt.Printf(format, "PASS", s.output)
	}
}

func (s *_scope) log(call entry, str string) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.output = append(s.output, decorate(call, str)...)
}

// decorate prefixes the string with the file and line of the call site
// and inserts the final newline if needed and indentation tabs for formascing.
func decorate(call entry, s string) string {

	file, line := call.File, call.Line
	if call.PC > 0 {
		// Truncate file name at last file name separator.
		if index := strings.LastIndex(file, "/"); index >= 0 {
			file = file[index+1:]
		} else if index = strings.LastIndex(file, "\\"); index >= 0 {
			file = file[index+1:]
		}
	} else {
		file = "???"
		line = 1
	}
	buf := new(bytes.Buffer)
	// Every line is indented at least one tab.
	buf.WriteByte('\t')
	fmt.Fprintf(buf, "%s:%d: ", file, line)
	lines := strings.Split(s, "\n")
	if l := len(lines); l > 1 && lines[l-1] == "" {
		lines = lines[:l-1]
	}
	for i, line := range lines {
		if i > 0 {
			// Second and subsequent lines are indented an extra tab.
			buf.WriteString("\n\t\t")
		}
		buf.WriteString(line)
	}
	buf.WriteByte('\n')
	return buf.String()
}

func findScope() (*_scope, entry) {
	registry.lock.RLock()
	defer registry.lock.RUnlock()
	table := registry.table
	depth := 2 // Starting depth
	call := entry{}
	for {
		pc, _, _, ok := goruntime.Caller(depth)
		if !ok {
			break
		}
		if s, exists := table[pc]; exists {
			pc, file, line, _ := goruntime.Caller(depth - 3) // Terst(...) + func(){}() + fn() => ???()
			call.PC = pc
			call.File = file
			call.Line = line
			return s, call
		}
		depth++
	}
	return nil, entry{}
}

// Call is a reference to a line immediately under a Terst testing scope.
type Call struct {
	scope *_scope
	entry entry
}

// Caller will search the stack, looking for a Terst testing scope. If a scope
// is found, then Caller returns a Call for logging errors, accessing testing.T, etc.
// If no scope is found, Caller returns nil.
func Caller() *Call {
	curScope, entry := findScope()
	if curScope == nil {
		return nil
	}
	return &Call{
		scope: curScope,
		entry: entry,
	}
}

// TestFunc returns the *runtime.Func entry for the top-level Test...(t testing.T)
// function.
func (cl *Call) TestFunc() *goruntime.Func {
	return cl.scope.testFunc
}

// T returns the original testing.T passed to Terst(...)
func (cl *Call) T() *testing.T {
	return cl.scope.t
}

// Log is the terst version of `testing.T.Log`
func (cl *Call) Log(arguments ...interface{}) {
	cl.scope.log(cl.entry, fmt.Sprintln(arguments...))
}

// Logf is the terst version of `testing.T.Logf`
func (cl *Call) Logf(format string, arguments ...interface{}) {
	cl.scope.log(cl.entry, fmt.Sprintf(format, arguments...))
}

// Error is the terst version of `testing.T.Error`
func (cl *Call) Error(arguments ...interface{}) {
	cl.scope.log(cl.entry, fmt.Sprintln(arguments...))
	cl.scope.t.Fail()
}

// Errorf is the terst version of `testing.T.Errorf`
func (cl *Call) Errorf(format string, arguments ...interface{}) {
	cl.scope.log(cl.entry, fmt.Sprintf(format, arguments...))
	cl.scope.t.Fail()
}

// Skip is the terst version of `testing.T.Skip`
func (cl *Call) Skip(arguments ...interface{}) {
	cl.scope.log(cl.entry, fmt.Sprintln(arguments...))
	cl.scope.t.SkipNow()
}

// Skipf is the terst version of `testing.T.Skipf`
func (cl *Call) Skipf(format string, arguments ...interface{}) {
	cl.scope.log(cl.entry, fmt.Sprintf(format, arguments...))
	cl.scope.t.SkipNow()
}

type _scope struct {
	t        *testing.T
	testFunc *goruntime.Func
	name     string
	mu       sync.RWMutex
	output   []byte
	start    time.Time
	duration time.Duration
}

type entry struct {
	PC   uintptr
	File string
	Line int
	Func *goruntime.Func
}

func findFunc(match string) (entry, *goruntime.Func) {
	depth := 2 // Starting depth
	for {
		pc, file, line, ok := goruntime.Caller(depth)
		if !ok {
			break
		}
		fn := goruntime.FuncForPC(pc)
		name := fn.Name()
		if index := strings.LastIndex(name, match); index >= 0 {
			// Assume we have an instance of TestXyzzy in a _test file
			return entry{
				PC:   pc,
				File: file,
				Line: line,
				Func: fn,
			}, fn
		}
		depth++
	}
	return entry{}, nil
}

func findTestFunc() (entry, *goruntime.Func) {
	return findFunc(".Test")
}

func findTerstFunc() (entry, *goruntime.Func) {
	return findFunc(".Terst")
}
