package pkgpath

import (
	"runtime"
	"strings"
)

// Caller returns the import path of functions on the calling goroutine's
// stack.  The skip parameter indicates how many stack frames should be
// ascended, with 0 referring to the caller of Caller.  The return values are
// the import path of the caller and whether the import path could be
// determined.
func Caller(skip int) (string, bool) {
	return caller(skip + 1)
}

func caller(skip int) (string, bool) {
	// Ask the runtime about this function's callers. We first get the program
	// address of the caller (pc), and then we ask the runtime to resolve that
	// address to a function.
	//
	// The function could have an explicit name, or it could have a partially
	// generated name (if it's a closure). The format of named functions is
	// relatively straightforward, but the format of closures' generated names
	// changed with go1.5.
	//
	// A named function might look like this:
	// code.justin.tv/foo/bar/baz.TestPkgpathCaller
	//
	// A closure might look like this in go1.4:
	// code.justin.tv/foo/bar/baz.func·001
	//
	// Or it might look like this in go1.5:
	// code.justin.tv/foo/bar/baz.TestPkgpathCallerClosure.func1
	//
	// We'll first divide the function name at the last possible file path
	// separator, to get e.g. "code.justin.tv/foo/bar" and either
	// "/baz.TestPkgpathCaller", "/baz.func·001", or
	// "/baz.TestPkgpathCallerClosure.func1". We'll then split the second
	// portion at the first occurance of "." to get "/baz".
	//
	// Concatenating the two strings gives us the package name we seek:
	// "code.justin.tv/foo/bar/baz".

	const sep = "."
	const fileSeps = `/\`
	pc, _, _, ok := runtime.Caller(skip + 1)
	if !ok {
		return "", false
	}
	name := runtime.FuncForPC(pc).Name()
	start := ""
	if strings.ContainsAny(name, fileSeps) {
		start = name[:strings.LastIndexAny(name, fileSeps)]
	}
	end := name[len(start):]
	if strings.Contains(end, sep) {
		end = end[:strings.Index(end, sep)]
	}
	name = start + end

	const vendor = "/vendor/"
	if strings.Contains(name, vendor) {
		name = name[strings.LastIndex(name, vendor)+len(vendor):]
	}

	if name == "main" {
		return Main()
	}
	return name, true
}
