package testjson

import (
	"bytes"
	"go/build"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
)

// RelativePackagePath attempts to remove a common prefix from a package path.
// The common prefix is determined either by looking at the GOPATH or reading
// the package value from go.mod file.
// If the pkgpath does not match the common prefix it will be returned
// unmodified.
// If the pkgpath matches the common prefix exactly then '.' will be returned.
func RelativePackagePath(pkgpath string) string {
	if pkgpath == pkgPathPrefix {
		return "."
	}
	return strings.TrimPrefix(pkgpath, pkgPathPrefix+"/")
}

func getPkgPathPrefix() string {
	cwd, _ := os.Getwd()
	if isGoModuleEnabled() {
		prefix := getPkgPathPrefixFromGoModule(cwd)
		if prefix != "" {
			return prefix
		}
	}
	return getPkgPathPrefixGoPath(cwd)
}

func isGoModuleEnabled() bool {
	version := runtime.Version()
	if strings.HasPrefix(version, "go1.10") {
		return false
	}
	// Go modules may not be enabled if env var is unset, or set to auto, however
	// we can always fall back to using GOPATH as the prefix if a go.mod is not
	// found.
	return os.Getenv("GO111MODULE") != "off"
}

// TODO: might not work on windows
func getPkgPathPrefixGoPath(cwd string) string {
	gopaths := strings.Split(build.Default.GOPATH, string(filepath.ListSeparator))
	for _, gopath := range gopaths {
		gosrcpath := gopath + "/src/"
		if strings.HasPrefix(cwd, gosrcpath) {
			return strings.TrimPrefix(cwd, gosrcpath)
		}
	}
	return ""
}

func getPkgPathPrefixFromGoModule(cwd string) string {
	filename := goModuleFilePath(cwd)
	if filename == "" {
		return ""
	}
	raw, err := ioutil.ReadFile(filename)
	if err != nil {
		// TODO: log.Warn
		return ""
	}
	return pkgPathFromGoModuleFile(raw)
}

var (
	slashSlash = []byte("//")
	moduleStr  = []byte("module")
)

// Copy of ModulePath from golang.org/src/cmd/go/internal/modfile/read.go
func pkgPathFromGoModuleFile(mod []byte) string {
	for len(mod) > 0 {
		line := mod
		mod = nil
		if i := bytes.IndexByte(line, '\n'); i >= 0 {
			line, mod = line[:i], line[i+1:]
		}
		if i := bytes.Index(line, slashSlash); i >= 0 {
			line = line[:i]
		}
		line = bytes.TrimSpace(line)
		if !bytes.HasPrefix(line, moduleStr) {
			continue
		}
		line = line[len(moduleStr):]
		n := len(line)
		line = bytes.TrimSpace(line)
		if len(line) == n || len(line) == 0 {
			continue
		}

		if line[0] == '"' || line[0] == '`' {
			p, err := strconv.Unquote(string(line))
			if err != nil {
				return "" // malformed quoted string or multi-line module path
			}
			return p
		}

		return string(line)
	}
	return "" // missing module path
}

// A rough re-implementation of FindModuleRoot from
// golang.org/src/cmd/go/internal/modload/init.go
func goModuleFilePath(cwd string) string {
	dir := filepath.Clean(cwd)

	for {
		path := filepath.Join(dir, "go.mod")
		if _, err := os.Stat(path); err == nil {
			return path
		}
		parent := filepath.Dir(dir)
		if parent == dir {
			return ""
		}
		dir = parent
	}
}

var pkgPathPrefix = getPkgPathPrefix()
