/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 commands

import (
	"bufio"
	"errors"
	"fmt"
	"strings"

	"github.com/apache/openwhisk-cli/wski18n"
	"github.com/apache/openwhisk-client-go/whisk"

	"github.com/fatih/color"
	"github.com/mattn/go-colorable"

	//prettyjson "github.com/hokaccha/go-prettyjson"  // See prettyjson comment below
	"archive/tar"
	"archive/zip"
	"bytes"
	"compress/gzip"
	"encoding/json"
	"io"
	"io/ioutil"
	"os"
	"reflect"
	"regexp"
	"sort"
)

func csvToQualifiedActions(artifacts string) []string {
	var res []string
	actions := strings.Split(artifacts, ",")
	for i := 0; i < len(actions); i++ {
		res = append(res, getQualifiedName(actions[i], Properties.Namespace))
	}

	return res
}

/**
 * Processes command line to retrieve pairs of key-value pairs, where the value must be valid JSON.
 *
 * Parameters and annotations are handled the same way. The flag here is only for generating an error messages
 * specific to one or the other.
 *
 * NOTE: this function will exit in case of a processing error since it indicates a problem parsing parameters.
 *
 * @return either an array or a JSON object (map) formatted representation of the key-value pairs.
 */
func getParameters(params []string, keyValueFormat bool, annotation bool) interface{} {
	var parameters interface{}
	var err error

	if !annotation {
		whisk.Debug(whisk.DbgInfo, "Parsing parameters: %#v\n", params)
	} else {
		whisk.Debug(whisk.DbgInfo, "Parsing annotations: %#v\n", params)
	}

	parameters, err = getJSONFromStrings(params, keyValueFormat)
	if err != nil {
		whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, %t) failed: %s\n", params, keyValueFormat, err)
		var errStr string

		if !annotation {
			errStr = wski18n.T("Invalid parameter argument '{{.param}}': {{.err}}",
				map[string]interface{}{"param": fmt.Sprintf("%#v", params), "err": err})
		} else {
			errStr = wski18n.T("Invalid annotation argument '{{.annotation}}': {{.err}}",
				map[string]interface{}{"annotation": fmt.Sprintf("%#v", params), "err": err})
		}
		werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
		ExitOnError(werr)
	}

	return parameters
}

func getJSONFromStrings(content []string, keyValueFormat bool) (interface{}, error) {
	var data map[string]interface{}
	var res interface{}

	whisk.Debug(whisk.DbgInfo, "Convert content to JSON: %#v\n", content)

	for i := 0; i < len(content); i++ {
		dc := json.NewDecoder(strings.NewReader(content[i]))
		dc.UseNumber()
		if err := dc.Decode(&data); err != nil {
			whisk.Debug(whisk.DbgError, "Invalid JSON detected for '%s' \n", content[i])
			return whisk.KeyValueArr{}, err
		}

		whisk.Debug(whisk.DbgInfo, "Created map '%v' from '%v'\n", data, content[i])
	}

	if data == nil {
		data = make(map[string]interface{})
	}

	if keyValueFormat {
		res = getKeyValueFormattedJSON(data)
	} else {
		res = data
	}

	return res, nil
}

func getKeyValueFormattedJSON(data map[string]interface{}) whisk.KeyValueArr {
	var keyValueArr whisk.KeyValueArr

	for key, value := range data {
		keyValue := whisk.KeyValue{
			Key:   key,
			Value: value,
		}
		keyValueArr = append(keyValueArr, keyValue)
	}

	whisk.Debug(whisk.DbgInfo, "Created key/value format '%v' from '%v'\n", keyValueArr, data)

	return keyValueArr
}

func getFormattedJSON(key string, value string) string {
	var res string

	key = getEscapedJSON(key)

	if isValidJSON(value) {
		whisk.Debug(whisk.DbgInfo, "Value '%s' is valid JSON.\n", value)
		res = fmt.Sprintf("{\"%s\": %s}", key, value)
	} else {
		whisk.Debug(whisk.DbgInfo, "Converting value '%s' to a string as it is not valid JSON.\n", value)
		res = fmt.Sprintf("{\"%s\": \"%s\"}", key, value)
	}

	whisk.Debug(whisk.DbgInfo, "Formatted JSON '%s'\n", res)

	return res
}

func getEscapedJSON(value string) string {
	value = strings.Replace(value, "\\", "\\\\", -1)
	value = strings.Replace(value, "\"", "\\\"", -1)

	return value
}

func isValidJSON(value string) bool {
	var jsonInterface interface{}
	err := json.Unmarshal([]byte(value), &jsonInterface)

	return err == nil
}

var boldString = color.New(color.Bold).SprintFunc()

type Sortables []whisk.Sortable

// Uses quickSort to sort commands based on their compare methods
// Param: Takes in a array of Sortable interfaces which contains a specific command
func Swap(sortables Sortables, i, j int) { sortables[i], sortables[j] = sortables[j], sortables[i] }

func toPrintable(sortable []whisk.Sortable) []whisk.Printable {
	sortedPrintable := make([]whisk.Printable, len(sortable), len(sortable))
	for i := range sortable {
		sortedPrintable[i] = sortable[i].(whisk.Printable)
	}
	return sortedPrintable
}

// Prints the parameters/list for wsk xxxx list
// Identifies type and then copies array into an array of interfaces(Sortable) to be sorted and printed
// Param: Takes in an interface which contains an array of a command(Ex: []Action)
func printList(collection interface{}, sortByName bool) {
	var commandToSort []whisk.Sortable
	switch collection := collection.(type) {
	case []whisk.Action:
		for i := range collection {
			commandToSort = append(commandToSort, collection[i])
		}
	case []whisk.Trigger:
		for i := range collection {
			commandToSort = append(commandToSort, collection[i])
		}
	case []whisk.Package:
		for i := range collection {
			commandToSort = append(commandToSort, collection[i])
		}
	case []whisk.Rule:
		for i := range collection {
			commandToSort = append(commandToSort, collection[i])
		}
	case []whisk.Namespace:
		for i := range collection {
			commandToSort = append(commandToSort, collection[i])
		}
	case []whisk.ActivationFilteredRow:
		for i := range collection {
			commandToSort = append(commandToSort, collection[i])
		}
	case []whisk.ApiFilteredList:
		for i := range collection {
			commandToSort = append(commandToSort, collection[i])
		}
	case []whisk.ApiFilteredRow:
		for i := range collection {
			commandToSort = append(commandToSort, collection[i])
		}
	}
	if sortByName && len(commandToSort) > 0 {
		quickSort(commandToSort, 0, len(commandToSort)-1)
	}
	printCommandsList(toPrintable(commandToSort), makeDefaultHeader(collection))
}

func quickSort(toSort Sortables, left int, right int) {
	low := left
	high := right
	pivot := toSort[(left+right)/2]

	for low <= high {
		for toSort[low].Compare(pivot) {
			low++
		}
		for pivot.Compare(toSort[high]) {
			high--
		}
		if low <= high {
			Swap(toSort, low, high)
			low++
			high--
		}
	}
	if left < high {
		quickSort(toSort, left, high)
	}
	if low < right {
		quickSort(toSort, low, right)
	}
}

// makeDefaultHeader(collection) returns the default header to be used in case
//      the list to be printed is empty.
func makeDefaultHeader(collection interface{}) string {
	defaultHeader := reflect.TypeOf(collection).String()
	defaultHeader = strings.ToLower(defaultHeader[8:] + "s") // Removes '[]whisk.' from `[]whisk.ENTITY_TYPE`
	if defaultHeader == "apifilteredrows" {
		defaultHeader = fmt.Sprintf("%-30s %7s %20s  %s", "Action", "Verb", "API Name", "URL")
	} else if defaultHeader == "apifilteredlists" {
		defaultHeader = ""
	} else if defaultHeader == "activationfilteredrows" {
		defaultHeader = ""
	}
	return defaultHeader
}

func stripTimestamp(log string) (strippedLog string) {
	// parses out the timestamp if it exists first
	// the timestamp expected format is YYYY-MM-DDTHH:MM:SS.[0-9]+Z
	// an optional " stdout" or " stderr" stream identifier
	// and the rest as the log line
	regex := regexp.MustCompile("\\d{4}-[01]{1}\\d{1}-[0-3]{1}\\d{1}T[0-2]{1}\\d{1}:[0-6]{1}\\d{1}:[0-6]{1}\\d{1}.\\d+Z( *(stdout|stderr):)?\\s(.*)")
	match := regex.FindStringSubmatch(log)

	if len(match) > 3 && len(match[3]) > 0 {
		strippedLog = match[3]
	} else {
		strippedLog = log
	}

	return strippedLog
}

func printFullList(collection interface{}) {
	switch collection := collection.(type) {
	case []whisk.Action:

	case []whisk.Trigger:

	case []whisk.Package:

	case []whisk.Rule:

	case []whisk.Namespace:

	case []whisk.Activation:
		printFullActivationList(collection)
	}
}

func printSummary(collection interface{}) {
	switch collection := collection.(type) {
	case *whisk.Action:
		printActionSummary(collection)
	case *whisk.Trigger:
		printTriggerSummary(collection)
	case *whisk.Package:
		printPackageSummary(collection)
	case *whisk.Rule:

	case *whisk.Namespace:

	case *whisk.Activation:
	}
}

// Used to print Action, Trigger, Package, and Rule lists
// Param: Takes in a array of Printable interface, and the name of the command
//          being sent to it
// **Note**: The name should be an empty string for APIs.
func printCommandsList(commands []whisk.Printable, defaultHeader string) {
	if len(commands) != 0 {
		fmt.Fprint(color.Output, boldString(commands[0].ToHeaderString()))
		for i := range commands {
			fmt.Print(commands[i].ToSummaryRowString())
		}
	} else {
		fmt.Fprintf(color.Output, "%s\n", boldString(defaultHeader))
	}
}

func printFullActivationList(activations []whisk.Activation) {
	fmt.Fprintf(color.Output, "%s\n", boldString("activations"))
	for _, activation := range activations {
		printJSON(activation)
	}
}

func printStrippedActivationLogs(logs []string) {
	for _, log := range logs {
		fmt.Printf("%s\n", stripTimestamp(log))
	}
}

func printActivationLogs(logs []string) {
	for _, log := range logs {
		fmt.Printf("%s\n", log)
	}
}

func printArrayContents(arrStr []string) {
	for _, str := range arrStr {
		fmt.Printf("%s\n", str)
	}
}

func printPackageSummary(pkg *whisk.Package) {
	printEntitySummary(fmt.Sprintf("%7s", "package"), getFullName(pkg.Namespace, pkg.Name, ""),
		getValueString(pkg.Annotations, "description"),
		strings.Join(getParamUnion(pkg.Annotations, pkg.Parameters, "name"), ", "))

	if pkg.Actions != nil {
		for _, action := range pkg.Actions {
			paramUnion := getParamUnion(action.Annotations, action.Parameters, "name")
			printEntitySummary(fmt.Sprintf("%7s", "action"), getFullName(pkg.Namespace, pkg.Name, action.Name),
				getValueString(action.Annotations, "description"),
				strings.Join(paramUnion, ", "))
		}
	}

	if pkg.Feeds != nil {
		for _, feed := range pkg.Feeds {
			printEntitySummary(fmt.Sprintf("%7s", "feed  "), getFullName(pkg.Namespace, pkg.Name, feed.Name),
				getValueString(feed.Annotations, "description"),
				strings.Join(getParamUnion(feed.Annotations, feed.Parameters, "name"), ", "))
		}
	}
}

func printActionSummary(action *whisk.Action) {
	paramUnion := getParamUnion(action.Annotations, action.Parameters, "name")
	printEntitySummary(fmt.Sprintf("%6s", "action"),
		getFullName(action.Namespace, "", action.Name),
		getValueString(action.Annotations, "description"),
		strings.Join(paramUnion, ", "))
}

func printTriggerSummary(trigger *whisk.Trigger) {
	printEntitySummary(fmt.Sprintf("%7s", "trigger"),
		getFullName(trigger.Namespace, "", trigger.Name),
		getValueString(trigger.Annotations, "description"),
		strings.Join(getParamUnion(trigger.Annotations, trigger.Parameters, "name"), ", "))
}

func printRuleSummary(rule *whisk.Rule) {
	fmt.Fprintf(color.Output, "%s %s\n", boldString(fmt.Sprintf("%4s", "rule")),
		getFullName(rule.Namespace, "", rule.Name))
	fmt.Fprintf(color.Output, "   (%s: %s)\n", boldString(wski18n.T("status")), rule.Status)
}

func printEntitySummary(entityType string, fullName string, description string, params string) {
	emptyParams := "none defined"
	if len(params) <= 0 {
		params = emptyParams
	}
	if len(description) > 0 {
		fmt.Fprintf(color.Output, "%s %s: %s\n", boldString(entityType), fullName, description)
	} else if params != emptyParams {
		descriptionFromParams := buildParamDescription(params)
		fmt.Fprintf(color.Output, "%s %s: %s\n", boldString(entityType), fullName, descriptionFromParams)
	} else {
		fmt.Fprintf(color.Output, "%s %s\n", boldString(entityType), fullName)
	}
	fmt.Fprintf(color.Output, "   (%s: %s)\n", boldString(wski18n.T("parameters")), params)
}

//  getParamUnion(keyValArrAnnots, keyValArrParams, key) returns the union
//      of parameters listed under annotations (keyValArrAnnots, using key) and
//      bound parameters (keyValArrParams). Bound parameters will be denoted with
//      a prefixed "*", and finalized bound parameters (can't be changed by
//      user) will be denoted by a prefixed "**".
func getParamUnion(keyValArrAnnots whisk.KeyValueArr, keyValArrParams whisk.KeyValueArr, key string) []string {
	var res []string
	tag := "*"
	if getValueBool(keyValArrAnnots, "final") {
		tag = "**"
	}
	boundParams := getKeys(keyValArrParams)
	annotatedParams := getChildValueStrings(keyValArrAnnots, "parameters", key)
	res = append(boundParams, annotatedParams...) // Create union of boundParams and annotatedParams with duplication
	for i := 0; i < len(res); i++ {
		for j := i + 1; j < len(res); j++ {
			if res[i] == res[j] {
				res = append(res[:j], res[j+1:]...) // Remove duplicate entry
			}
		}
	}
	sort.Strings(res)
	res = tagBoundParams(boundParams, res, tag)
	return res
}

//  tagBoundParams(boundParams, paramUnion, tag) returns the list paramUnion with
//      all strings listed under boundParams set with a prefix tag.
func tagBoundParams(boundParams []string, paramUnion []string, tag string) []string {
	res := paramUnion
	for i := 0; i < len(boundParams); i++ {
		for j := 0; j < len(res); j++ {
			if boundParams[i] == res[j] {
				res[j] = fmt.Sprintf("%s%s", tag, res[j])
			}
		}
	}
	return res
}

//  buildParamDescription(params) returns a default entity description for
//      `$ wsk [ENTITY] get [ENTITY_NAME] --summary` when parameters are defined,
//      but the entity description under annotations is not.
func buildParamDescription(params string) string {
	preamble := "Returns a result based on parameter"
	params = strings.Replace(params, "*", "", -1)
	temp := strings.Split(params, ",")
	if len(temp) > 1 {
		lastParam := temp[len(temp)-1]
		newParams := strings.Replace(params, fmt.Sprintf(",%s", lastParam), fmt.Sprintf(" and%s", lastParam), 1)
		return fmt.Sprintf("%ss %s", preamble, newParams)
	}
	return fmt.Sprintf("%s %s", preamble, params)
}

func getFullName(namespace string, packageName string, entityName string) string {
	var fullName string

	if len(namespace) > 0 && len(packageName) > 0 && len(entityName) > 0 {
		fullName = fmt.Sprintf("/%s/%s/%s", namespace, packageName, entityName)
	} else if len(namespace) > 0 && len(packageName) > 0 {
		fullName = fmt.Sprintf("/%s/%s", namespace, packageName)
	} else if len(namespace) > 0 && len(entityName) > 0 {
		fullName = fmt.Sprintf("/%s/%s", namespace, entityName)
	} else if len(namespace) > 0 {
		fullName = fmt.Sprintf("/%s", namespace)
	}

	return fullName
}

func deleteKey(key string, keyValueArr whisk.KeyValueArr) whisk.KeyValueArr {
	for i := 0; i < len(keyValueArr); i++ {
		if keyValueArr[i].Key == key {
			keyValueArr = append(keyValueArr[:i], keyValueArr[i+1:]...)
			break
		}
	}

	return keyValueArr
}

func addKeyValue(key string, value interface{}, keyValueArr whisk.KeyValueArr) whisk.KeyValueArr {
	keyValue := whisk.KeyValue{
		Key:   key,
		Value: value,
	}

	return append(keyValueArr, keyValue)
}

func getKeys(keyValueArr whisk.KeyValueArr) []string {
	var res []string

	for i := 0; i < len(keyValueArr); i++ {
		res = append(res, keyValueArr[i].Key)
	}

	sort.Strings(res)
	whisk.Debug(whisk.DbgInfo, "Got keys '%v' from '%v'\n", res, keyValueArr)

	return res
}

func getValueString(keyValueArr whisk.KeyValueArr, key string) string {
	var value interface{}
	var res string

	value = keyValueArr.GetValue(key)
	castedValue, canCast := value.(string)

	if canCast {
		res = castedValue
	}

	whisk.Debug(whisk.DbgInfo, "Got string value '%v' for key '%s'\n", res, key)

	return res
}

func getValueBool(keyValueArr whisk.KeyValueArr, key string) bool {
	var value interface{}
	var res bool

	value = keyValueArr.GetValue(key)
	castedValue, canCast := value.(bool)

	if canCast {
		res = castedValue
	}

	whisk.Debug(whisk.DbgInfo, "Got bool value '%v' for key '%s'\n", res, key)

	return res
}

func getChildValues(keyValueArr whisk.KeyValueArr, key string, childKey string) []interface{} {
	var value interface{}
	var res []interface{}

	value = keyValueArr.GetValue(key)

	castedValue, canCast := value.([]interface{})
	if canCast {
		for i := 0; i < len(castedValue); i++ {
			castedValue, canCast := castedValue[i].(map[string]interface{})
			if canCast {
				for subKey, subValue := range castedValue {
					if subKey == childKey {
						res = append(res, subValue)
					}
				}
			}
		}
	}

	whisk.Debug(whisk.DbgInfo, "Got values '%s' from '%v' for key '%s' and child key '%s'\n", res, keyValueArr, key,
		childKey)

	return res
}

func getChildValueStrings(keyValueArr whisk.KeyValueArr, key string, childKey string) []string {
	var keys []interface{}
	var res []string

	keys = getChildValues(keyValueArr, key, childKey)

	for i := 0; i < len(keys); i++ {
		castedValue, canCast := keys[i].(string)
		if canCast {
			res = append(res, castedValue)
		}
	}

	sort.Strings(res)
	whisk.Debug(whisk.DbgInfo, "Got values '%s' from '%v' for key '%s' and child key '%s'\n", res, keyValueArr, key,
		childKey)

	return res
}

func getValueFromResponse(field string, response interface{}) interface{} {
	if result, ok := response.(map[string]interface{}); ok {
		for key, value := range result {
			if key == field {
				return value
			}
		}
	}
	if result, ok := response.([]interface{}); ok {
		return result
	} else {
		return ""
	}
}

func logoText() string {
	logo := `
        ____      ___                   _    _ _     _     _
       /\   \    / _ \ _ __   ___ _ __ | |  | | |__ (_)___| | __
  /\  /__\   \  | | | | '_ \ / _ \ '_ \| |  | | '_ \| / __| |/ /
 /  \____ \  /  | |_| | |_) |  __/ | | | |/\| | | | | \__ \   <
 \   \  /  \/    \___/| .__/ \___|_| |_|__/\__|_| |_|_|___/_|\_\
  \___\/ tm           |_|
`

	return logo
}

func printJSON(v interface{}, stream ...io.Writer) {
	// Can't use prettyjson util issue  https://github.com/hokaccha/go-prettyjson/issues/1 is fixed
	//output, _ := prettyjson.Marshal(v)
	//
	//if len(stream) > 0 {
	//    fmt.Fprintf(stream[0], string(output))
	//} else {
	//    fmt.Fprintf(color.Output, string(output))
	//}
	printJsonNoColor(v, stream...)
}

func printJsonNoColor(decoded interface{}, stream ...io.Writer) {
	var output bytes.Buffer

	buffer := new(bytes.Buffer)
	encoder := json.NewEncoder(buffer)
	encoder.SetEscapeHTML(false)
	encoder.Encode(&decoded)
	json.Indent(&output, buffer.Bytes(), "", "    ")

	if len(stream) > 0 {
		fmt.Fprintf(stream[0], "%s", string(output.Bytes()))
	} else {
		fmt.Fprintf(os.Stdout, "%s", string(output.Bytes()))
	}
}

func unpackGzip(inpath string, outpath string) error {
	var exists bool
	var err error

	exists, err = FileExists(outpath)

	if err != nil {
		return err
	}

	if exists {
		errStr := wski18n.T("The file '{{.name}}' already exists.  Delete it and retry.",
			map[string]interface{}{"name": outpath})
		werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
		return werr
	}

	exists, err = FileExists(inpath)

	if err != nil {
		return err
	}

	if !exists {
		errMsg := wski18n.T("File '{{.name}}' is not a valid file or it does not exist",
			map[string]interface{}{
				"name": inpath,
			})
		whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_USAGE,
			whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)

		return whiskErr
	}

	unGzFile, err := os.Create(outpath)
	if err != nil {
		whisk.Debug(whisk.DbgError, "os.Create(%s) failed: %s\n", outpath, err)
		errStr := wski18n.T("Error creating unGzip file '{{.name}}': {{.err}}",
			map[string]interface{}{"name": outpath, "err": err})
		werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
		return werr
	}
	defer unGzFile.Close()

	gzFile, err := os.Open(inpath)
	if err != nil {
		whisk.Debug(whisk.DbgError, "os.Open(%s) failed: %s\n", inpath, err)
		errStr := wski18n.T("Error opening Gzip file '{{.name}}': {{.err}}",
			map[string]interface{}{"name": inpath, "err": err})
		werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
		return werr
	}
	defer gzFile.Close()

	gzReader, err := gzip.NewReader(gzFile)
	if err != nil {
		whisk.Debug(whisk.DbgError, "gzip.NewReader() failed: %s\n", err)
		errStr := wski18n.T("Unable to unzip file '{{.name}}': {{.err}}",
			map[string]interface{}{"name": inpath, "err": err})
		werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
		return werr
	}

	_, err = io.Copy(unGzFile, gzReader)
	if err != nil {
		whisk.Debug(whisk.DbgError, "io.Copy() failed: %s\n", err)
		errStr := wski18n.T("Unable to unzip file '{{.name}}': {{.err}}",
			map[string]interface{}{"name": inpath, "err": err})
		werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
		return werr
	}

	return nil
}

func unpackZip(inpath string) error {
	exists, err := FileExists(inpath)

	if err != nil {
		return err
	}

	if !exists {
		errMsg := wski18n.T("File '{{.name}}' is not a valid file or it does not exist",
			map[string]interface{}{
				"name": inpath,
			})
		whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_USAGE,
			whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)

		return whiskErr
	}
	zipFileReader, err := zip.OpenReader(inpath)
	if err != nil {
		whisk.Debug(whisk.DbgError, "zip.OpenReader(%s) failed: %s\n", inpath, err)
		errStr := wski18n.T("Unable to opens '{{.name}}' for unzipping: {{.err}}",
			map[string]interface{}{"name": inpath, "err": err})
		werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
		return werr
	}
	defer zipFileReader.Close()

	// Loop through the files in the zipfile
	for _, item := range zipFileReader.File {
		itemName := item.Name
		itemType := item.Mode()

		whisk.Debug(whisk.DbgInfo, "file item - %#v\n", item)

		if itemType.IsDir() {
			if err := os.MkdirAll(item.Name, item.Mode()); err != nil {
				whisk.Debug(whisk.DbgError, "os.MkdirAll(%s, %d) failed: %s\n", item.Name, item.Mode(), err)
				errStr := wski18n.T("Unable to create directory '{{.dir}}' while unzipping '{{.name}}': {{.err}}",
					map[string]interface{}{"dir": item.Name, "name": inpath, "err": err})
				werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
				return werr
			}
		}

		if itemType.IsRegular() {
			unzipFile, err := item.Open()
			defer unzipFile.Close()
			if err != nil {
				whisk.Debug(whisk.DbgError, "'%s' Open() failed: %s\n", item.Name, err)
				errStr := wski18n.T("Unable to open zipped file '{{.file}}' while unzipping '{{.name}}': {{.err}}",
					map[string]interface{}{"file": item.Name, "name": inpath, "err": err})
				werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
				return werr
			}
			targetFile, err := os.Create(itemName)
			if err != nil {
				whisk.Debug(whisk.DbgError, "os.Create(%s) failed: %s\n", itemName, err)
				errStr := wski18n.T("Unable to create file '{{.file}}' while unzipping '{{.name}}': {{.err}}",
					map[string]interface{}{"file": item.Name, "name": inpath, "err": err})
				werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
				return werr
			}
			if _, err := io.Copy(targetFile, unzipFile); err != nil {
				whisk.Debug(whisk.DbgError, "io.Copy() of '%s' failed: %s\n", itemName, err)
				errStr := wski18n.T("Unable to unzip file '{{.name}}': {{.err}}",
					map[string]interface{}{"name": itemName, "err": err})
				werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
				return werr
			}
		}
	}

	return nil
}

func unpackTar(inpath string) error {
	exists, err := FileExists(inpath)

	if err != nil {
		return err
	}

	if !exists {
		errMsg := wski18n.T("File '{{.name}}' is not a valid file or it does not exist",
			map[string]interface{}{
				"name": inpath,
			})
		whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_USAGE,
			whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)

		return whiskErr
	}

	tarFileReader, err := os.Open(inpath)
	if err != nil {
		whisk.Debug(whisk.DbgError, "os.Open(%s) failed: %s\n", inpath, err)
		errStr := wski18n.T("Error opening tar file '{{.name}}': {{.err}}",
			map[string]interface{}{"name": inpath, "err": err})
		werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
		return werr
	}
	defer tarFileReader.Close()

	// Loop through the files in the tarfile
	tReader := tar.NewReader(tarFileReader)
	for {
		item, err := tReader.Next()
		if err == io.EOF {
			whisk.Debug(whisk.DbgError, "EOF reach during untar\n")
			break // end of tar
		}
		if err != nil {
			whisk.Debug(whisk.DbgError, "tReader.Next() failed: %s\n", err)
			errStr := wski18n.T("Error reading tar file '{{.name}}': {{.err}}",
				map[string]interface{}{"name": inpath, "err": err})
			werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
			return werr
		}

		whisk.Debug(whisk.DbgInfo, "tar file item - %#v\n", item)
		switch item.Typeflag {
		case tar.TypeDir:
			if err := os.MkdirAll(item.Name, os.FileMode(item.Mode)); err != nil {
				whisk.Debug(whisk.DbgError, "os.MkdirAll(%s, %d) failed: %s\n", item.Name, item.Mode, err)
				errStr := wski18n.T("Unable to create directory '{{.dir}}' while untarring '{{.name}}': {{.err}}",
					map[string]interface{}{"dir": item.Name, "name": inpath, "err": err})
				werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
				return werr
			}
		case tar.TypeReg:
			untarFile, err := os.OpenFile(item.Name, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(item.Mode))
			defer untarFile.Close()
			if err != nil {
				whisk.Debug(whisk.DbgError, "os.Create(%s) failed: %s\n", item.Name, err)
				errStr := wski18n.T("Unable to create file '{{.file}}' while untarring '{{.name}}': {{.err}}",
					map[string]interface{}{"file": item.Name, "name": inpath, "err": err})
				werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
				return werr
			}
			if _, err := io.Copy(untarFile, tReader); err != nil {
				whisk.Debug(whisk.DbgError, "io.Copy() of '%s' failed: %s\n", item.Name, err)
				errStr := wski18n.T("Unable to untar file '{{.name}}': {{.err}}",
					map[string]interface{}{"name": item.Name, "err": err})
				werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
				return werr
			}
		default:
			whisk.Debug(whisk.DbgError, "Unexpected tar file type of %q\n", item.Typeflag)
			errStr := wski18n.T("Unable to untar '{{.name}}' due to unexpected tar file type\n",
				map[string]interface{}{"name": item.Name})
			werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
			return werr
		}
	}
	return nil
}

func CheckArgs(args []string, minimumArgNumber int, maximumArgNumber int, commandName string,
	requiredArgMsg string) *whisk.WskError {
	exactlyOrAtLeast := wski18n.T("exactly")
	exactlyOrNoMoreThan := wski18n.T("exactly")

	if minimumArgNumber != maximumArgNumber {
		exactlyOrAtLeast = wski18n.T("at least")
		exactlyOrNoMoreThan = wski18n.T("no more than")
	}

	if len(args) < minimumArgNumber {
		whisk.Debug(whisk.DbgError, fmt.Sprintf("%s command must have %s %d argument(s)\n", commandName,
			exactlyOrAtLeast, minimumArgNumber))
		errMsg := wski18n.T("Invalid argument(s). {{.required}}", map[string]interface{}{"required": requiredArgMsg})
		whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
			whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
		return whiskErr
	} else if len(args) > maximumArgNumber {
		whisk.Debug(whisk.DbgError, fmt.Sprintf("%s command must have %s %d argument(s)\n", commandName,
			exactlyOrNoMoreThan, maximumArgNumber))
		errMsg := wski18n.T("Invalid argument(s): {{.args}}. {{.required}}",
			map[string]interface{}{"args": strings.Join(args[maximumArgNumber:], ", "), "required": requiredArgMsg})
		whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
			whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
		return whiskErr
	} else {
		return nil
	}
}

func normalizeNamespace(namespace string) string {
	if namespace == "_" {
		namespace = wski18n.T("default")
	}

	return namespace
}

func getClientNamespace() string {
	return normalizeNamespace(Client.Config.Namespace)
}

func ReadFile(filename string) (string, error) {
	exists, err := FileExists(filename)

	if err != nil {
		return "", err
	}

	if !exists {
		errMsg := wski18n.T("File '{{.name}}' is not a valid file or it does not exist",
			map[string]interface{}{
				"name": filename,
			})
		whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_USAGE,
			whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)

		return "", whiskErr
	}

	file, err := ioutil.ReadFile(filename)
	if err != nil {
		whisk.Debug(whisk.DbgError, "os.ioutil.ReadFile(%s) error: %s\n", filename, err)
		errMsg := wski18n.T("Unable to read the file '{{.name}}': {{.err}}",
			map[string]interface{}{"name": filename, "err": err})
		whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_GENERAL,
			whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
		return "", whiskErr
	}

	return string(file), nil
}

func writeFile(filename string, content string) error {
	file, err := os.Create(filename)

	if err != nil {
		whisk.Debug(whisk.DbgError, "os.Create(%s) error: %#v\n", filename, err)
		errMsg := wski18n.T("Cannot create file '{{.name}}': {{.err}}",
			map[string]interface{}{"name": filename, "err": err})
		whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_USAGE, whisk.DISPLAY_MSG,
			whisk.DISPLAY_USAGE)
		return whiskErr
	}

	defer file.Close()

	if _, err = file.WriteString(content); err != nil {
		whisk.Debug(whisk.DbgError, "File.WriteString(%s) error: %#v\n", content, err)
		errMsg := wski18n.T("Cannot create file '{{.name}}': {{.err}}",
			map[string]interface{}{"name": filename, "err": err})
		whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_USAGE, whisk.DISPLAY_MSG,
			whisk.DISPLAY_USAGE)
		return whiskErr
	}

	return nil
}

func FileExists(file string) (bool, error) {
	_, err := os.Stat(file)

	if err != nil {
		if os.IsNotExist(err) == true {
			return false, nil
		} else {
			whisk.Debug(whisk.DbgError, "os.Stat(%s) error: %#v\n", file, err)
			errMsg := wski18n.T("Cannot access file '{{.name}}': {{.err}}",
				map[string]interface{}{"name": file, "err": err})
			whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_USAGE,
				whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
			return true, whiskErr
		}
	}

	return true, nil
}

func fieldExists(value interface{}, field string) bool {
	element := reflect.ValueOf(value).Elem()

	for i := 0; i < element.NumField(); i++ {
		if strings.ToLower(element.Type().Field(i).Name) == strings.ToLower(field) {
			return true
		}
	}

	return false
}

func printField(value interface{}, field string) {
	var matchFunc = func(structField string) bool {
		return strings.ToLower(structField) == strings.ToLower(field)
	}

	structValue := reflect.ValueOf(value)
	fieldValue := reflect.Indirect(structValue).FieldByNameFunc(matchFunc)

	printJSON(fieldValue.Interface())
}

func parseShared(shared string) (bool, bool, error) {
	var isShared, isSet bool

	if strings.ToLower(shared) == "yes" {
		isShared = true
		isSet = true
	} else if strings.ToLower(shared) == "no" {
		isShared = false
		isSet = true
	} else if len(shared) == 0 {
		isSet = false
	} else {
		whisk.Debug(whisk.DbgError, "Cannot use value '%s' for shared.\n", shared)
		errMsg := wski18n.T("Cannot use value '{{.arg}}' for shared.", map[string]interface{}{"arg": shared})
		whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG,
			whisk.DISPLAY_USAGE)
		return false, false, whiskErr
	}

	whisk.Debug(whisk.DbgError, "Sharing is '%t'\n", isShared)

	return isShared, isSet, nil
}

func max(a int, b int) int {
	if a > b {
		return a
	}
	return b
}

func min(a int, b int) int {
	if a < b {
		return a
	}
	return b
}

func ReadProps(path string) (map[string]string, error) {

	props := map[string]string{}

	file, err := os.Open(path)
	if err != nil {
		// If file does not exist, just return props
		whisk.Debug(whisk.DbgWarn, "Unable to read whisk properties file '%s' (file open error: %s); falling back to default properties\n", path, err)
		return props, nil
	}
	defer file.Close()

	lines := []string{}
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}

	props = map[string]string{}
	for _, line := range lines {
		re := regexp.MustCompile("#.*")
		line = re.ReplaceAllString(line, "")
		line = strings.TrimSpace(line)
		kv := strings.Split(line, "=")
		if len(kv) != 2 {
			// Invalid format; skip
			continue
		}
		props[kv[0]] = kv[1]
	}

	return props, nil

}

func WriteProps(path string, props map[string]string) error {

	file, err := os.Create(path)
	if err != nil {
		whisk.Debug(whisk.DbgError, "os.Create(%s) failed: %s\n", path, err)
		errStr := wski18n.T("Whisk properties file write failure: {{.err}}", map[string]interface{}{"err": err})
		werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
		return werr
	}
	defer file.Close()

	writer := bufio.NewWriter(file)
	defer writer.Flush()
	for key, value := range props {
		line := fmt.Sprintf("%s=%s", strings.ToUpper(key), value)
		_, err = fmt.Fprintln(writer, line)
		if err != nil {
			whisk.Debug(whisk.DbgError, "fmt.Fprintln() write to '%s' failed: %s\n", path, err)
			errStr := wski18n.T("Whisk properties file write failure: {{.err}}", map[string]interface{}{"err": err})
			werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
			return werr
		}
	}
	return nil
}

func getSpaceGuid() (string, error) {
	// get current props
	props, err := ReadProps(Properties.PropsFile)
	if err != nil {
		whisk.Debug(whisk.DbgError, "readProps(%s) failed: %s\n", Properties.PropsFile, err)
		errStr := wski18n.T("Unable to obtain the `auth` property value: {{.err}}", map[string]interface{}{"err": err})
		werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
		return "", werr
	}

	// get the auth key and parse out the space guid
	if authToken, hasProp := props["AUTH"]; hasProp {
		spaceGuid := strings.Split(authToken, ":")[0]
		return spaceGuid, nil
	}

	whisk.Debug(whisk.DbgError, "auth not found in properties: %#q\n", props)
	errStr := wski18n.T("Auth key property value is not set")
	werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
	return "", werr
}

func isBlockingTimeout(err error) bool {
	var blockingTimeout bool

	whiskErr, isWhiskErr := err.(*whisk.WskError)

	if isWhiskErr && whiskErr.TimedOut {
		blockingTimeout = true
	}

	return blockingTimeout
}

func isApplicationError(err error) bool {
	var applicationError bool

	whiskErr, isWhiskErr := err.(*whisk.WskError)

	if isWhiskErr && whiskErr.ApplicationError {
		applicationError = true
	}

	return applicationError
}

func contains(arr []string, element string) bool {
	for _, e := range arr {
		if e == element {
			return true
		}
	}
	return false
}

func ExitOnError(err error) {
	if err == nil {
		return
	}

	whisk.Debug(whisk.DbgInfo, "err object type: %s\n", reflect.TypeOf(err).String())

	T := wski18n.T
	var exitCode int = 0
	var displayUsage bool = false
	var displayMsg bool = false
	var msgDisplayed bool = true
	var displayPrefix bool = true

	werr, isWskError := err.(*whisk.WskError) // Is the err a WskError?
	if isWskError {
		whisk.Debug(whisk.DbgError, "Got a *whisk.WskError error: %#v\n", werr)
		displayUsage = werr.DisplayUsage
		displayMsg = werr.DisplayMsg
		msgDisplayed = werr.MsgDisplayed
		displayPrefix = werr.DisplayPrefix
		exitCode = werr.ExitCode
	} else {
		whisk.Debug(whisk.DbgError, "Got some other error: %s\n", err)
		fmt.Fprintf(os.Stderr, "%s\n", err)

		displayUsage = false // Cobra already displayed the usage message
		exitCode = 1
	}

	outputStream := colorable.NewColorableStderr()

	// If the err msg should be displayed to the console and it has not already been
	// displayed, display it now.
	if displayMsg && !msgDisplayed && displayPrefix && exitCode != 0 {
		fmt.Fprintf(outputStream, "%s%s\n", color.RedString(T("error: ")), err)
	} else if displayMsg && !msgDisplayed && !displayPrefix && exitCode != 0 {
		fmt.Fprintf(outputStream, "%s\n", err)
	} else if displayMsg && !msgDisplayed && exitCode == 0 {
		fmt.Fprintf(outputStream, "%s\n", err)
	}

	// Displays usage
	if displayUsage {
		fmt.Fprintf(outputStream, T("Run '{{.Name}} --help' for usage.\n",
			map[string]interface{}{"Name": WskCmd.CommandPath()}))
	}

	os.Exit(exitCode)
}
