text: /*
  Package cli implements the CLI cmd's methods.
  
  Includes methods for manipulating wallets files and interacting with the
  REST API to query a skycoin node's status.
  */
  package cli
  
  import (
  	"encoding/json"
  	"errors"
  	"flag"
  	"fmt"
  	"net/url"
  	"path/filepath"
  	"strings"
  	"syscall"
  
  	"os"
  
  	"github.com/spf13/cobra"
  	"github.com/spf13/pflag"
  	"golang.org/x/crypto/ssh/terminal"
  
  	"github.com/amherag/skycoin/src/api"
  	"github.com/amherag/skycoin/src/util/file"
  )
  
  var (
  	// Version is the CLI Version
  	Version = "0.26.0"
  )
  
  const (
  	walletExt         = ".wlt"
  	defaultCoin       = "skycoin"
  	defaultWalletName = "$COIN_cli" + walletExt
  	defaultWalletDir  = "$DATA_DIR/wallets"
  	defaultRPCAddress = "http://127.0.0.1:6420"
  	defaultDataDir    = "$HOME/.$COIN/"
  )
  
  var (
  	envVarsHelp = fmt.Sprintf(`ENVIRONMENT VARIABLES:
      RPC_ADDR: Address of RPC node. Must be in scheme://host format. Default "%s"
      RPC_USER: Username for RPC API, if enabled in the RPC.
      RPC_PASS: Password for RPC API, if enabled in the RPC.
      COIN: Name of the coin. Default "%s"
      WALLET_DIR: Directory where wallets are stored. This value is overridden by any subcommand flag specifying a wallet filename, if that filename includes a path. Default "%s"
      WALLET_NAME: Name of wallet file (without path). This value is overridden by any subcommand flag specifying a wallet filename. Default "%s"
      DATA_DIR: Directory where everything is stored. Default "%s"`, defaultRPCAddress, defaultCoin, defaultWalletDir, defaultWalletName, defaultDataDir)
  
  	helpTemplate = fmt.Sprintf(`USAGE:{{if .Runnable}}
    {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
    {{.CommandPath}} [command] [flags] [arguments...]{{end}}{{with (or .Long .Short)}}
  
  DESCRIPTION:
      {{. | trimTrailingWhitespaces}}{{end}}{{if .HasExample}}
  
  EXAMPLES:
  {{.Example}}{{end}}{{if .HasAvailableSubCommands}}
  
  COMMANDS:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
    {{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}}
  
  %s
  `, envVarsHelp)
  
  	// ErrWalletName is returned if the wallet file name is invalid
  	ErrWalletName = fmt.Errorf("error wallet file name, must have %s extension", walletExt)
  	// ErrAddress is returned if an address is invalid
  	ErrAddress = errors.New("invalid address")
  	// ErrJSONMarshal is returned if JSON marshaling failed
  	ErrJSONMarshal = errors.New("json marshal failed")
  )
  
  var (
  	cliConfig Config
  	apiClient *api.Client
  	quitChan  = make(chan struct{})
  )
  
  // Config cli's configuration struct
  type Config struct {
  	WalletDir   string `json:"wallet_directory"`
  	WalletName  string `json:"wallet_name"`
  	DataDir     string `json:"data_directory"`
  	Coin        string `json:"coin"`
  	RPCAddress  string `json:"rpc_address"`
  	RPCUsername string `json:"-"`
  	RPCPassword string `json:"-"`
  }
  
  // LoadConfig loads config from environment, prior to parsing CLI flags
  func LoadConfig() (Config, error) {
  	// get coin name from env
  	coin := os.Getenv("COIN")
  	if coin == "" {
  		coin = defaultCoin
  	}
  
  	// get rpc address from env
  	rpcAddr := os.Getenv("RPC_ADDR")
  	if rpcAddr == "" {
  		rpcAddr = defaultRPCAddress
  	}
  
  	if _, err := url.Parse(rpcAddr); err != nil {
  		return Config{}, errors.New("RPC_ADDR must be in scheme://host format")
  	}
  
  	rpcUser := os.Getenv("RPC_USER")
  	rpcPass := os.Getenv("RPC_PASS")
  
  	home := file.UserHome()
  
  	// get data dir dir from env
  	dataDir := os.Getenv("DATA_DIR")
  	if dataDir == "" {
  		dataDir = filepath.Join(home, fmt.Sprintf(".%s", coin))
  	}
  
  	// get wallet dir from env
  	wltDir := os.Getenv("WALLET_DIR")
  	if wltDir == "" {
  		wltDir = filepath.Join(dataDir, "wallets")
  	}
  
  	// get wallet name from env
  	wltName := os.Getenv("WALLET_NAME")
  	if wltName == "" {
  		wltName = fmt.Sprintf("%s_cli%s", coin, walletExt)
  	}
  
  	if !strings.HasSuffix(wltName, walletExt) {
  		return Config{}, ErrWalletName
  	}
  
  	return Config{
  		WalletDir:   wltDir,
  		WalletName:  wltName,
  		DataDir:     dataDir,
  		Coin:        coin,
  		RPCAddress:  rpcAddr,
  		RPCUsername: rpcUser,
  		RPCPassword: rpcPass,
  	}, nil
  }
  
  // FullWalletPath returns the joined wallet dir and wallet name path
  func (c Config) FullWalletPath() string {
  	return filepath.Join(c.WalletDir, c.WalletName)
  }
  
  // FullDBPath returns the joined data directory and db file name path
  func (c Config) FullDBPath() string {
  	return filepath.Join(c.DataDir, "data.db")
  }
  
  // Returns a full wallet path based on cfg and optional cli arg specifying wallet file
  // FIXME: A CLI flag for the wallet filename is redundant with the envvar. Remove the flags or the envvar.
  func resolveWalletPath(cfg Config, w string) (string, error) {
  	if w == "" {
  		w = cfg.FullWalletPath()
  	}
  
  	if !strings.HasSuffix(w, walletExt) {
  		return "", ErrWalletName
  	}
  
  	// If w is only the basename, use the default wallet directory
  	if filepath.Base(w) == w {
  		w = filepath.Join(cfg.WalletDir, w)
  	}
  
  	absW, err := filepath.Abs(w)
  	if err != nil {
  		return "", fmt.Errorf("Invalid wallet path %s: %v", w, err)
  	}
  
  	return absW, nil
  }
  
  func resolveDBPath(cfg Config, db string) (string, error) {
  	if db == "" {
  		db = cfg.FullDBPath()
  	}
  
  	// If db is only the basename, use the default data dir
  	if filepath.Base(db) == db {
  		db = filepath.Join(cfg.DataDir, db)
  	}
  
  	absDB, err := filepath.Abs(db)
  	if err != nil {
  		return "", fmt.Errorf("Invalid data path %s: %v", db, err)
  	}
  	return absDB, nil
  }
  
  // NewCLI creates a cli instance
  func NewCLI(cfg Config) (*cobra.Command, error) {
  	apiClient = api.NewClient(cfg.RPCAddress)
  	apiClient.SetAuth(cfg.RPCUsername, cfg.RPCPassword)
  
  	cliConfig = cfg
  
  	skyCLI := &cobra.Command{
  		Short: fmt.Sprintf("The %s command line interface", cfg.Coin),
  		Use:   fmt.Sprintf("%s-cli", cfg.Coin),
  	}
  
  	commands := []*cobra.Command{
  		addPrivateKeyCmd(),
  		addressBalanceCmd(),
  		addressGenCmd(),
  		fiberAddressGenCmd(),
  		addressOutputsCmd(),
  		blocksCmd(),
  		broadcastTxCmd(),
  		checkDBCmd(),
  		checkDBEncodingCmd(),
  		createRawTxnCmd(),
  		decodeRawTxnCmd(),
  		decryptWalletCmd(),
  		encryptWalletCmd(),
  		lastBlocksCmd(),
  		listAddressesCmd(),
  		listWalletsCmd(),
  		sendCmd(),
  		showConfigCmd(),
  		showSeedCmd(),
  		statusCmd(),
  		transactionCmd(),
  		verifyTransactionCmd(),
  		verifyAddressCmd(),
  		versionCmd(),
  		walletCreateCmd(),
  		walletAddAddressesCmd(),
  		walletBalanceCmd(),
  		walletDirCmd(),
  		walletHisCmd(),
  		walletOutputsCmd(),
  		richlistCmd(),
  		addressTransactionsCmd(),
  		pendingTransactionsCmd(),
  		addresscountCmd(),
  	}
  
  	skyCLI.Version = Version
  	skyCLI.SuggestionsMinimumDistance = 1
  	skyCLI.AddCommand(commands...)
  
  	skyCLI.SetHelpTemplate(helpTemplate)
  	skyCLI.SetUsageTemplate(helpTemplate)
  	pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
  
  	return skyCLI, nil
  }
  
  func printHelp(c *cobra.Command) {
  	c.Printf("See '%s %s --help'\n", c.Parent().Name(), c.Name())
  }
  
  func formatJSON(obj interface{}) ([]byte, error) {
  	d, err := json.MarshalIndent(obj, "", "    ")
  	if err != nil {
  		return nil, ErrJSONMarshal
  	}
  	return d, nil
  }
  
  func printJSON(obj interface{}) error {
  	d, err := formatJSON(obj)
  	if err != nil {
  		return err
  	}
  
  	fmt.Println(string(d))
  
  	return nil
  }
  
  // readPasswordFromTerminal promotes user to enter password and read it.
  func readPasswordFromTerminal() ([]byte, error) {
  	// Promotes to enter the wallet password
  	fmt.Fprint(os.Stdout, "enter password:")
  	bp, err := terminal.ReadPassword(int(syscall.Stdin)) // nolint: unconvert
  	if err != nil {
  		return nil, err
  	}
  	fmt.Fprintln(os.Stdout, "")
  	return bp, nil
  }
  
  // PUBLIC
  
  // WalletLoadError is returned if a wallet could not be loaded
  type WalletLoadError struct {
  	error
  }
  
  func (e WalletLoadError) Error() string {
  	return fmt.Sprintf("Load wallet failed: %v", e.error)
  }
  
  // WalletSaveError is returned if a wallet could not be saved
  type WalletSaveError struct {
  	error
  }
  
  func (e WalletSaveError) Error() string {
  	return fmt.Sprintf("Save wallet failed: %v", e.error)
  }
  
  // PasswordReader is an interface for getting password
  type PasswordReader interface {
  	Password() ([]byte, error)
  }
  
  // PasswordFromBytes represents an implementation of PasswordReader,
  // which reads password from the bytes itself.
  type PasswordFromBytes []byte
  
  // Password implements the PasswordReader's Password method
  func (p PasswordFromBytes) Password() ([]byte, error) {
  	return []byte(p), nil
  }
  
  // PasswordFromTerm reads password from terminal
  type PasswordFromTerm struct{}
  
  // Password implements the PasswordReader's Password method
  func (p PasswordFromTerm) Password() ([]byte, error) {
  	v, err := readPasswordFromTerminal()
  	if err != nil {
  		return nil, err
  	}
  
  	return v, nil
  }
  
  // NewPasswordReader creats a PasswordReader instance,
  // reads password from the input bytes first, if it's empty, then read from terminal.
  func NewPasswordReader(p []byte) PasswordReader {
  	if len(p) != 0 {
  		return PasswordFromBytes(p)
  	}
  
  	return PasswordFromTerm{}
  }
  