// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package diag

import (
	"fmt"
	"runtime"
	"strings"
)

// Caller returns the file and line number of a frame on the caller's stack.
// If the funtion fails an empty string is returned.
// skipFrames - the number of frames to skip when determining the caller.
// Passing a value of 0 will return the immediate caller of this function.
func Caller(skipFrames int) string {
	if pc, file, line, ok := runtime.Caller(skipFrames + 1); ok {
		// the skipFrames + 1 is to skip ourselves
		frame := runtime.FuncForPC(pc)
		return fmt.Sprintf("%s()\n\t%s:%d", frame.Name(), file, line)
	}
	return ""
}

// StackTrace returns a formatted stack trace string.
// If the funtion fails an empty string is returned.
// skipFrames - the number of stack frames to skip before composing the trace string.
// totalFrames - the maximum number of stack frames to include in the trace string.
func StackTrace(skipFrames, totalFrames int) string {
	pcCallers := make([]uintptr, totalFrames)
	if frames := runtime.Callers(skipFrames, pcCallers); frames == 0 {
		return ""
	}
	frames := runtime.CallersFrames(pcCallers)
	sb := strings.Builder{}
	for {
		frame, more := frames.Next()
		sb.WriteString(frame.Function)
		sb.WriteString("()\n\t")
		sb.WriteString(frame.File)
		sb.WriteRune(':')
		fmt.Fprintf(&sb, "%d\n", frame.Line)
		if !more {
			break
		}
	}
	return sb.String()
}
