package commands

import (
	"fmt"
	"os"
	"regexp"

	"github.com/spf13/cobra"

	"github.com/vapor/util"
)

// vaporcli usage template
var usageTemplate = `Usage:{{if .Runnable}}
  {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
  {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}

Aliases:
  {{.NameAndAliases}}{{end}}{{if .HasExample}}

Examples:
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}

Available Commands:
    {{range .Commands}}{{if (and .IsAvailableCommand (.Name | WalletDisable))}}
    {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}

  available with wallet enable:
    {{range .Commands}}{{if (and .IsAvailableCommand (.Name | WalletEnable))}}
    {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}

Flags:
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}

Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}

Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
  {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}

Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
`

// commandError is an error used to signal different error situations in command handling.
type commandError struct {
	s         string
	userError bool
}

func (c commandError) Error() string {
	return c.s
}

func (c commandError) isUserError() bool {
	return c.userError
}

func newUserError(a ...interface{}) commandError {
	return commandError{s: fmt.Sprintln(a...), userError: true}
}

func newSystemError(a ...interface{}) commandError {
	return commandError{s: fmt.Sprintln(a...), userError: false}
}

func newSystemErrorF(format string, a ...interface{}) commandError {
	return commandError{s: fmt.Sprintf(format, a...), userError: false}
}

// Catch some of the obvious user errors from Cobra.
// We don't want to show the usage message for every error.
// The below may be to generic. Time will show.
var userErrorRegexp = regexp.MustCompile("argument|flag|shorthand")

func isUserError(err error) bool {
	if cErr, ok := err.(commandError); ok && cErr.isUserError() {
		return true
	}

	return userErrorRegexp.MatchString(err.Error())
}

// VaporCmd is Vaporcli's root command.
// Every other command attached to VaporcliCmd is a child command to it.
var VaporcliCmd = &cobra.Command{
	Use:   "vaporcli",
	Short: "Vaporcli is a commond line client for vapor (a.k.a. vapord)",
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) < 1 {
			cmd.SetUsageTemplate(usageTemplate)
			cmd.Usage()
		}
	},
}

// Execute adds all child commands to the root command VaporcliCmd and sets flags appropriately.
func Execute() {

	AddCommands()
	AddTemplateFunc()

	if _, err := VaporcliCmd.ExecuteC(); err != nil {
		os.Exit(util.ErrLocalExe)
	}
}

// AddCommands adds child commands to the root command VaporcliCmd.
func AddCommands() {
	VaporcliCmd.AddCommand(createAccessTokenCmd)
	VaporcliCmd.AddCommand(listAccessTokenCmd)
	VaporcliCmd.AddCommand(deleteAccessTokenCmd)
	VaporcliCmd.AddCommand(checkAccessTokenCmd)

	VaporcliCmd.AddCommand(createAccountCmd)
	VaporcliCmd.AddCommand(deleteAccountCmd)
	VaporcliCmd.AddCommand(listAccountsCmd)
	VaporcliCmd.AddCommand(updateAccountAliasCmd)
	VaporcliCmd.AddCommand(createAccountReceiverCmd)
	VaporcliCmd.AddCommand(listAddressesCmd)
	VaporcliCmd.AddCommand(validateAddressCmd)
	VaporcliCmd.AddCommand(listPubKeysCmd)

	VaporcliCmd.AddCommand(createAssetCmd)
	VaporcliCmd.AddCommand(getAssetCmd)
	VaporcliCmd.AddCommand(listAssetsCmd)
	VaporcliCmd.AddCommand(updateAssetAliasCmd)

	VaporcliCmd.AddCommand(getTransactionCmd)
	VaporcliCmd.AddCommand(listTransactionsCmd)

	VaporcliCmd.AddCommand(getUnconfirmedTransactionCmd)
	VaporcliCmd.AddCommand(listUnconfirmedTransactionsCmd)
	VaporcliCmd.AddCommand(decodeRawTransactionCmd)

	VaporcliCmd.AddCommand(listUnspentOutputsCmd)
	VaporcliCmd.AddCommand(listBalancesCmd)

	VaporcliCmd.AddCommand(rescanWalletCmd)
	VaporcliCmd.AddCommand(walletInfoCmd)

	VaporcliCmd.AddCommand(buildTransactionCmd)
	VaporcliCmd.AddCommand(signTransactionCmd)
	VaporcliCmd.AddCommand(submitTransactionCmd)
	VaporcliCmd.AddCommand(estimateTransactionGasCmd)

	VaporcliCmd.AddCommand(getBlockCountCmd)
	VaporcliCmd.AddCommand(getBlockHashCmd)
	VaporcliCmd.AddCommand(getBlockCmd)
	VaporcliCmd.AddCommand(getBlockHeaderCmd)
	VaporcliCmd.AddCommand(getDifficultyCmd)
	VaporcliCmd.AddCommand(getHashRateCmd)

	VaporcliCmd.AddCommand(createKeyCmd)
	VaporcliCmd.AddCommand(deleteKeyCmd)
	VaporcliCmd.AddCommand(listKeysCmd)
	VaporcliCmd.AddCommand(updateKeyAliasCmd)
	VaporcliCmd.AddCommand(resetKeyPwdCmd)
	VaporcliCmd.AddCommand(checkKeyPwdCmd)

	VaporcliCmd.AddCommand(signMsgCmd)
	VaporcliCmd.AddCommand(verifyMsgCmd)
	VaporcliCmd.AddCommand(decodeProgCmd)

	VaporcliCmd.AddCommand(createTransactionFeedCmd)
	VaporcliCmd.AddCommand(listTransactionFeedsCmd)
	VaporcliCmd.AddCommand(deleteTransactionFeedCmd)
	VaporcliCmd.AddCommand(getTransactionFeedCmd)
	VaporcliCmd.AddCommand(updateTransactionFeedCmd)

	VaporcliCmd.AddCommand(isMiningCmd)
	VaporcliCmd.AddCommand(setMiningCmd)

	VaporcliCmd.AddCommand(netInfoCmd)
	VaporcliCmd.AddCommand(gasRateCmd)

	VaporcliCmd.AddCommand(versionCmd)
}

// AddTemplateFunc adds usage template to the root command VaporcliCmd.
func AddTemplateFunc() {
	walletEnableCmd := []string{
		createAccountCmd.Name(),
		listAccountsCmd.Name(),
		deleteAccountCmd.Name(),
		updateAccountAliasCmd.Name(),
		createAccountReceiverCmd.Name(),
		listAddressesCmd.Name(),
		validateAddressCmd.Name(),
		listPubKeysCmd.Name(),

		createAssetCmd.Name(),
		getAssetCmd.Name(),
		listAssetsCmd.Name(),
		updateAssetAliasCmd.Name(),

		createKeyCmd.Name(),
		deleteKeyCmd.Name(),
		listKeysCmd.Name(),
		resetKeyPwdCmd.Name(),
		checkKeyPwdCmd.Name(),
		signMsgCmd.Name(),

		buildTransactionCmd.Name(),
		signTransactionCmd.Name(),

		getTransactionCmd.Name(),
		listTransactionsCmd.Name(),
		listUnspentOutputsCmd.Name(),
		listBalancesCmd.Name(),

		rescanWalletCmd.Name(),
		walletInfoCmd.Name(),
	}

	cobra.AddTemplateFunc("WalletEnable", func(cmdName string) bool {
		for _, name := range walletEnableCmd {
			if name == cmdName {
				return true
			}
		}
		return false
	})

	cobra.AddTemplateFunc("WalletDisable", func(cmdName string) bool {
		for _, name := range walletEnableCmd {
			if name == cmdName {
				return false
			}
		}
		return true
	})
}
