package pkgpath

import (
	"path"
	"runtime"
)

const strMain = "main"

var (
	// mainPath is the import path of package main, or "main" if the import
	// path could not be determined.
	mainPath = strMain
)

// Main returns the import path of package main, the currently running
// command.  If the import path could not be determined, it returns "main" and
// false.
//
// It does not work on binaries compiled on filesystems that use a separator
// other than '/'.  It does not correctly detect the import path if a segment
// of the path is "src".
func Main() (string, bool) {
	return mainPath, (mainPath != strMain)
}

// 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 init() {
	// When using gc version go1.4.1:
	//
	// skip = 0 gives us this func, ending with something like
	// "pkgpath.init·1".
	//
	// skip = 1 gives us this package's primary init func, which calls the
	// individual "func init" bodies.
	//
	// Larger skip values take us up the import chain, eventually leading to
	// package main's primary init func, which calls the primary init funcs of
	// its imported packages.  Its name is "main.init".

	slashFilesystem := false

	var filename string
	for i := 0; ; i++ {
		pc, file, _, ok := runtime.Caller(i)
		if !ok {
			break
		}
		if i == 0 && path.IsAbs(file) {
			slashFilesystem = true
		}

		name := runtime.FuncForPC(pc).Name()
		if name == "main.init" {
			filename = file
			break
		}
	}

	if !slashFilesystem {
		// filename is a filepath from an unknown OS with an unknown path
		// separator.  We give up here since the path is probably weird.
		//
		// or empty
		// (if we didn't find main.init in the callstack).
		return
	}

	mainPath = fileImportPath(filename)
}

func fileImportPath(filename string) string {
	if !path.IsAbs(filename) {
		// This is a synthetic file path, as is generated by go test.
		return path.Dir(filename)
	}

	prefix, suffix := filename, ""
	var revparts, parts []string
	for {
		prefix, suffix = path.Split(prefix)
		prefix = path.Clean(prefix)

		if len(suffix) == 0 {
			// We got to the root without finding a "src" segment - maybe this
			// is "go run" of a file that's not in GOPATH?
			return strMain
		}
		if suffix == "src" {
			break
		}

		revparts = append(revparts, suffix)
	}

	for i := len(revparts) - 1; i >= 0; i-- {
		parts = append(parts, revparts[i])
	}
	return path.Dir(path.Join(parts...))
}
