package builder

import (
	"fmt"
	"go/build"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strconv"
	"strings"
)

const (
	flagSymlink = 1 << iota
)

type gocmd struct {
	ctxt *build.Context

	outPrefix string
	linkDir   string
	trim      string
}

// run will fork and exec a "go" process with a bunch of arguments. argStart
// should be something like []string{"go", "build"} or []string{"go", "test",
// "-c"}.
func (gc *gocmd) run(argStart []string, pkgName string, flags int) error {
	ctxt := gc.ctxt
	outPrefix := gc.outPrefix
	trim := gc.trim

	args := append([]string(nil), argStart...)

	if len(args) < 2 {
		return fmt.Errorf("need at least two starting arguments")
	}
	switch {
	case args[0] == "go":
		args[0] = filepath.Join(ctxt.GOROOT, "bin", "go")
	default:
		return fmt.Errorf("unknown command %q", args[0])
	}

	outfile := filepath.Join(outPrefix, pkgName+".a")

	args = append(args, "-i")
	args = append(args, "-p", strconv.Itoa(parallelCompileCount))
	args = append(args, "-o", outfile)
	if ctxt.Compiler != "" {
		args = append(args, "-compiler", ctxt.Compiler)
	}

	if ctxt.InstallSuffix != "" {
		args = append(args, "-installsuffix", ctxt.InstallSuffix)
	}
	if len(ctxt.BuildTags) != 0 {
		args = append(args, "-tags", strings.Join(ctxt.BuildTags, " "))
	}

	gcflags := []string{
		"-trimpath",
		trim,
	}
	if len(gcflags) != 0 {
		var quoted []string
		for _, gcflag := range gcflags {
			quoted = append(quoted, strconv.Quote(gcflag))
		}
		args = append(args, "-gcflags", strings.Join(quoted, " "))
	}

	// It would be best to separate the positional arguments from the named
	// arguments with two dashes. However, "go test" has a different
	// interpretation of that idiom and will break with go1.5. This may be
	// fixed in go1.6. For now, we'll make sure the package doesn't have a
	// confusing name.
	if strings.HasPrefix(pkgName, "-") {
		return fmt.Errorf("package name cannot start with a dash: %q", pkgName)
	}
	args = append(args, pkgName)

	goCmd := exec.Command(args[0], args[1:]...)
	goCmd.Env = []string{
		"GOARCH=" + ctxt.GOARCH,
		"GOOS=" + ctxt.GOOS,
		"GOROOT=" + ctxt.GOROOT,
		"GOPATH=" + ctxt.GOPATH,
		"CGO_ENABLED=" + map[bool]string{true: "1", false: "0"}[ctxt.CgoEnabled],
		"GO15VENDOREXPERIMENT=1",
		"GODEBUG=sbrk=1",
	}
	goCmd.Dir = "/" // The working directory somehow shows up in the compiled binary

	buf, err := goCmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("go error: %v\n%s", err, buf)
	}

	if flags&flagSymlink != 0 && gc.linkDir != "" {
		shortName := filepath.Join(gc.linkDir, path.Base(pkgName))
		rel, err := filepath.Rel(gc.linkDir, outfile)
		if err != nil {
			return fmt.Errorf("relative path calculation: %v", err)
		}
		err = os.Symlink(rel, shortName)
		if err != nil {
			if os.IsExist(err) {
				return fmt.Errorf("file exists: %v", shortName)
			}
			return fmt.Errorf("symlink: %v", err)
		}
	}

	return nil
}
