// Copyright 2019 CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
)

// Common flags
const (
	flagAll             flagName = "all"
	flagAllErrors       flagName = "all-errors"
	flagAllMajor        flagName = "all-major"
	flagAllVersions     flagName = "all-versions"
	flagCheck           flagName = "check"
	flagDeps            flagName = "deps"
	flagDiff            flagName = "diff"
	flagDryRun          flagName = "dry-run"
	flagEscape          flagName = "escape"
	flagExact           flagName = "exact"
	flagExpression      flagName = "expression"
	flagExt             flagName = "ext"
	flagFiles           flagName = "files"
	flagForce           flagName = "force"
	flagFrom            flagName = "from"
	flagGlob            flagName = "name"
	flagGroup           flagName = "group"
	flagIdent           flagName = "ident"
	flagIgnore          flagName = "ignore"
	flagInject          flagName = "inject"
	flagInjectVars      flagName = "inject-vars"
	flagInlineImports   flagName = "inline-imports"
	flagJSON            flagName = "json"
	flagLanguageVersion flagName = "language-version"
	flagList            flagName = "list"
	flagMerge           flagName = "merge"
	flagMod             flagName = "mod"
	flagNoDeps          flagName = "no-deps"
	flagOut             flagName = "out"
	flagOutFile         flagName = "outfile"
	flagPackage         flagName = "package"
	flagPath            flagName = "path"
	flagProtoEnum       flagName = "proto_enum"
	flagProtoPath       flagName = "proto_path"
	flagRecursive       flagName = "recursive"
	flagSchema          flagName = "schema"
	flagSimplify        flagName = "simplify"
	flagSource          flagName = "source"
	flagTo              flagName = "to"
	flagTrace           flagName = "trace"
	flagUpdateIdent     flagName = "update-ident"
	flagVerbose         flagName = "verbose"
	flagWithContext     flagName = "with-context"

	// Hidden flags.
	flagCpuProfile flagName = "cpuprofile"
	flagMemProfile flagName = "memprofile"
)

func addOutFlags(cmd *cobra.Command) {
	f := cmd.Flags()

	switch cmd.Name() {
	case "def", "eval", "export":
		f.String(string(flagOut), "",
			`output format (run 'cue help filetypes' for more info)`)
	}
	f.StringP(string(flagOutFile), "o", "",
		`filename or - for stdout with optional file prefix (run 'cue help filetypes' for more info)`)
	f.BoolP(string(flagForce), "f", false, "force overwriting existing files")
}

func addGlobalFlags(cmd *cobra.Command) {
	f := cmd.PersistentFlags()

	f.BoolP(string(flagSimplify), "s", false,
		"simplify output")
	f.BoolP(string(flagIgnore), "i", false,
		"proceed in the presence of errors")
	f.BoolP(string(flagAllErrors), "E", false, "print all available errors")

	f.String(string(flagCpuProfile), "", "write a CPU profile to the specified file before exiting")
	f.MarkHidden(string(flagCpuProfile))
	f.String(string(flagMemProfile), "", "write an allocation profile to the specified file before exiting")
	f.MarkHidden(string(flagMemProfile))
}

func addOrphanFlags(cmd *cobra.Command) {
	f := cmd.Flags()

	f.StringP(string(flagPackage), "p", "", "package name for non-CUE files")
	f.StringP(string(flagSchema), "d", "",
		"expression to select schema for evaluating values in non-CUE files")
	f.StringArrayP(string(flagPath), "l", nil, "CUE expression for single path component (see 'cue help flags' for details)")
	f.Bool(string(flagList), false, "concatenate multiple objects into a list")
	f.Bool(string(flagWithContext), false, "import as object with contextual data")
	f.StringArrayP(string(flagProtoPath), "I", nil, "paths in which to search for imports")
	f.String(string(flagProtoEnum), "int", "mode for rendering enums (int|json)")
	f.StringP(string(flagGlob), "n", "", "regexp filter for non-CUE file names in directories")
	f.Bool(string(flagMerge), true, "merge non-CUE files")
}

func addInjectionFlags(cmd *cobra.Command) {
	f := cmd.Flags()

	f.StringArrayP(string(flagInject), "t", nil,
		"set the value of a tagged field")
	autoInject := cmd.Name() == "cmd"
	f.BoolP(string(flagInjectVars), "T", autoInject,
		"inject system variables in tags")
}

type flagName string

// ensureAdded detects if a flag is being used without it first being
// added to the flagSet. Because flagNames are global, it is quite
// easy to accidentally use a flag in a command without adding it to
// the flagSet.
func (f flagName) ensureAdded(cmd *Command) {
	if cmd.Flags().Lookup(string(f)) == nil {
		panic(fmt.Sprintf("Cmd %q uses flag %q without adding it", cmd.Name(), f))
	}
}

// IsSet reports whether f was provided by the user, which is useful to tell
// `cue` apart from `cue --flag=` when the flag's default value is the zero value.
func (f flagName) IsSet(cmd *Command) bool {
	f.ensureAdded(cmd)
	found := false
	cmd.Flags().Visit(func(pf *pflag.Flag) {
		if pf.Name == string(f) {
			found = true
		}
	})
	return found
}

func (f flagName) Bool(cmd *Command) bool {
	f.ensureAdded(cmd)
	v, _ := cmd.Flags().GetBool(string(f))
	return v
}

func (f flagName) String(cmd *Command) string {
	f.ensureAdded(cmd)
	v, _ := cmd.Flags().GetString(string(f))
	return v
}

func (f flagName) StringArray(cmd *Command) []string {
	f.ensureAdded(cmd)
	v, _ := cmd.Flags().GetStringArray(string(f))
	return v
}
