// +build go1.7

package is

import (
	"regexp"
	"runtime"
)

// Helper marks the calling function as a test helper function.
// When printing file and line information, that function will be skipped.
//
// Available with Go 1.7 and later.
func (is *I) Helper() {
	is.helpers[callerName(1)] = struct{}{}
}

// callerName gives the function name (qualified with a package path)
// for the caller after skip frames (where 0 means the current function).
func callerName(skip int) string {
	// Make room for the skip PC.
	var pc [1]uintptr
	n := runtime.Callers(skip+2, pc[:]) // skip + runtime.Callers + callerName
	if n == 0 {
		panic("is: zero callers found")
	}
	frames := runtime.CallersFrames(pc[:n])
	frame, _ := frames.Next()
	return frame.Function
}

// The maximum number of stack frames to go through when skipping helper functions for
// the purpose of decorating log messages.
const maxStackLen = 50

var reIsSourceFile = regexp.MustCompile("is(-1.7)?\\.go$")

func (is *I) callerinfo() (path string, line int, ok bool) {
	var pc [maxStackLen]uintptr
	// Skip two extra frames to account for this function
	// and runtime.Callers itself.
	n := runtime.Callers(2, pc[:])
	if n == 0 {
		panic("is: zero callers found")
	}
	frames := runtime.CallersFrames(pc[:n])
	var firstFrame, frame runtime.Frame
	for more := true; more; {
		frame, more = frames.Next()
		if reIsSourceFile.MatchString(frame.File) {
			continue
		}
		if firstFrame.PC == 0 {
			firstFrame = frame
		}
		if _, ok := is.helpers[frame.Function]; ok {
			// Frame is inside a helper function.
			continue
		}
		return frame.File, frame.Line, true
	}
	// If no "non-helper" frame is found, the first non is frame is returned.
	return firstFrame.File, firstFrame.Line, true
}
