// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package envcmd implements the ``go env'' command.
package envcmd

import (
	"encoding/json"
	"fmt"
	"os"
	"runtime"
	"strings"

	"cmd/go/internal/base"
	"cmd/go/internal/cfg"
	"cmd/go/internal/load"
	"cmd/go/internal/work"
)

var CmdEnv = &base.Command{
	UsageLine: "env [-json] [var ...]",
	Short:     "print Go environment information",
	Long: `
Env prints Go environment information.

By default env prints information as a shell script
(on Windows, a batch file). If one or more variable
names is given as arguments, env prints the value of
each named variable on its own line.

The -json flag prints the environment in JSON format
instead of as a shell script.
	`,
}

func init() {
	CmdEnv.Run = runEnv // break init cycle
}

var envJson = CmdEnv.Flag.Bool("json", false, "")

func MkEnv() []cfg.EnvVar {
	var b work.Builder
	b.Init()

	env := []cfg.EnvVar{
		{Name: "GOARCH", Value: cfg.Goarch},
		{Name: "GOBIN", Value: cfg.GOBIN},
		{Name: "GOEXE", Value: cfg.ExeSuffix},
		{Name: "GOHOSTARCH", Value: runtime.GOARCH},
		{Name: "GOHOSTOS", Value: runtime.GOOS},
		{Name: "GOOS", Value: cfg.Goos},
		{Name: "GOPATH", Value: cfg.BuildContext.GOPATH},
		{Name: "GORACE", Value: os.Getenv("GORACE")},
		{Name: "GOROOT", Value: cfg.GOROOT},
		{Name: "GOTOOLDIR", Value: base.ToolDir},

		// disable escape codes in clang errors
		{Name: "TERM", Value: "dumb"},
	}

	if work.GccgoBin != "" {
		env = append(env, cfg.EnvVar{Name: "GCCGO", Value: work.GccgoBin})
	} else {
		env = append(env, cfg.EnvVar{Name: "GCCGO", Value: work.GccgoName})
	}

	switch cfg.Goarch {
	case "arm":
		env = append(env, cfg.EnvVar{Name: "GOARM", Value: cfg.GOARM})
	case "386":
		env = append(env, cfg.EnvVar{Name: "GO386", Value: cfg.GO386})
	}

	cmd := b.GccCmd(".")
	env = append(env, cfg.EnvVar{Name: "CC", Value: cmd[0]})
	env = append(env, cfg.EnvVar{Name: "GOGCCFLAGS", Value: strings.Join(cmd[3:], " ")})
	cmd = b.GxxCmd(".")
	env = append(env, cfg.EnvVar{Name: "CXX", Value: cmd[0]})

	if cfg.BuildContext.CgoEnabled {
		env = append(env, cfg.EnvVar{Name: "CGO_ENABLED", Value: "1"})
	} else {
		env = append(env, cfg.EnvVar{Name: "CGO_ENABLED", Value: "0"})
	}

	return env
}

func findEnv(env []cfg.EnvVar, name string) string {
	for _, e := range env {
		if e.Name == name {
			return e.Value
		}
	}
	return ""
}

// ExtraEnvVars returns environment variables that should not leak into child processes.
func ExtraEnvVars() []cfg.EnvVar {
	var b work.Builder
	b.Init()
	cppflags, cflags, cxxflags, fflags, ldflags, err := b.CFlags(&load.Package{})
	if err != nil {
		// Should not happen - b.CFlags was given an empty package.
		fmt.Fprintf(os.Stderr, "go: invalid cflags: %v\n", err)
		return nil
	}
	return []cfg.EnvVar{
		{Name: "CGO_CFLAGS", Value: strings.Join(cflags, " ")},
		{Name: "CGO_CPPFLAGS", Value: strings.Join(cppflags, " ")},
		{Name: "CGO_CXXFLAGS", Value: strings.Join(cxxflags, " ")},
		{Name: "CGO_FFLAGS", Value: strings.Join(fflags, " ")},
		{Name: "CGO_LDFLAGS", Value: strings.Join(ldflags, " ")},
		{Name: "PKG_CONFIG", Value: b.PkgconfigCmd()},
	}
}

func runEnv(cmd *base.Command, args []string) {
	env := cfg.CmdEnv
	env = append(env, ExtraEnvVars()...)
	if len(args) > 0 {
		if *envJson {
			var es []cfg.EnvVar
			for _, name := range args {
				e := cfg.EnvVar{Name: name, Value: findEnv(env, name)}
				es = append(es, e)
			}
			printEnvAsJSON(es)
		} else {
			for _, name := range args {
				fmt.Printf("%s\n", findEnv(env, name))
			}
		}
		return
	}

	if *envJson {
		printEnvAsJSON(env)
		return
	}

	for _, e := range env {
		if e.Name != "TERM" {
			switch runtime.GOOS {
			default:
				fmt.Printf("%s=\"%s\"\n", e.Name, e.Value)
			case "plan9":
				if strings.IndexByte(e.Value, '\x00') < 0 {
					fmt.Printf("%s='%s'\n", e.Name, strings.Replace(e.Value, "'", "''", -1))
				} else {
					v := strings.Split(e.Value, "\x00")
					fmt.Printf("%s=(", e.Name)
					for x, s := range v {
						if x > 0 {
							fmt.Printf(" ")
						}
						fmt.Printf("%s", s)
					}
					fmt.Printf(")\n")
				}
			case "windows":
				fmt.Printf("set %s=%s\n", e.Name, e.Value)
			}
		}
	}
}

func printEnvAsJSON(env []cfg.EnvVar) {
	m := make(map[string]string)
	for _, e := range env {
		if e.Name == "TERM" {
			continue
		}
		m[e.Name] = e.Value
	}
	enc := json.NewEncoder(os.Stdout)
	enc.SetIndent("", "\t")
	if err := enc.Encode(m); err != nil {
		base.Fatalf("%s", err)
	}
}
