/*
 * Copyright (c) 2013 Conformal Systems LLC <info@conformal.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package main

import (
	"bytes"
	"errors"
	"fmt"
	"github.com/conformal/btcjson"
	"github.com/conformal/btcutil"
	"github.com/conformal/btcwallet/tx"
	"github.com/conformal/btcwallet/wallet"
	"github.com/conformal/btcwire"
	"github.com/conformal/btcws"
	"os"
	"path/filepath"
	"sync"
	"time"
)

var (
	// ErrNoWallet describes an error where a wallet does not exist and
	// must be created first.
	ErrNoWallet = errors.New("wallet file does not exist")

	// ErrNoUtxos describes an error where the wallet file was successfully
	// read, but the UTXO file was not.  To properly handle this error,
	// a rescan should be done since the wallet creation block.
	ErrNoUtxos = errors.New("utxo file cannot be read")

	// ErrNoTxs describes an error where the wallet and UTXO files were
	// successfully read, but the TX history file was not.  It is up to
	// the caller whether this necessitates a rescan or not.
	ErrNoTxs = errors.New("tx file cannot be read")

	cfg *config

	curBlock = struct {
		sync.RWMutex
		wallet.BlockStamp
	}{
		BlockStamp: wallet.BlockStamp{
			Height: int32(btcutil.BlockHeightUnknown),
		},
	}

	wallets = NewBtcWalletStore()
)

// BtcWallet is a structure containing all the components for a
// complete wallet.  It contains the Armory-style wallet (to store
// addresses and keys), and tx and utxo data stores, along with locks
// to prevent against incorrect multiple access.
type BtcWallet struct {
	*wallet.Wallet
	mtx               sync.RWMutex
	name              string
	dirty             bool
	fullRescan        bool
	NewBlockTxSeqN    uint64
	SpentOutpointSeqN uint64
	UtxoStore         struct {
		sync.RWMutex
		dirty bool
		s     tx.UtxoStore
	}
	TxStore struct {
		sync.RWMutex
		dirty bool
		s     tx.TxStore
	}
}

// BtcWalletStore stores all wallets currently being handled by
// btcwallet.  Wallet are stored in a map with the account name as the
// key.  A RWMutex is used to protect against incorrect concurrent
// access.
type BtcWalletStore struct {
	sync.Mutex
	m map[string]*BtcWallet
}

// NewBtcWalletStore returns an initialized and empty BtcWalletStore.
func NewBtcWalletStore() *BtcWalletStore {
	return &BtcWalletStore{
		m: make(map[string]*BtcWallet),
	}
}

// Rollback rolls back each BtcWallet saved in the store.
//
// TODO(jrick): This must also roll back the UTXO and TX stores, and notify
// all wallets of new account balances.
func (s *BtcWalletStore) Rollback(height int32, hash *btcwire.ShaHash) {
	for _, w := range s.m {
		w.Rollback(height, hash)
	}
}

// Rollback reverts each stored BtcWallet to a state before the block
// with the passed chainheight and block hash was connected to the main
// chain.  This is used to remove transactions and utxos for each wallet
// that occured on a chain no longer considered to be the main chain.
func (w *BtcWallet) Rollback(height int32, hash *btcwire.ShaHash) {
	w.UtxoStore.Lock()
	w.UtxoStore.dirty = w.UtxoStore.dirty || w.UtxoStore.s.Rollback(height, hash)
	w.UtxoStore.Unlock()

	w.TxStore.Lock()
	w.TxStore.dirty = w.TxStore.dirty || w.TxStore.s.Rollback(height, hash)
	w.TxStore.Unlock()

	if err := w.writeDirtyToDisk(); err != nil {
		log.Errorf("cannot sync dirty wallet: %v", err)
	}
}

// walletdir returns the directory path which holds the wallet, utxo,
// and tx files.
func walletdir(cfg *config, account string) string {
	var wname string
	if account == "" {
		wname = "btcwallet"
	} else {
		wname = fmt.Sprintf("btcwallet-%s", account)
	}

	return filepath.Join(cfg.DataDir, wname)
}

// OpenWallet opens a wallet described by account in the data
// directory specified by cfg.  If the wallet does not exist, ErrNoWallet
// is returned as an error.
//
// Wallets opened from this function are not set to track against a
// btcd connection.
func OpenWallet(cfg *config, account string) (*BtcWallet, error) {
	wdir := walletdir(cfg, account)
	fi, err := os.Stat(wdir)
	if err != nil {
		if os.IsNotExist(err) {
			// Attempt data directory creation
			if err = os.MkdirAll(wdir, 0700); err != nil {
				return nil, fmt.Errorf("cannot create data directory: %s", err)
			}
		} else {
			return nil, fmt.Errorf("error checking data directory: %s", err)
		}
	} else {
		if !fi.IsDir() {
			return nil, fmt.Errorf("data directory '%s' is not a directory", cfg.DataDir)
		}
	}

	wfilepath := filepath.Join(wdir, "wallet.bin")
	utxofilepath := filepath.Join(wdir, "utxo.bin")
	txfilepath := filepath.Join(wdir, "tx.bin")
	var wfile, utxofile, txfile *os.File

	// Read wallet file.
	if wfile, err = os.Open(wfilepath); err != nil {
		if os.IsNotExist(err) {
			// Must create and save wallet first.
			return nil, ErrNoWallet
		}
		return nil, fmt.Errorf("cannot open wallet file: %s", err)
	}
	defer wfile.Close()

	wlt := new(wallet.Wallet)
	if _, err = wlt.ReadFrom(wfile); err != nil {
		return nil, fmt.Errorf("cannot read wallet: %s", err)
	}

	w := &BtcWallet{
		Wallet: wlt,
		name:   account,
	}

	// Read utxo file.  If this fails, return a ErrNoUtxos error so a
	// rescan can be done since the wallet creation block.
	var utxos tx.UtxoStore
	if utxofile, err = os.Open(utxofilepath); err != nil {
		log.Errorf("cannot open utxo file: %s", err)
		return w, ErrNoUtxos
	}
	defer utxofile.Close()
	if _, err = utxos.ReadFrom(utxofile); err != nil {
		log.Errorf("cannot read utxo file: %s", err)
		return w, ErrNoUtxos
	}
	w.UtxoStore.s = utxos

	// Read tx file.  If this fails, return a ErrNoTxs error and let
	// the caller decide if a rescan is necessary.
	if txfile, err = os.Open(txfilepath); err != nil {
		log.Errorf("cannot open tx file: %s", err)
		return w, ErrNoTxs
	}
	defer txfile.Close()
	var txs tx.TxStore
	if _, err = txs.ReadFrom(txfile); err != nil {
		log.Errorf("cannot read tx file: %s", err)
		return w, ErrNoTxs
	}
	w.TxStore.s = txs

	return w, nil
}

// GetCurBlock returns the blockchain height and SHA hash of the most
// recently seen block.  If no blocks have been seen since btcd has
// connected, btcd is queried for the current block height and hash.
func GetCurBlock() (bs wallet.BlockStamp, err error) {
	curBlock.RLock()
	bs = curBlock.BlockStamp
	curBlock.RUnlock()
	if bs.Height != int32(btcutil.BlockHeightUnknown) {
		return bs, nil
	}

	// This is a hack and may result in races, but we need to make
	// sure that btcd is connected and sending a message will succeed,
	// or this will block forever. A better solution is to return an
	// error to the reply handler immediately if btcd is disconnected.
	if !btcdConnected.b {
		return wallet.BlockStamp{
			Height: int32(btcutil.BlockHeightUnknown),
		}, errors.New("current block unavailable")
	}

	n := <-NewJSONID
	cmd := btcws.NewGetBestBlockCmd(fmt.Sprintf("btcwallet(%v)", n))
	mcmd, err := cmd.MarshalJSON()
	if err != nil {
		return wallet.BlockStamp{
			Height: int32(btcutil.BlockHeightUnknown),
		}, errors.New("cannot ask for best block")
	}

	c := make(chan *struct {
		hash   *btcwire.ShaHash
		height int32
	})

	replyHandlers.Lock()
	replyHandlers.m[n] = func(result interface{}, e *btcjson.Error) bool {
		if e != nil {
			c <- nil
			return true
		}
		m, ok := result.(map[string]interface{})
		if !ok {
			c <- nil
			return true
		}
		hashBE, ok := m["hash"].(string)
		if !ok {
			c <- nil
			return true
		}
		hash, err := btcwire.NewShaHashFromStr(hashBE)
		if err != nil {
			c <- nil
			return true
		}
		fheight, ok := m["height"].(float64)
		if !ok {
			c <- nil
			return true
		}
		c <- &struct {
			hash   *btcwire.ShaHash
			height int32
		}{
			hash:   hash,
			height: int32(fheight),
		}
		return true
	}
	replyHandlers.Unlock()

	// send message
	btcdMsgs <- mcmd

	// Block until reply is ready.
	reply, ok := <-c
	if !ok || reply == nil {
		return wallet.BlockStamp{
			Height: int32(btcutil.BlockHeightUnknown),
		}, errors.New("current block unavailable")
	}

	curBlock.Lock()
	if reply.height > curBlock.BlockStamp.Height {
		bs = wallet.BlockStamp{
			Height: reply.height,
			Hash:   *reply.hash,
		}
		curBlock.BlockStamp = bs
	}
	curBlock.Unlock()
	return bs, nil
}

// CalculateBalance sums the amounts of all unspent transaction
// outputs to addresses of a wallet and returns the balance as a
// float64.
//
// If confirmations is 0, all UTXOs, even those not present in a
// block (height -1), will be used to get the balance.  Otherwise,
// a UTXO must be in a block.  If confirmations is 1 or greater,
// the balance will be calculated based on how many how many blocks
// include a UTXO.
func (w *BtcWallet) CalculateBalance(confirms int) float64 {
	var bal uint64 // Measured in satoshi

	bs, err := GetCurBlock()
	if bs.Height == int32(btcutil.BlockHeightUnknown) || err != nil {
		return 0.
	}

	w.UtxoStore.RLock()
	for _, u := range w.UtxoStore.s {
		// Utxos not yet in blocks (height -1) should only be
		// added if confirmations is 0.
		if confirms == 0 || (u.Height != -1 && int(bs.Height-u.Height+1) >= confirms) {
			bal += u.Amt
		}
	}
	w.UtxoStore.RUnlock()
	return float64(bal) / float64(btcutil.SatoshiPerBitcoin)
}

// Track requests btcd to send notifications of new transactions for
// each address stored in a wallet and sets up a new reply handler for
// these notifications.
func (w *BtcWallet) Track() {
	n := <-NewJSONID
	w.mtx.Lock()
	w.NewBlockTxSeqN = n
	w.mtx.Unlock()

	replyHandlers.Lock()
	replyHandlers.m[n] = w.newBlockTxOutHandler
	replyHandlers.Unlock()
	for _, addr := range w.GetActiveAddresses() {
		w.ReqNewTxsForAddress(addr.Address)
	}

	n = <-NewJSONID
	w.mtx.Lock()
	w.SpentOutpointSeqN = n
	w.mtx.Unlock()

	replyHandlers.Lock()
	replyHandlers.m[n] = w.spentUtxoHandler
	replyHandlers.Unlock()
	w.UtxoStore.RLock()
	for _, utxo := range w.UtxoStore.s {
		w.ReqSpentUtxoNtfn(utxo)
	}
	w.UtxoStore.RUnlock()
}

// RescanToBestBlock requests btcd to rescan the blockchain for new
// transactions to all wallet addresses.  This is needed for making
// btcwallet catch up to a long-running btcd process, as otherwise
// it would have missed notifications as blocks are attached to the
// main chain.
func (w *BtcWallet) RescanToBestBlock() {
	beginBlock := int32(0)

	if w.fullRescan {
		// Need to perform a complete rescan since the wallet creation
		// block.
		beginBlock = w.CreatedAt()
		log.Debugf("Rescanning account '%v' for new transactions since block height %v",
			w.name, beginBlock)
	} else {
		// The last synced block height should be used the starting
		// point for block rescanning.  Grab the block stamp here.
		bs := w.SyncedWith()

		log.Debugf("Rescanning account '%v' for new transactions since block height %v hash %v",
			w.name, bs.Height, bs.Hash)

		// If we're synced with block x, must scan the blocks x+1 to best block.
		beginBlock = bs.Height + 1
	}

	n := <-NewJSONID
	cmd, err := btcws.NewRescanCmd(fmt.Sprintf("btcwallet(%v)", n),
		beginBlock, w.ActivePaymentAddresses())
	if err != nil {
		log.Errorf("cannot create rescan request: %v", err)
		return
	}
	mcmd, err := cmd.MarshalJSON()
	if err != nil {
		log.Errorf("cannot create rescan request: %v", err)
		return
	}

	replyHandlers.Lock()
	replyHandlers.m[n] = func(result interface{}, e *btcjson.Error) bool {
		// Rescan is compatible with new txs from connected block
		// notifications, so use that handler.
		_ = w.newBlockTxOutHandler(result, e)

		if result != nil {
			// Notify frontends of new account balance.
			confirmed := w.CalculateBalance(1)
			unconfirmed := w.CalculateBalance(0) - confirmed
			NotifyWalletBalance(frontendNotificationMaster, w.name, confirmed)
			NotifyWalletBalanceUnconfirmed(frontendNotificationMaster, w.name, unconfirmed)

			return false
		}
		if bs, err := GetCurBlock(); err == nil {
			w.SetSyncedWith(&bs)
			w.dirty = true
			if err = w.writeDirtyToDisk(); err != nil {
				log.Errorf("cannot sync dirty wallet: %v",
					err)
			}
		}
		// If result is nil, the rescan has completed.  Returning
		// true removes this handler.
		return true
	}
	replyHandlers.Unlock()

	btcdMsgs <- mcmd
}

// SortedActivePaymentAddresses returns a slice of all active payment
// addresses in an account.
func (w *BtcWallet) SortedActivePaymentAddresses() []string {
	w.mtx.RLock()
	defer w.mtx.RUnlock()

	infos := w.GetSortedActiveAddresses()
	addrs := make([]string, len(infos))

	for i, addr := range infos {
		addrs[i] = addr.Address
	}

	return addrs
}

// ActivePaymentAddresses returns a set of all active pubkey hashes
// in an account.
func (w *BtcWallet) ActivePaymentAddresses() map[string]struct{} {
	w.mtx.RLock()
	defer w.mtx.RUnlock()

	infos := w.GetActiveAddresses()
	addrs := make(map[string]struct{}, len(infos))

	for _, info := range infos {
		addrs[info.Address] = struct{}{}
	}

	return addrs
}

// ReqNewTxsForAddress sends a message to btcd to request tx updates
// for addr for each new block that is added to the blockchain.
func (w *BtcWallet) ReqNewTxsForAddress(addr string) {
	log.Debugf("Requesting notifications of TXs sending to address %v", addr)

	w.mtx.RLock()
	n := w.NewBlockTxSeqN
	w.mtx.RUnlock()

	cmd := btcws.NewNotifyNewTXsCmd(fmt.Sprintf("btcwallet(%d)", n),
		[]string{addr})
	mcmd, err := cmd.MarshalJSON()
	if err != nil {
		log.Errorf("cannot request transaction notifications: %v", err)
	}

	btcdMsgs <- mcmd
}

// ReqSpentUtxoNtfn sends a message to btcd to request updates for when
// a stored UTXO has been spent.
func (w *BtcWallet) ReqSpentUtxoNtfn(u *tx.Utxo) {
	log.Debugf("Requesting spent UTXO notifications for Outpoint hash %s index %d",
		u.Out.Hash, u.Out.Index)

	w.mtx.RLock()
	n := w.SpentOutpointSeqN
	w.mtx.RUnlock()

	cmd := btcws.NewNotifySpentCmd(fmt.Sprintf("btcwallet(%d)", n),
		(*btcwire.OutPoint)(&u.Out))
	mcmd, err := cmd.MarshalJSON()
	if err != nil {
		log.Errorf("cannot create spent request: %v", err)
		return
	}

	btcdMsgs <- mcmd
}

// spentUtxoHandler is the handler function for btcd spent UTXO notifications
// resulting from transactions in newly-attached blocks.
func (w *BtcWallet) spentUtxoHandler(result interface{}, e *btcjson.Error) bool {
	if e != nil {
		log.Errorf("Spent UTXO Handler: Error %d received from btcd: %s",
			e.Code, e.Message)
		return false
	}
	v, ok := result.(map[string]interface{})
	if !ok {
		return false
	}
	txHashBE, ok := v["txhash"].(string)
	if !ok {
		log.Error("Spent UTXO Handler: Unspecified transaction hash.")
		return false
	}
	txHash, err := btcwire.NewShaHashFromStr(txHashBE)
	if err != nil {
		log.Errorf("Spent UTXO Handler: Bad transaction hash: %s", err)
		return false
	}
	index, ok := v["index"].(float64)
	if !ok {
		log.Error("Spent UTXO Handler: Unspecified index.")
	}

	_, _ = txHash, index

	// Never remove this handler.
	return false
}

// newBlockTxOutHandler is the handler function for btcd transaction
// notifications resulting from newly-attached blocks.
func (w *BtcWallet) newBlockTxOutHandler(result interface{}, e *btcjson.Error) bool {
	if e != nil {
		log.Errorf("Tx Handler: Error %d received from btcd: %s",
			e.Code, e.Message)
		return false
	}

	v, ok := result.(map[string]interface{})
	if !ok {
		// The first result sent from btcd is nil.  This could be used to
		// indicate that the request for notifications succeeded.
		if result != nil {
			log.Errorf("Tx Handler: Unexpected result type %T.", result)
		}
		return false
	}
	sender, ok := v["sender"].(string)
	if !ok {
		log.Error("Tx Handler: Unspecified sender.")
		return false
	}
	receiver, ok := v["receiver"].(string)
	if !ok {
		log.Error("Tx Handler: Unspecified receiver.")
		return false
	}
	blockhashBE, ok := v["blockhash"].(string)
	if !ok {
		log.Error("Tx Handler: Unspecified block hash.")
		return false
	}
	height, ok := v["height"].(float64)
	if !ok {
		log.Error("Tx Handler: Unspecified height.")
		return false
	}
	txhashBE, ok := v["txhash"].(string)
	if !ok {
		log.Error("Tx Handler: Unspecified transaction hash.")
		return false
	}
	index, ok := v["index"].(float64)
	if !ok {
		log.Error("Tx Handler: Unspecified transaction index.")
		return false
	}
	amt, ok := v["amount"].(float64)
	if !ok {
		log.Error("Tx Handler: Unspecified amount.")
		return false
	}
	pkscript58, ok := v["pkscript"].(string)
	if !ok {
		log.Error("Tx Handler: Unspecified pubkey script.")
		return false
	}
	pkscript := btcutil.Base58Decode(pkscript58)
	spent := false
	if tspent, ok := v["spent"].(bool); ok {
		spent = tspent
	}

	// btcd sends the block and tx hashes as BE strings.  Convert both
	// to a LE ShaHash.
	blockhash, err := btcwire.NewShaHashFromStr(blockhashBE)
	if err != nil {
		log.Errorf("Tx Handler: Block hash string cannot be parsed: %v", err)
		return false
	}
	txhash, err := btcwire.NewShaHashFromStr(txhashBE)
	if err != nil {
		log.Errorf("Tx Handler: Tx hash string cannot be parsed: %v", err)
		return false
	}
	// TODO(jrick): btcd does not find the sender yet.
	senderHash, _, _ := btcutil.DecodeAddress(sender)
	receiverHash, _, err := btcutil.DecodeAddress(receiver)
	if err != nil {
		log.Errorf("Tx Handler: receiver address can not be decoded: %v", err)
		return false
	}

	// Add to TxStore.
	t := &tx.RecvTx{
		Amt: uint64(amt),
	}
	copy(t.TxHash[:], txhash[:])
	copy(t.BlockHash[:], blockhash[:])
	copy(t.SenderAddr[:], senderHash)
	copy(t.ReceiverAddr[:], receiverHash)

	w.TxStore.Lock()
	txs := w.TxStore.s
	w.TxStore.s = append(txs, t)
	w.TxStore.dirty = true
	w.TxStore.Unlock()

	// Add to UtxoStore if unspent.
	if !spent {
		// First, iterate through all stored utxos.  If an unconfirmed utxo
		// (not present in a block) has the same outpoint as this utxo,
		// update the block height and hash.
		w.UtxoStore.RLock()
		for _, u := range w.UtxoStore.s {
			if bytes.Equal(u.Out.Hash[:], txhash[:]) && u.Out.Index == uint32(index) {
				// Found a either a duplicate, or a change UTXO.  If not change,
				// ignore it.
				w.UtxoStore.RUnlock()
				if u.Height != -1 {
					return false
				}

				w.UtxoStore.Lock()
				copy(u.BlockHash[:], blockhash[:])
				u.Height = int32(height)
				w.UtxoStore.dirty = true
				w.UtxoStore.Unlock()

				return false
			}
		}
		w.UtxoStore.RUnlock()

		// After iterating through all UTXOs, it was not a duplicate or
		// change UTXO appearing in a block.  Append a new Utxo to the end.

		u := &tx.Utxo{
			Amt:       uint64(amt),
			Height:    int32(height),
			Subscript: pkscript,
		}
		copy(u.Out.Hash[:], txhash[:])
		u.Out.Index = uint32(index)
		copy(u.AddrHash[:], receiverHash)
		copy(u.BlockHash[:], blockhash[:])
		w.UtxoStore.Lock()
		w.UtxoStore.s = append(w.UtxoStore.s, u)
		w.UtxoStore.dirty = true
		w.UtxoStore.Unlock()

		// If this notification came from mempool (TODO: currently
		// unimplemented) notify the new unconfirmed balance immediately.
		// Otherwise, wait until the blockconnection notifiation is processed.
	}

	// Never remove this handler.
	return false
}

// NewJSONID is used to receive the next unique JSON ID for btcd
// requests, starting from zero and incrementing by one after each
// read.
var NewJSONID = make(chan uint64)

// JSONIDGenerator sends incremental integers across a channel.  This
// is meant to provide a unique value for the JSON ID field for btcd
// messages.
func JSONIDGenerator(c chan uint64) {
	var n uint64
	for {
		c <- n
		n++
	}
}

func main() {
	// Initialize logging and setup deferred flushing to ensure all
	// outstanding messages are written on shutdown
	loggers := setLogLevel(defaultLogLevel)
	defer func() {
		for _, logger := range loggers {
			logger.Flush()
		}
	}()

	tcfg, _, err := loadConfig()
	if err != nil {
		log.Error(err)
		os.Exit(1)
	}
	cfg = tcfg

	// Change the logging level if needed.
	if cfg.DebugLevel != defaultLogLevel {
		loggers = setLogLevel(cfg.DebugLevel)
	}

	// Open default wallet
	w, err := OpenWallet(cfg, "")
	switch err {
	case ErrNoTxs:
		// Do nothing special for now.  This will be implemented when
		// the tx history file is properly written.
		wallets.Lock()
		wallets.m[""] = w
		wallets.Unlock()

	case ErrNoUtxos:
		// Add wallet, but mark wallet as needing a full rescan since
		// the wallet creation block.  This will take place when btcd
		// connects.
		wallets.Lock()
		wallets.m[""] = w
		wallets.Unlock()
		w.fullRescan = true

	case nil:
		wallets.Lock()
		wallets.m[""] = w
		wallets.Unlock()

	default:
		log.Errorf("cannot open wallet: %v", err)
	}

	// Start wallet disk syncer goroutine.
	go DirtyWalletSyncer()

	go func() {
		// Start HTTP server to listen and send messages to frontend and btcd
		// backend.  Try reconnection if connection failed.
		for {
			if err := FrontendListenAndServe(); err != nil {
				log.Info("Unable to start frontend HTTP server: %v", err)
				os.Exit(1)
			}
		}
	}()

	// Begin generating new IDs for JSON calls.
	go JSONIDGenerator(NewJSONID)

	for {
		replies := make(chan error)
		done := make(chan int)
		go func() {
			BtcdConnect(replies)
			close(done)
		}()
	selectLoop:
		for {
			select {
			case <-done:
				break selectLoop
			case err := <-replies:
				switch err {
				case ErrConnRefused:
					btcdConnected.c <- false
					log.Info("btcd connection refused, retying in 5 seconds")
					time.Sleep(5 * time.Second)
				case ErrConnLost:
					btcdConnected.c <- false
					log.Info("btcd connection lost, retrying in 5 seconds")
					time.Sleep(5 * time.Second)
				case nil:
					btcdConnected.c <- true
					log.Info("Established connection to btcd.")
				default:
					log.Infof("Unhandled error: %v", err)
				}
			}
		}
	}
}