package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"sort"
	"strconv"

	"github.com/btcsuite/btcutil"
	flags "github.com/btcsuite/go-flags"
	"github.com/conformal/btcjson"
	"github.com/conformal/btcws"
	"github.com/davecgh/go-spew/spew"
)

// conversionHandler is a handler that is used to convert parameters from the
// command line to a specific type.  This is needed since the btcjson API
// expects certain types for various parameters.
type conversionHandler func(string) (interface{}, error)

// displayHandler is a handler that takes an interface and displays it to
// standard out.  It is used by the handler data to type assert replies and
// show them formatted as desired.
type displayHandler func(interface{}) error

// handlerData contains information about how a command should be handled.
type handlerData struct {
	requiredArgs       int
	optionalArgs       int
	displayHandler     displayHandler
	conversionHandlers []conversionHandler
	makeCmd            func([]interface{}) (btcjson.Cmd, error)
	usage              string
}

// Errors used in the various handlers.
var (
	ErrNoDisplayHandler = errors.New("no display handler specified")
	ErrUsage            = errors.New("btcctl usage") // Real usage is shown.
)

const outpointArrayStr = `"[{"txid":"id","vout":n},...]"`

// commandHandlers is a map of commands and associated handler data that is used
// to validate correctness and perform the command.
var commandHandlers = map[string]*handlerData{
	"addmultisigaddress":    {2, 1, displayGeneric, []conversionHandler{toInt, nil, nil}, makeAddMultiSigAddress, "<numrequired> <[\"pubkey\",...]> [account]"},
	"addnode":               {2, 0, displayJSONDump, nil, makeAddNode, "<ip> <add/remove/onetry>"},
	"createencryptedwallet": {1, 0, displayGeneric, nil, makeCreateEncryptedWallet, "<passphrase>"},
	"createrawtransaction":  {2, 0, displayGeneric, nil, makeCreateRawTransaction, outpointArrayStr + " " + "\"{\"address\":amount,...}\""},
	"debuglevel":            {1, 0, displayGeneric, nil, makeDebugLevel, "<levelspec>"},
	"decoderawtransaction":  {1, 0, displayJSONDump, nil, makeDecodeRawTransaction, "<txhash>"},
	"decodescript":          {1, 0, displayJSONDump, nil, makeDecodeScript, "<hex>"},
	"dumpprivkey":           {1, 0, displayGeneric, nil, makeDumpPrivKey, "<bitcoinaddress>"},
	"estimatefee":           {1, 0, displayGeneric, []conversionHandler{toInt64}, makeEstimateFee, "<numblocks>"},
	"estimatepriority":      {1, 0, displayGeneric, []conversionHandler{toInt64}, makeEstimatePriority, "<numblocks>"},
	"getaccount":            {1, 0, displayGeneric, nil, makeGetAccount, "<address>"},
	"getaccountaddress":     {1, 0, displayGeneric, nil, makeGetAccountAddress, "<account>"},
	"getaddednodeinfo":      {1, 1, displayJSONDump, []conversionHandler{toBool, nil}, makeGetAddedNodeInfo, "<dns> [node]"},
	"getaddressesbyaccount": {1, 0, displayJSONDump, nil, makeGetAddressesByAccount, "[account]"},
	"getbalance":            {0, 2, displayGeneric, []conversionHandler{nil, toInt}, makeGetBalance, "[account] [minconf=1]"},
	"getbestblockhash":      {0, 0, displayGeneric, nil, makeGetBestBlockHash, ""},
	"getblock":              {1, 2, displayJSONDump, []conversionHandler{nil, toBool, toBool}, makeGetBlock, "<blockhash>"},
	"getblockchaininfo":     {0, 0, displayJSONDump, nil, makeGetBlockChainInfo, ""},
	"getblockcount":         {0, 0, displayGeneric, nil, makeGetBlockCount, ""},
	"getblockhash":          {1, 0, displayGeneric, []conversionHandler{toInt64}, makeGetBlockHash, "<blocknumber>"},
	"getblocktemplate":      {0, 1, displayJSONDump, nil, makeGetBlockTemplate, "[jsonrequestobject]"},
	"getconnectioncount":    {0, 0, displayGeneric, nil, makeGetConnectionCount, ""},
	"getdifficulty":         {0, 0, displayFloat64, nil, makeGetDifficulty, ""},
	"getgenerate":           {0, 0, displayGeneric, nil, makeGetGenerate, ""},
	"gethashespersec":       {0, 0, displayGeneric, nil, makeGetHashesPerSec, ""},
	"getinfo":               {0, 0, displayJSONDump, nil, makeGetInfo, ""},
	"getmininginfo":         {0, 0, displayJSONDump, nil, makeGetMiningInfo, ""},
	"getnetworkhashps":      {0, 2, displayGeneric, []conversionHandler{toInt, toInt}, makeGetNetworkHashPS, "[blocks height]"},
	"getnettotals":          {0, 0, displayJSONDump, nil, makeGetNetTotals, ""},
	"getnetworkinfo":        {0, 0, displayJSONDump, nil, makeGetNetworkInfo, ""},
	"getnewaddress":         {0, 1, displayGeneric, nil, makeGetNewAddress, "[account]"},
	"getpeerinfo":           {0, 0, displayJSONDump, nil, makeGetPeerInfo, ""},
	"getrawchangeaddress":   {0, 0, displayGeneric, nil, makeGetRawChangeAddress, ""},
	"getrawmempool":         {0, 1, displayJSONDump, []conversionHandler{toBool}, makeGetRawMempool, "[verbose=false]"},
	"getrawtransaction":     {1, 1, displayJSONDump, []conversionHandler{nil, toInt}, makeGetRawTransaction, "<txhash> [verbose=0]"},
	"getreceivedbyaccount":  {1, 1, displayGeneric, []conversionHandler{nil, toInt}, makeGetReceivedByAccount, "<account> [minconf=1]"},
	"getreceivedbyaddress":  {1, 1, displayGeneric, []conversionHandler{nil, toInt}, makeGetReceivedByAddress, "<address> [minconf=1]"},
	"gettransaction":        {1, 1, displayJSONDump, nil, makeGetTransaction, "txid"},
	"gettxout":              {2, 1, displayJSONDump, []conversionHandler{nil, toInt, toBool}, makeGetTxOut, "<txid> <n> [includemempool=false]"},
	"gettxoutsetinfo":       {0, 0, displayJSONDump, nil, makeGetTxOutSetInfo, ""},
	"getwork":               {0, 1, displayJSONDump, nil, makeGetWork, "[data]"},
	"help":                  {0, 1, displayGeneric, nil, makeHelp, "[commandName]"},
	"importprivkey":         {1, 2, displayGeneric, []conversionHandler{nil, nil, toBool}, makeImportPrivKey, "<wifprivkey> [label] [rescan=true]"},
	"importwallet":          {1, 0, displayGeneric, nil, makeImportWallet, "<filename>"},
	"keypoolrefill":         {0, 1, displayGeneric, []conversionHandler{toInt}, makeKeyPoolRefill, "[newsize]"},
	"listaccounts":          {0, 1, displayJSONDump, []conversionHandler{toInt}, makeListAccounts, "[minconf=1]"},
	"listaddressgroupings":  {0, 0, displayJSONDump, nil, makeListAddressGroupings, ""},
	"listreceivedbyaccount": {0, 2, displayJSONDump, []conversionHandler{toInt, toBool}, makeListReceivedByAccount, "[minconf] [includeempty]"},
	"listreceivedbyaddress": {0, 2, displayJSONDump, []conversionHandler{toInt, toBool}, makeListReceivedByAddress, "[minconf] [includeempty]"},
	"listlockunspent":       {0, 0, displayJSONDump, nil, makeListLockUnspent, ""},
	"listsinceblock":        {0, 2, displayJSONDump, []conversionHandler{nil, toInt}, makeListSinceBlock, "[blockhash] [minconf=10]"},
	"listtransactions":      {0, 3, displayJSONDump, []conversionHandler{nil, toInt, toInt}, makeListTransactions, "[account] [count=10] [from=0]"},
	"listunspent":           {0, 3, displayJSONDump, []conversionHandler{toInt, toInt, nil}, makeListUnspent, "[minconf=1] [maxconf=9999999] [jsonaddressarray]"},
	"lockunspent":           {1, 2, displayJSONDump, []conversionHandler{toBool, nil}, makeLockUnspent, "<unlock> " + outpointArrayStr},
	"ping":                  {0, 0, displayGeneric, nil, makePing, ""},
	"sendfrom": {3, 3, displayGeneric, []conversionHandler{nil, nil, toSatoshi, toInt, nil, nil},
		makeSendFrom, "<account> <address> <amount> [minconf=1] [comment] [comment-to]"},
	"sendmany":               {2, 2, displayGeneric, []conversionHandler{nil, nil, toInt, nil}, makeSendMany, "<account> <{\"address\":amount,...}> [minconf=1] [comment]"},
	"sendrawtransaction":     {1, 0, displayGeneric, nil, makeSendRawTransaction, "<hextx>"},
	"sendtoaddress":          {2, 2, displayGeneric, []conversionHandler{nil, toSatoshi, nil, nil}, makeSendToAddress, "<address> <amount> [comment] [comment-to]"},
	"setgenerate":            {1, 1, displayGeneric, []conversionHandler{toBool, toInt}, makeSetGenerate, "<generate> [genproclimit]"},
	"settxfee":               {1, 0, displayGeneric, []conversionHandler{toSatoshi}, makeSetTxFee, "<amount>"},
	"signmessage":            {2, 2, displayGeneric, nil, makeSignMessage, "<address> <message>"},
	"signrawtransaction":     {1, 3, displayJSONDump, nil, makeSignRawTransaction, "<hex> [{\"txid\":txid,\"vout\":n,\"scriptPubKey\":hex,\"redeemScript\":hex},...] [<privatekey1>,...] [sighashtype=\"ALL\"]"},
	"stop":                   {0, 0, displayGeneric, nil, makeStop, ""},
	"submitblock":            {1, 1, displayGeneric, nil, makeSubmitBlock, "<hexdata> [jsonparametersobject]"},
	"validateaddress":        {1, 0, displayJSONDump, nil, makeValidateAddress, "<address>"},
	"verifychain":            {0, 2, displayJSONDump, []conversionHandler{toInt, toInt}, makeVerifyChain, "[level] [numblocks]"},
	"verifymessage":          {3, 0, displayGeneric, nil, makeVerifyMessage, "<address> <signature> <message>"},
	"walletlock":             {0, 0, displayGeneric, nil, makeWalletLock, ""},
	"walletpassphrase":       {1, 1, displayGeneric, []conversionHandler{nil, toInt64}, makeWalletPassphrase, "<passphrase> [timeout]"},
	"walletpassphrasechange": {2, 0, displayGeneric, nil, makeWalletPassphraseChange, "<oldpassphrase> <newpassphrase>"},
}

// toSatoshi attempts to convert the passed string to a satoshi amount returned
// as an int64.  It returns the int64 packed into an interface so it can be used
// in the calls which expect interfaces.  An error will be returned if the string
// can't be converted first to a float64.
func toSatoshi(val string) (interface{}, error) {
	idx, err := strconv.ParseFloat(val, 64)
	if err != nil {
		return nil, err
	}
	amt, err := btcutil.NewAmount(idx)
	if err != nil {
		return nil, err
	}
	return int64(amt), nil
}

// toInt attempts to convert the passed string to an integer.  It returns the
// integer packed into an interface so it can be used in the calls which expect
// interfaces.  An error will be returned if the string can't be converted to an
// integer.
func toInt(val string) (interface{}, error) {
	idx, err := strconv.Atoi(val)
	if err != nil {
		return nil, err
	}

	return idx, nil
}

// toInt64 attempts to convert the passed string to an int64.  It returns the
// integer packed into an interface so it can be used in the calls which expect
// interfaces.  An error will be returned if the string can't be converted to an
// integer.
func toInt64(val string) (interface{}, error) {
	idx, err := strconv.ParseInt(val, 10, 64)
	if err != nil {
		return nil, err
	}

	return idx, nil
}

// toBool attempts to convert the passed string to a bool.  It returns the
// bool packed into the empty interface so it can be used in the calls which
// accept interfaces.  An error will be returned if the string can't be
// converted to a bool.
func toBool(val string) (interface{}, error) {
	return strconv.ParseBool(val)
}

// displayGeneric is a displayHandler that simply displays the passed interface
// using fmt.Println.
func displayGeneric(reply interface{}) error {
	fmt.Println(reply)
	return nil
}

// displayFloat64 is a displayHandler that ensures the concrete type of the
// passed interface is a float64 and displays it using fmt.Printf.  An error
// is returned if a float64 is not passed.
func displayFloat64(reply interface{}) error {
	if val, ok := reply.(float64); ok {
		fmt.Printf("%f\n", val)
		return nil
	}

	return fmt.Errorf("reply type is not a float64: %v", spew.Sdump(reply))
}

// displaySpewDump is a displayHandler that simply uses spew.Dump to display the
// passed interface.
func displaySpewDump(reply interface{}) error {
	spew.Dump(reply)
	return nil
}

// displayJSONDump is a displayHandler that uses json.Indent to display the
// passed interface.
func displayJSONDump(reply interface{}) error {
	marshaledBytes, err := json.Marshal(reply)
	if err != nil {
		return err
	}

	var buf bytes.Buffer
	err = json.Indent(&buf, marshaledBytes, "", "\t")
	if err != nil {
		return err
	}
	fmt.Println(buf.String())
	return nil
}

// makeAddMultiSigAddress generates the cmd structure for addmultisigaddress commands.
func makeAddMultiSigAddress(args []interface{}) (btcjson.Cmd, error) {
	var pubkeys []string
	err := json.Unmarshal([]byte(args[1].(string)), &pubkeys)
	if err != nil {
		return nil, err
	}
	var opt string
	if len(args) > 2 {
		opt = args[2].(string)
	}
	return btcjson.NewAddMultisigAddressCmd("btcctl", args[0].(int), pubkeys, opt)
}

// makeAddNode generates the cmd structure for addnode commands.
func makeAddNode(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewAddNodeCmd("btcctl", args[0].(string),
		args[1].(string))
}

// makeCreateEncryptedWallet generates the cmd structure for
// createencryptedwallet commands.
func makeCreateEncryptedWallet(args []interface{}) (btcjson.Cmd, error) {
	return btcws.NewCreateEncryptedWalletCmd("btcctl", args[0].(string)), nil
}

// makeCreateRawTransaction generates the cmd structure for createrawtransaction
// commands.
func makeCreateRawTransaction(args []interface{}) (btcjson.Cmd, error) {
	var inputs []btcjson.TransactionInput
	err := json.Unmarshal([]byte(args[0].(string)), &inputs)
	if err != nil {
		return nil, err
	}

	var famounts map[string]float64
	err = json.Unmarshal([]byte(args[1].(string)), &famounts)
	if err != nil {
		return nil, err
	}

	amounts := make(map[string]int64, len(famounts))
	for k, v := range famounts {
		amt, err := btcutil.NewAmount(v)
		if err != nil {
			return nil, err
		}
		amounts[k] = int64(amt)
	}

	return btcjson.NewCreateRawTransactionCmd("btcctl", inputs, amounts)
}

// makeDebugLevel generates the cmd structure for debuglevel commands.
func makeDebugLevel(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewDebugLevelCmd("btcctl", args[0].(string))
}

// makeDecodeRawTransaction generates the cmd structure for
// decoderawtransaction commands.
func makeDecodeRawTransaction(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewDecodeRawTransactionCmd("btcctl", args[0].(string))
}

// makeDecodeScript generates the cmd structure for decodescript commands.
func makeDecodeScript(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewDecodeScriptCmd("btcctl", args[0].(string))
}

// makeDumpPrivKey generates the cmd structure for
// dumpprivkey commands.
func makeDumpPrivKey(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewDumpPrivKeyCmd("btcctl", args[0].(string))
}

// makeEstimateFee generates the cmd structure for estimatefee commands.
func makeEstimateFee(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewEstimateFeeCmd("btcctl", args[0].(int64))
}

// makeEstimatePriority generates the cmd structure for estimatepriority commands.
func makeEstimatePriority(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewEstimatePriorityCmd("btcctl", args[0].(int64))
}

// makeGetAccount generates the cmd structure for
// getaccount commands.
func makeGetAccount(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetAccountCmd("btcctl", args[0].(string))
}

// makeGetAccountAddress generates the cmd structure for
// getaccountaddress commands.
func makeGetAccountAddress(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetAccountAddressCmd("btcctl", args[0].(string))
}

// makeGetAddedNodeInfo generates the cmd structure for
// getaccountaddress commands.
func makeGetAddedNodeInfo(args []interface{}) (btcjson.Cmd, error) {
	// Create the getaddednodeinfo command with defaults for the optional
	// parameters.
	cmd, err := btcjson.NewGetAddedNodeInfoCmd("btcctl", args[0].(bool))
	if err != nil {
		return nil, err
	}

	// Override the optional parameter if it was specified.
	if len(args) > 1 {
		cmd.Node = args[1].(string)
	}

	return cmd, nil
}

// makeGetAddressesByAccount generates the cmd structure for
// getaddressesbyaccount commands.
func makeGetAddressesByAccount(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetAddressesByAccountCmd("btcctl", args[0].(string))
}

// makeGetBalance generates the cmd structure for
// getbalance commands.
func makeGetBalance(args []interface{}) (btcjson.Cmd, error) {
	optargs := make([]interface{}, 0, 2)
	if len(args) > 0 {
		optargs = append(optargs, args[0].(string))
	}
	if len(args) > 1 {
		optargs = append(optargs, args[1].(int))
	}

	return btcjson.NewGetBalanceCmd("btcctl", optargs...)
}

// makeGetBestBlockHash generates the cmd structure for
// makebestblockhash commands.
func makeGetBestBlockHash(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetBestBlockHashCmd("btcctl")
}

// makeGetBlock generates the cmd structure for getblock commands.
func makeGetBlock(args []interface{}) (btcjson.Cmd, error) {
	// Create the getblock command with defaults for the optional
	// parameters.
	getBlockCmd, err := btcjson.NewGetBlockCmd("btcctl", args[0].(string))
	if err != nil {
		return nil, err
	}

	// Override the optional parameters if they were specified.
	if len(args) > 1 {
		getBlockCmd.Verbose = args[1].(bool)
	}
	if len(args) > 2 {
		getBlockCmd.VerboseTx = args[2].(bool)
	}

	return getBlockCmd, nil
}

// makeGetBlockChainInfo generates the cmd structure for getblockchaininfo commands.
func makeGetBlockChainInfo(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetBlockChainInfoCmd("btcctl")
}

// makeGetBlockCount generates the cmd structure for getblockcount commands.
func makeGetBlockCount(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetBlockCountCmd("btcctl")
}

// makeGetBlockHash generates the cmd structure for getblockhash commands.
func makeGetBlockHash(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetBlockHashCmd("btcctl", args[0].(int64))
}

// makeGetBlockTemplate generates the cmd structure for getblocktemplate commands.
func makeGetBlockTemplate(args []interface{}) (btcjson.Cmd, error) {
	cmd, err := btcjson.NewGetBlockTemplateCmd("btcctl")
	if err != nil {
		return nil, err
	}
	if len(args) == 1 {
		err = cmd.UnmarshalJSON([]byte(args[0].(string)))
		if err != nil {
			return nil, err
		}
	}
	return cmd, nil
}

// makeGetConnectionCount generates the cmd structure for
// getconnectioncount commands.
func makeGetConnectionCount(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetConnectionCountCmd("btcctl")
}

// makeGetDifficulty generates the cmd structure for
// getdifficulty commands.
func makeGetDifficulty(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetDifficultyCmd("btcctl")
}

// makeGetGenerate generates the cmd structure for
// getgenerate commands.
func makeGetGenerate(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetGenerateCmd("btcctl")
}

// makeGetHashesPerSec generates the cmd structure for gethashespersec commands.
func makeGetHashesPerSec(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetHashesPerSecCmd("btcctl")
}

// makeGetInfo generates the cmd structure for getinfo commands.
func makeGetInfo(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetInfoCmd("btcctl")
}

// makeGetMiningInfo generates the cmd structure for getmininginfo commands.
func makeGetMiningInfo(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetMiningInfoCmd("btcctl")
}

// makeGetNetworkHashPS generates the cmd structure for getnetworkhashps
// commands.
func makeGetNetworkHashPS(args []interface{}) (btcjson.Cmd, error) {
	// Create the getnetworkhashps command with defaults for the optional
	// parameters.
	cmd, err := btcjson.NewGetNetworkHashPSCmd("btcctl")
	if err != nil {
		return nil, err
	}

	// Override the optional blocks if specified.
	if len(args) > 0 {
		cmd.Blocks = args[0].(int)
	}

	// Override the optional height if specified.
	if len(args) > 1 {
		cmd.Height = args[1].(int)
	}

	return cmd, nil
}

// makeGetNetworkInfo generates the cmd structure for getnetworkinfo commands.
func makeGetNetworkInfo(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetNetworkInfoCmd("btcctl")
}

// makeGetNetTotals generates the cmd structure for getnettotals commands.
func makeGetNetTotals(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetNetTotalsCmd("btcctl")
}

// makeGetNewAddress generates the cmd structure for getnewaddress commands.
func makeGetNewAddress(args []interface{}) (btcjson.Cmd, error) {
	var account string
	if len(args) > 0 {
		account = args[0].(string)
	}
	return btcjson.NewGetNewAddressCmd("btcctl", account)
}

// makePeerInfo generates the cmd structure for
// getpeerinfo commands.
func makeGetPeerInfo(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetPeerInfoCmd("btcctl")
}

// makeGetRawChangeAddress generates the cmd structure for getrawchangeaddress commands.
func makeGetRawChangeAddress(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetRawChangeAddressCmd("btcctl")
}

// makeRawMempool generates the cmd structure for
// getrawmempool commands.
func makeGetRawMempool(args []interface{}) (btcjson.Cmd, error) {
	opt := make([]bool, 0, 1)
	if len(args) > 0 {
		opt = append(opt, args[0].(bool))
	}
	return btcjson.NewGetRawMempoolCmd("btcctl", opt...)
}

// makeGetReceivedByAccount generates the cmd structure for
// getreceivedbyaccount commands.
func makeGetReceivedByAccount(args []interface{}) (btcjson.Cmd, error) {
	opt := make([]int, 0, 1)
	if len(args) > 1 {
		opt = append(opt, args[1].(int))
	}
	return btcjson.NewGetReceivedByAccountCmd("btcctl", args[0].(string), opt...)
}

// makeGetReceivedByAddress generates the cmd structure for
// getreceivedbyaddress commands.
func makeGetReceivedByAddress(args []interface{}) (btcjson.Cmd, error) {
	opt := make([]int, 0, 1)
	if len(args) > 1 {
		opt = append(opt, args[1].(int))
	}
	return btcjson.NewGetReceivedByAddressCmd("btcctl", args[0].(string), opt...)
}

// makeGetTransaction generates the cmd structure for gettransaction commands.
func makeGetTransaction(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetTransactionCmd("btcctl", args[0].(string))
}

// makeGetTxOut generates the cmd structure for gettxout commands.
func makeGetTxOut(args []interface{}) (btcjson.Cmd, error) {
	opt := make([]bool, 0, 1)
	if len(args) > 2 {
		opt = append(opt, args[2].(bool))
	}
	return btcjson.NewGetTxOutCmd("btcctl", args[0].(string), args[1].(int), opt...)
}

// makeGetTxOutSetInfo generates the cmd structure for gettxoutsetinfo commands.
func makeGetTxOutSetInfo(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewGetTxOutSetInfoCmd("btcctl")
}

func makeGetWork(args []interface{}) (btcjson.Cmd, error) {
	cmd, err := btcjson.NewGetWorkCmd("btcctl")
	if err != nil {
		return nil, err
	}
	if len(args) == 1 {
		cmd.Data = args[0].(string)
	}
	return cmd, nil
}

func makeHelp(args []interface{}) (btcjson.Cmd, error) {
	opt := make([]string, 0, 1)
	if len(args) > 0 {
		opt = append(opt, args[0].(string))
	}
	return btcjson.NewHelpCmd("btcctl", opt...)
}

// makeRawTransaction generates the cmd structure for
// getrawtransaction commands.
func makeGetRawTransaction(args []interface{}) (btcjson.Cmd, error) {
	opt := make([]int, 0, 1)
	if len(args) > 1 {
		opt = append(opt, args[1].(int))
	}

	return btcjson.NewGetRawTransactionCmd("btcctl", args[0].(string), opt...)
}

// makeImportPrivKey generates the cmd structure for
// importprivkey commands.
func makeImportPrivKey(args []interface{}) (btcjson.Cmd, error) {
	var optargs = make([]interface{}, 0, 2)
	if len(args) > 1 {
		optargs = append(optargs, args[1].(string))
	}
	if len(args) > 2 {
		optargs = append(optargs, args[2].(bool))
	}

	return btcjson.NewImportPrivKeyCmd("btcctl", args[0].(string), optargs...)
}

// makeImportWallet generates the cmd structure for
// importwallet commands.
func makeImportWallet(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewImportWalletCmd("btcctl", args[0].(string))
}

// makeKeyPoolRefill generates the cmd structure for keypoolrefill commands.
func makeKeyPoolRefill(args []interface{}) (btcjson.Cmd, error) {
	var optargs = make([]uint, 0, 1)
	if len(args) > 0 {
		optargs = append(optargs, uint(args[0].(int)))
	}

	return btcjson.NewKeyPoolRefillCmd("btcctl", optargs...)
}

// makeListAccounts generates the cmd structure for listaccounts commands.
func makeListAccounts(args []interface{}) (btcjson.Cmd, error) {
	var optargs = make([]int, 0, 1)
	if len(args) > 0 {
		optargs = append(optargs, args[0].(int))
	}
	return btcjson.NewListAccountsCmd("btcctl", optargs...)
}

// makeListAddressGroupings generates the cmd structure for listaddressgroupings commands.
func makeListAddressGroupings(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewListAddressGroupingsCmd("btcctl")
}

// makeListReceivedByAccount generates the cmd structure for listreceivedbyaccount commands.
func makeListReceivedByAccount(args []interface{}) (btcjson.Cmd, error) {
	var optargs = make([]interface{}, 0, 2)
	if len(args) > 0 {
		optargs = append(optargs, args[0].(int))
	}
	if len(args) > 1 {
		optargs = append(optargs, args[1].(bool))
	}
	return btcjson.NewListReceivedByAccountCmd("btcctl", optargs...)
}

// makeListReceivedByAddress generates the cmd structure for listreceivedbyaddress commands.
func makeListReceivedByAddress(args []interface{}) (btcjson.Cmd, error) {
	var optargs = make([]interface{}, 0, 2)
	if len(args) > 0 {
		optargs = append(optargs, args[0].(int))
	}
	if len(args) > 1 {
		optargs = append(optargs, args[1].(bool))
	}
	return btcjson.NewListReceivedByAddressCmd("btcctl", optargs...)
}

// makeListLockUnspent generates the cmd structure for listlockunspent commands.
func makeListLockUnspent(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewListLockUnspentCmd("btcctl")
}

// makeListSinceBlock generates the cmd structure for
// listsinceblock commands.
func makeListSinceBlock(args []interface{}) (btcjson.Cmd, error) {
	var optargs = make([]interface{}, 0, 2)
	if len(args) > 0 {
		optargs = append(optargs, args[0].(string))
	}
	if len(args) > 1 {
		optargs = append(optargs, args[1].(int))
	}

	return btcjson.NewListSinceBlockCmd("btcctl", optargs...)
}

// makeListTransactions generates the cmd structure for
// listtransactions commands.
func makeListTransactions(args []interface{}) (btcjson.Cmd, error) {
	var optargs = make([]interface{}, 0, 3)
	if len(args) > 0 {
		optargs = append(optargs, args[0].(string))
	}
	if len(args) > 1 {
		optargs = append(optargs, args[1].(int))
	}
	if len(args) > 2 {
		optargs = append(optargs, args[2].(int))
	}

	return btcjson.NewListTransactionsCmd("btcctl", optargs...)
}

// makeListUnspent generates the cmd structure for listunspent commands.
func makeListUnspent(args []interface{}) (btcjson.Cmd, error) {
	var optargs = make([]interface{}, 0, 3)
	if len(args) > 0 {
		optargs = append(optargs, args[0].(int))
	}
	if len(args) > 1 {
		optargs = append(optargs, args[1].(int))
	}
	if len(args) > 2 {
		var addrs []string
		err := json.Unmarshal([]byte(args[2].(string)), &addrs)
		if err != nil {
			return nil, err
		}
		optargs = append(optargs, addrs)
	}
	return btcjson.NewListUnspentCmd("btcctl", optargs...)
}

// makeLockUnspent generates the cmd structure for lockunspent commands.
func makeLockUnspent(args []interface{}) (btcjson.Cmd, error) {
	var optargs = make([][]btcjson.TransactionInput, 0, 1)
	if len(args) > 1 {
		var inputs []btcjson.TransactionInput
		err := json.Unmarshal([]byte(args[1].(string)), &inputs)
		if err != nil {
			return nil, err
		}
		optargs = append(optargs, inputs)
	}

	return btcjson.NewLockUnspentCmd("btcctl", args[0].(bool), optargs...)
}

// makePing generates the cmd structure for ping commands.
func makePing(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewPingCmd("btcctl")
}

// makeSendFrom generates the cmd structure for sendfrom commands.
func makeSendFrom(args []interface{}) (btcjson.Cmd, error) {
	var optargs = make([]interface{}, 0, 3)
	if len(args) > 3 {
		optargs = append(optargs, args[3].(int))
	}
	if len(args) > 4 {
		optargs = append(optargs, args[4].(string))
	}
	if len(args) > 5 {
		optargs = append(optargs, args[5].(string))
	}
	return btcjson.NewSendFromCmd("btcctl", args[0].(string),
		args[1].(string), args[2].(int64), optargs...)
}

// makeSendMany generates the cmd structure for sendmany commands.
func makeSendMany(args []interface{}) (btcjson.Cmd, error) {
	origPairs := make(map[string]float64)
	err := json.Unmarshal([]byte(args[1].(string)), &origPairs)
	if err != nil {
		return nil, err
	}
	pairs := make(map[string]int64)
	for addr, value := range origPairs {
		pairs[addr] = int64(btcutil.SatoshiPerBitcoin * value)
	}

	var optargs = make([]interface{}, 0, 2)
	if len(args) > 2 {
		optargs = append(optargs, args[2].(int))
	}
	if len(args) > 3 {
		optargs = append(optargs, args[3].(string))
	}
	return btcjson.NewSendManyCmd("btcctl", args[0].(string), pairs, optargs...)
}

// makeSendRawTransaction generates the cmd structure for sendrawtransaction
// commands.
func makeSendRawTransaction(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewSendRawTransactionCmd("btcctl", args[0].(string))
}

// makeSendToAddress generates the cmd struture for sendtoaddress commands.
func makeSendToAddress(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewSendToAddressCmd("btcctl", args[0].(string), args[1].(int64), args[2:]...)
}

//  makeSetGenerate generates the cmd structure for setgenerate commands.
func makeSetGenerate(args []interface{}) (btcjson.Cmd, error) {
	var optargs = make([]int, 0, 1)
	if len(args) > 1 {
		optargs = append(optargs, args[1].(int))
	}
	return btcjson.NewSetGenerateCmd("btcctl", args[0].(bool), optargs...)
}

// makeSetTxFee generates the cmd structure for settxfee commands.
func makeSetTxFee(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewSetTxFeeCmd("btcctl", args[0].(int64))
}

// makeSignMessage generates the cmd structure for signmessage commands.
func makeSignMessage(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewSignMessageCmd("btcctl", args[0].(string),
		args[1].(string))
}

// makeSignRawTransaction generates the cmd structure for signrawtransaction commands.
func makeSignRawTransaction(args []interface{}) (btcjson.Cmd, error) {
	optArgs := make([]interface{}, 0, 3)
	if len(args) > 1 {
		var inputs []btcjson.RawTxInput
		err := json.Unmarshal([]byte(args[1].(string)), &inputs)
		if err != nil {
			return nil, err
		}
		optArgs = append(optArgs, inputs)
	}
	if len(args) > 2 {
		var inputs []string
		err := json.Unmarshal([]byte(args[2].(string)), &inputs)
		if err != nil {
			return nil, err
		}
		optArgs = append(optArgs, inputs)
	}
	if len(args) > 3 {
		optArgs = append(optArgs, args[3])
	}
	return btcjson.NewSignRawTransactionCmd("btcctl", args[0].(string), optArgs...)
}

// makeStop generates the cmd structure for stop commands.
func makeStop(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewStopCmd("btcctl")
}

// makeSubmitBlock generates the cmd structure for submitblock commands.
func makeSubmitBlock(args []interface{}) (btcjson.Cmd, error) {
	opts := &btcjson.SubmitBlockOptions{}
	if len(args) == 2 {
		opts.WorkID = args[1].(string)
	}

	return btcjson.NewSubmitBlockCmd("btcctl", args[0].(string), opts)
}

// makeValidateAddress generates the cmd structure for validateaddress commands.
func makeValidateAddress(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewValidateAddressCmd("btcctl", args[0].(string))
}

// makeVerifyChain generates the cmd structure for verifychain commands.
func makeVerifyChain(args []interface{}) (btcjson.Cmd, error) {
	iargs := make([]int32, 0, 2)
	for _, i := range args {
		iargs = append(iargs, int32(i.(int)))
	}
	return btcjson.NewVerifyChainCmd("btcctl", iargs...)
}

func makeVerifyMessage(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewVerifyMessageCmd("btcctl", args[0].(string),
		args[1].(string), args[2].(string))
}

// makeWalletLock generates the cmd structure for walletlock commands.
func makeWalletLock(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewWalletLockCmd("btcctl")
}

// makeWalletPassphrase generates the cmd structure for walletpassphrase commands.
func makeWalletPassphrase(args []interface{}) (btcjson.Cmd, error) {
	timeout := int64(60)
	if len(args) > 1 {
		timeout = args[1].(int64)
	}
	return btcjson.NewWalletPassphraseCmd("btcctl", args[0].(string), timeout)
}

// makeWalletPassphraseChange generates the cmd structure for
// walletpassphrasechange commands.
func makeWalletPassphraseChange(args []interface{}) (btcjson.Cmd, error) {
	return btcjson.NewWalletPassphraseChangeCmd("btcctl", args[0].(string),
		args[1].(string))
}

// send sends a JSON-RPC command to the specified RPC server and examines the
// results for various error conditions.  It either returns a valid result or
// an appropriate error.
func send(cfg *config, msg []byte) (interface{}, error) {
	var reply btcjson.Reply
	var err error
	if cfg.NoTLS || (cfg.RPCCert == "" && !cfg.TLSSkipVerify) {
		reply, err = btcjson.RpcCommand(cfg.RPCUser, cfg.RPCPassword,
			cfg.RPCServer, msg)
	} else {
		var pem []byte
		if cfg.RPCCert != "" {
			pem, err = ioutil.ReadFile(cfg.RPCCert)
			if err != nil {
				return nil, err
			}
		}
		reply, err = btcjson.TlsRpcCommand(cfg.RPCUser,
			cfg.RPCPassword, cfg.RPCServer, msg, pem,
			cfg.TLSSkipVerify)
	}
	if err != nil {
		return nil, err
	}

	if reply.Error != nil {
		return nil, reply.Error
	}

	return reply.Result, nil
}

// sendCommand creates a JSON-RPC command using the passed command and arguments
// and then sends it.  A prefix is added to any errors that occur indicating
// what step failed.
func sendCommand(cfg *config, command btcjson.Cmd) (interface{}, error) {
	msg, err := json.Marshal(command)
	if err != nil {
		return nil, fmt.Errorf("createMessage: %v", err.Error())
	}

	reply, err := send(cfg, msg)
	if err != nil {
		return nil, fmt.Errorf("rpcCommand: %v", err.Error())
	}

	return reply, nil
}

// commandHandler handles commands provided via the cli using the specific
// handler data to instruct the handler what to do.
func commandHandler(cfg *config, command string, data *handlerData, args []string) error {
	// Ensure the number of arguments are the expected value.
	if len(args) < data.requiredArgs {
		return ErrUsage
	}
	if len(args) > data.requiredArgs+data.optionalArgs {
		return ErrUsage
	}

	// Ensure there is a display handler.
	if data.displayHandler == nil {
		return ErrNoDisplayHandler
	}

	// Ensure the number of conversion handlers is valid if any are
	// specified.
	convHandlers := data.conversionHandlers
	if convHandlers != nil && len(convHandlers) < len(args) {
		return fmt.Errorf("the number of conversion handlers is invalid")
	}

	// Convert input parameters per the conversion handlers.
	iargs := make([]interface{}, len(args))
	for i, arg := range args {
		iargs[i] = arg
	}
	for i := range iargs {
		if convHandlers != nil {
			converter := convHandlers[i]
			if converter != nil {
				convertedArg, err := converter(args[i])
				if err != nil {
					return err
				}
				iargs[i] = convertedArg
			}
		}
	}
	cmd, err := data.makeCmd(iargs)
	if err != nil {
		return err
	}

	// Create and send the appropriate JSON-RPC command.
	reply, err := sendCommand(cfg, cmd)
	if err != nil {
		return err
	}

	// Display the results of the JSON-RPC command using the provided
	// display handler.
	if reply != nil {
		err = data.displayHandler(reply)
		if err != nil {
			return err

		}
	}

	return nil
}

// usage displays the command usage.
func usage(parser *flags.Parser) {
	parser.WriteHelp(os.Stderr)

	// Extract usage information for each command from the command handler
	// data and sort by command name.
	fmt.Fprintf(os.Stderr, "\nCommands:\n")
	usageStrings := make([]string, 0, len(commandHandlers))
	for command, data := range commandHandlers {
		usage := command
		if len(data.usage) > 0 {
			usage += " " + data.usage
		}
		usageStrings = append(usageStrings, usage)
	}
	sort.Sort(sort.StringSlice(usageStrings))
	for _, usage := range usageStrings {
		fmt.Fprintf(os.Stderr, "\t%s\n", usage)
	}
}

func main() {
	parser, cfg, args, err := loadConfig()
	if err != nil {
		usage(parser)
		os.Exit(1)
	}
	if len(args) < 1 {
		usage(parser)
		return
	}

	// Display usage if the command is not supported.
	data, exists := commandHandlers[args[0]]
	if !exists {
		fmt.Fprintf(os.Stderr, "Unrecognized command: %s\n", args[0])
		usage(parser)
		os.Exit(1)
	}

	// Execute the command.
	err = commandHandler(cfg, args[0], data, args[1:])
	if err != nil {
		if err == ErrUsage {
			usage(parser)
			os.Exit(1)
		}

		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}
}