package dcr

import (
	"blockbook/bchain"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"math/big"
	"net"
	"net/http"
	"runtime/debug"
	"strconv"
	"strings"
	"sync"
	"time"

	"blockbook/bchain/coins/btc"

	"github.com/decred/dcrd/dcrjson"
	"github.com/golang/glog"
	"github.com/juju/errors"
)

// voteBitYes defines the vote bit set when a given block validates the previous
// block
const voteBitYes = 0x0001

type DecredRPC struct {
	*btc.BitcoinRPC
	mtx         sync.Mutex
	client      http.Client
	rpcURL      string
	rpcUser     string
	bestBlock   uint32
	rpcPassword string
}

// NewDecredRPC returns new DecredRPC instance.
func NewDecredRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
	b, err := btc.NewBitcoinRPC(config, pushHandler)
	if err != nil {
		return nil, err
	}

	var c btc.Configuration
	if err = json.Unmarshal(config, &c); err != nil {
		return nil, errors.Annotate(err, "Invalid configuration file")
	}

	transport := &http.Transport{
		Dial:                (&net.Dialer{KeepAlive: 600 * time.Second}).Dial,
		MaxIdleConns:        100,
		MaxIdleConnsPerHost: 100, // necessary to not to deplete ports
	}

	d := &DecredRPC{
		BitcoinRPC:  b.(*btc.BitcoinRPC),
		client:      http.Client{Timeout: time.Duration(c.RPCTimeout) * time.Second, Transport: transport},
		rpcURL:      c.RPCURL,
		rpcUser:     c.RPCUser,
		rpcPassword: c.RPCPass,
	}

	d.BitcoinRPC.RPCMarshaler = btc.JSONMarshalerV1{}
	d.BitcoinRPC.ChainConfig.SupportsEstimateSmartFee = false

	return d, nil
}

// Initialize initializes DecredRPC instance.
func (d *DecredRPC) Initialize() error {
	chainInfo, err := d.GetChainInfo()
	if err != nil {
		return err
	}

	chainName := chainInfo.Chain
	glog.Info("Chain name ", chainName)

	params := GetChainParams(chainName)

	// always create parser
	d.BitcoinRPC.Parser = NewDecredParser(params, d.BitcoinRPC.ChainConfig)

	// parameters for getInfo request
	if params.Net == MainnetMagic {
		d.BitcoinRPC.Testnet = false
		d.BitcoinRPC.Network = "livenet"
	} else {
		d.BitcoinRPC.Testnet = true
		d.BitcoinRPC.Network = "testnet"
	}

	glog.Info("rpc: block chain ", params.Name)

	return nil
}

type Error struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

type GenericCmd struct {
	ID     int           `json:"id"`
	Method string        `json:"method"`
	Params []interface{} `json:"params,omitempty"`
}

type GetBlockChainInfoResult struct {
	Error  Error `json:"error"`
	Result struct {
		Chain                string  `json:"chain"`
		Blocks               int64   `json:"blocks"`
		Headers              int64   `json:"headers"`
		SyncHeight           int64   `json:"syncheight"`
		BestBlockHash        string  `json:"bestblockhash"`
		Difficulty           uint32  `json:"difficulty"`
		VerificationProgress float64 `json:"verificationprogress"`
		ChainWork            string  `json:"chainwork"`
		InitialBlockDownload bool    `json:"initialblockdownload"`
		MaxBlockSize         int64   `json:"maxblocksize"`
	} `json:"result"`
}

type GetNetworkInfoResult struct {
	Error  Error `json:"error"`
	Result struct {
		Version         int32   `json:"version"`
		ProtocolVersion int32   `json:"protocolversion"`
		TimeOffset      int64   `json:"timeoffset"`
		Connections     int32   `json:"connections"`
		RelayFee        float64 `json:"relayfee"`
	} `json:"result"`
}

type GetInfoChainResult struct {
	Error  Error `json:"error"`
	Result struct {
		Version         int32   `json:"version"`
		ProtocolVersion int32   `json:"protocolversion"`
		Blocks          int64   `json:"blocks"`
		TimeOffset      int64   `json:"timeoffset"`
		Connections     int32   `json:"connections"`
		Proxy           string  `json:"proxy"`
		Difficulty      float64 `json:"difficulty"`
		TestNet         bool    `json:"testnet"`
		RelayFee        float64 `json:"relayfee"`
		Errors          string  `json:"errors"`
	}
}

type GetBestBlockResult struct {
	Error  Error `json:"error"`
	Result struct {
		Hash   string `json:"hash"`
		Height uint32 `json:"height"`
	} `json:"result"`
}

type GetBlockHashResult struct {
	Error  Error  `json:"error"`
	Result string `json:"result"`
}

type GetBlockResult struct {
	Error  Error `json:"error"`
	Result struct {
		Hash          string      `json:"hash"`
		Confirmations int64       `json:"confirmations"`
		Size          int32       `json:"size"`
		Height        uint32      `json:"height"`
		Version       json.Number `json:"version"`
		MerkleRoot    string      `json:"merkleroot"`
		StakeRoot     string      `json:"stakeroot"`
		RawTx         []RawTx     `json:"rawtx"`
		Tx            []string    `json:"tx,omitempty"`
		STx           []string    `json:"stx,omitempty"`
		Time          int64       `json:"time"`
		Nonce         json.Number `json:"nonce"`
		VoteBits      uint16      `json:"votebits"`
		FinalState    string      `json:"finalstate"`
		Voters        uint16      `json:"voters"`
		FreshStake    uint8       `json:"freshstake"`
		Revocations   uint8       `json:"revocations"`
		PoolSize      uint32      `json:"poolsize"`
		Bits          string      `json:"bits"`
		SBits         float64     `json:"sbits"`
		ExtraData     string      `json:"extradata"`
		StakeVersion  uint32      `json:"stakeversion"`
		Difficulty    float64     `json:"difficulty"`
		ChainWork     string      `json:"chainwork"`
		PreviousHash  string      `json:"previousblockhash"`
		NextHash      string      `json:"nextblockhash,omitempty"`
	} `json:"result"`
}

type GetBlockHeaderResult struct {
	Error  Error `json:"error"`
	Result struct {
		Hash          string      `json:"hash"`
		Confirmations int64       `json:"confirmations"`
		Version       json.Number `json:"version"`
		MerkleRoot    string      `json:"merkleroot"`
		StakeRoot     string      `json:"stakeroot"`
		VoteBits      uint16      `json:"votebits"`
		FinalState    string      `json:"finalstate"`
		Voters        uint16      `json:"voters"`
		FreshStake    uint8       `json:"freshstake"`
		Revocations   uint8       `json:"revocations"`
		PoolSize      uint32      `json:"poolsize"`
		Bits          string      `json:"bits"`
		SBits         float64     `json:"sbits"`
		Height        uint32      `json:"height"`
		Size          uint32      `json:"size"`
		Time          int64       `json:"time"`
		Nonce         uint32      `json:"nonce"`
		ExtraData     string      `json:"extradata"`
		StakeVersion  uint32      `json:"stakeversion"`
		Difficulty    float64     `json:"difficulty"`
		ChainWork     string      `json:"chainwork"`
		PreviousHash  string      `json:"previousblockhash,omitempty"`
		NextHash      string      `json:"nextblockhash,omitempty"`
	} `json:"result"`
}

type ScriptSig struct {
	Asm string `json:"asm"`
	Hex string `json:"hex"`
}

type Vin struct {
	Coinbase    string     `json:"coinbase"`
	Stakebase   string     `json:"stakebase"`
	Txid        string     `json:"txid"`
	Vout        uint32     `json:"vout"`
	Tree        int8       `json:"tree"`
	Sequence    uint32     `json:"sequence"`
	AmountIn    float64    `json:"amountin"`
	BlockHeight uint32     `json:"blockheight"`
	BlockIndex  uint32     `json:"blockindex"`
	ScriptSig   *ScriptSig `json:"scriptsig"`
}

type ScriptPubKeyResult struct {
	Asm       string   `json:"asm"`
	Hex       string   `json:"hex,omitempty"`
	ReqSigs   int32    `json:"reqSigs,omitempty"`
	Type      string   `json:"type"`
	Addresses []string `json:"addresses,omitempty"`
	CommitAmt *float64 `json:"commitamt,omitempty"`
}

type Vout struct {
	Value        float64            `json:"value"`
	N            uint32             `json:"n"`
	Version      uint16             `json:"version"`
	ScriptPubKey ScriptPubKeyResult `json:"scriptPubKey"`
}

type RawTx struct {
	Hex           string `json:"hex"`
	Txid          string `json:"txid"`
	Version       int32  `json:"version"`
	LockTime      uint32 `json:"locktime"`
	Vin           []Vin  `json:"vin"`
	Vout          []Vout `json:"vout"`
	Expiry        uint32 `json:"expiry"`
	BlockIndex    uint32 `json:"blockindex,omitempty"`
	Confirmations int64  `json:"confirmations,omitempty"`
	Time          int64  `json:"time,omitempty"`
	Blocktime     int64  `json:"blocktime,omitempty"`
	TxExtraInfo
}

type GetTransactionResult struct {
	Error  Error `json:"error"`
	Result struct {
		RawTx
	} `json:"result"`
}

type MempoolTxsResult struct {
	Error  Error    `json:"error"`
	Result []string `json:"result"`
}

type EstimateSmartFeeResult struct {
	Error  Error `json:"error"`
	Result struct {
		FeeRate float64  `json:"feerate"`
		Errors  []string `json:"errors"`
		Blocks  int64    `json:"blocks"`
	} `json:"result"`
}

type EstimateFeeResult struct {
	Error  Error       `json:"error"`
	Result json.Number `json:"result"`
}

type SendRawTransactionResult struct {
	Error  Error  `json:"error"`
	Result string `json:"result"`
}

type DecodeRawTransactionResult struct {
	Error  Error `json:"error"`
	Result struct {
		Txid     string `json:"txid"`
		Version  int32  `json:"version"`
		Locktime uint32 `json:"locktime"`
		Expiry   uint32 `json:"expiry"`
		Vin      []Vin  `json:"vin"`
		Vout     []Vout `json:"vout"`
		TxExtraInfo
	} `json:"result"`
}

type TxExtraInfo struct {
	BlockHeight uint32 `json:"blockheight,omitempty"`
	BlockHash   string `json:"blockhash,omitempty"`
}

func (d *DecredRPC) GetChainInfo() (*bchain.ChainInfo, error) {
	blockchainInfoRequest := GenericCmd{
		ID:     1,
		Method: "getblockchaininfo",
	}

	var blockchainInfoResult GetBlockChainInfoResult
	if err := d.Call(blockchainInfoRequest, &blockchainInfoResult); err != nil {
		return nil, err
	}

	if blockchainInfoResult.Error.Message != "" {
		return nil, mapToStandardErr("Error fetching blockchain info: %s", blockchainInfoResult.Error)
	}

	infoChainRequest := GenericCmd{
		ID:     2,
		Method: "getinfo",
	}

	var infoChainResult GetInfoChainResult
	if err := d.Call(infoChainRequest, &infoChainResult); err != nil {
		return nil, err
	}

	if infoChainResult.Error.Message != "" {
		return nil, mapToStandardErr("Error fetching network info: %s", infoChainResult.Error)
	}

	chainInfo := &bchain.ChainInfo{
		Chain:           blockchainInfoResult.Result.Chain,
		Blocks:          int(blockchainInfoResult.Result.Blocks),
		Headers:         int(blockchainInfoResult.Result.Headers),
		Bestblockhash:   blockchainInfoResult.Result.BestBlockHash,
		Difficulty:      strconv.Itoa(int(blockchainInfoResult.Result.Difficulty)),
		SizeOnDisk:      blockchainInfoResult.Result.SyncHeight,
		Version:         strconv.Itoa(int(infoChainResult.Result.Version)),
		Subversion:      "",
		ProtocolVersion: strconv.Itoa(int(infoChainResult.Result.ProtocolVersion)),
		Timeoffset:      float64(infoChainResult.Result.TimeOffset),
		Warnings:        "",
	}
	return chainInfo, nil
}

// getChainBestBlock returns the best block according to dcrd chain. This block
// has no atleast one confirming block.
func (d *DecredRPC) getChainBestBlock() (*GetBestBlockResult, error) {
	bestBlockRequest := GenericCmd{
		ID:     1,
		Method: "getbestblock",
	}

	var bestBlockResult GetBestBlockResult
	if err := d.Call(bestBlockRequest, &bestBlockResult); err != nil {
		return nil, err
	}

	if bestBlockResult.Error.Message != "" {
		return nil, mapToStandardErr("Error fetching best block: %s", bestBlockResult.Error)
	}

	return &bestBlockResult, nil
}

// getBestBlock returns details for the block mined immediately before the
// official dcrd chain's bestblock i.e. it has a minimum of 1 confirmation.
// The chain's best block is not returned as its block validity is not guarranteed.
func (d *DecredRPC) getBestBlock() (*GetBestBlockResult, error) {
	bestBlockResult, err := d.getChainBestBlock()
	if err != nil {
		return nil, err
	}

	// remove the block with less than 1 confirming block
	bestBlockResult.Result.Height--
	validBlockHash, err := d.getBlockHashByHeight(bestBlockResult.Result.Height)
	if err != nil {
		return nil, err
	}

	bestBlockResult.Result.Hash = validBlockHash.Result

	return bestBlockResult, nil
}

// GetBestBlockHash returns the block hash of the most recent block to be mined
// and has a minimum of 1 confirming block.
func (d *DecredRPC) GetBestBlockHash() (string, error) {
	bestBlock, err := d.getBestBlock()
	if err != nil {
		return "", err
	}

	return bestBlock.Result.Hash, nil
}

// GetBestBlockHeight returns the block height of the most recent block to be mined
// and has a minimum of 1 confirming block.
func (d *DecredRPC) GetBestBlockHeight() (uint32, error) {
	bestBlock, err := d.getBestBlock()
	if err != nil {
		return 0, err
	}

	return uint32(bestBlock.Result.Height), err
}

// GetBlockHash returns the block hash of the block at the provided height.
func (d *DecredRPC) GetBlockHash(height uint32) (string, error) {
	blockHashResult, err := d.getBlockHashByHeight(height)
	if err != nil {
		return "", err
	}

	return blockHashResult.Result, nil
}

func (d *DecredRPC) getBlockHashByHeight(height uint32) (*GetBlockHashResult, error) {
	blockHashRequest := GenericCmd{
		ID:     1,
		Method: "getblockhash",
		Params: []interface{}{height},
	}

	var blockHashResult GetBlockHashResult
	if err := d.Call(blockHashRequest, &blockHashResult); err != nil {
		return nil, err
	}

	if blockHashResult.Error.Message != "" {
		return nil, mapToStandardErr("Error fetching block hash: %s", blockHashResult.Error)
	}

	return &blockHashResult, nil
}

// GetBlockHeader returns the block header of the block the provided block hash.
func (d *DecredRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) {
	blockHeaderRequest := GenericCmd{
		ID:     1,
		Method: "getblockheader",
		Params: []interface{}{hash},
	}

	var blockHeader GetBlockHeaderResult
	if err := d.Call(blockHeaderRequest, &blockHeader); err != nil {
		return nil, err
	}

	if blockHeader.Error.Message != "" {
		return nil, mapToStandardErr("Error fetching block info: %s", blockHeader.Error)
	}

	header := &bchain.BlockHeader{
		Hash:          blockHeader.Result.Hash,
		Prev:          blockHeader.Result.PreviousHash,
		Next:          blockHeader.Result.NextHash,
		Height:        blockHeader.Result.Height,
		Confirmations: int(blockHeader.Result.Confirmations),
		Size:          int(blockHeader.Result.Size),
		Time:          blockHeader.Result.Time,
	}

	return header, nil
}

func (d *DecredRPC) GetBlockHeaderByHeight(height uint32) (*bchain.BlockHeader, error) {
	return nil, nil
}

// GetBlock returns the block retreived using the provided block hash by default
// or using the block height if an empty hash string was provided. If the
// requested block has less than 2 confirmation bchain.ErrBlockNotFound error
// is returned. This rule is in places to guarrantee that only validated block
// details (txs) are saved to the db. Access to the bestBlock height is threadsafe.
func (d *DecredRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
	// Confirm if the block at provided height has at least 2 confirming blocks.
	d.mtx.Lock()
	var bestBlockHeight = d.bestBlock
	if height > bestBlockHeight {
		bestBlock, err := d.getBestBlock()
		if err != nil || height > bestBlock.Result.Height {
			// If an error occured or the current height doesn't have a minimum
			// of two confirming blocks (greater than best block), quit.
			d.mtx.Unlock()
			return nil, bchain.ErrBlockNotFound
		}

		d.bestBlock = bestBlock.Result.Height
		bestBlockHeight = bestBlock.Result.Height
	}
	d.mtx.Unlock() // Releases the lock soonest possible

	if hash == "" {
		getHashResult, err := d.getBlockHashByHeight(height)
		if err != nil {
			return nil, err
		}
		hash = getHashResult.Result
	}

	block, err := d.getBlock(hash)
	if err != nil {
		return nil, err
	}

	header := bchain.BlockHeader{
		Hash:          block.Result.Hash,
		Prev:          block.Result.PreviousHash,
		Next:          block.Result.NextHash,
		Height:        block.Result.Height,
		Confirmations: int(block.Result.Confirmations),
		Size:          int(block.Result.Size),
		Time:          block.Result.Time,
	}

	bchainBlock := &bchain.Block{BlockHeader: header}

	// Check the current block validity by fetch the next block
	nextBlockHashResult, err := d.getBlockHashByHeight(height + 1)
	if err != nil {
		return nil, err
	}

	nextBlock, err := d.getBlock(nextBlockHashResult.Result)
	if err != nil {
		return nil, err
	}

	// If the Votesbits set equals to voteBitYes append the regular transactions.
	if nextBlock.Result.VoteBits == voteBitYes {
		for _, txID := range block.Result.Tx {
			if block.Result.Height == 0 {
				continue
			}

			tx, err := d.GetTransaction(txID)
			if err != nil {
				return nil, err
			}

			bchainBlock.Txs = append(bchainBlock.Txs, *tx)
		}
	}

	return bchainBlock, nil
}

func (d *DecredRPC) getBlock(hash string) (*GetBlockResult, error) {
	blockRequest := GenericCmd{
		ID:     1,
		Method: "getblock",
		Params: []interface{}{hash},
	}

	var block GetBlockResult
	if err := d.Call(blockRequest, &block); err != nil {
		return nil, err
	}

	if block.Error.Message != "" {
		return nil, mapToStandardErr("Error fetching block info: %s", block.Error)
	}

	return &block, nil
}

func (d *DecredRPC) decodeRawTransaction(txHex string) (*bchain.Tx, error) {
	decodeRawTxRequest := GenericCmd{
		ID:     1,
		Method: "decoderawtransaction",
		Params: []interface{}{txHex},
	}

	var decodeRawTxResult DecodeRawTransactionResult
	if err := d.Call(decodeRawTxRequest, &decodeRawTxResult); err != nil {
		return nil, err
	}

	if decodeRawTxResult.Error.Message != "" {
		return nil, mapToStandardErr("Error decoding raw tx: %s", decodeRawTxResult.Error)
	}

	tx := &bchain.Tx{
		Hex:      txHex,
		Txid:     decodeRawTxResult.Result.Txid,
		Version:  decodeRawTxResult.Result.Version,
		LockTime: decodeRawTxResult.Result.Locktime,
	}

	// Add block height and block hash info
	tx.CoinSpecificData = decodeRawTxResult.Result.TxExtraInfo

	return tx, nil
}

func (d *DecredRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
	block, err := d.getBlock(hash)
	if err != nil {
		return nil, err
	}

	header := bchain.BlockHeader{
		Hash:          block.Result.Hash,
		Prev:          block.Result.PreviousHash,
		Next:          block.Result.NextHash,
		Height:        block.Result.Height,
		Confirmations: int(block.Result.Confirmations),
		Size:          int(block.Result.Size),
		Time:          int64(block.Result.Time),
	}

	bInfo := &bchain.BlockInfo{
		BlockHeader: header,
		MerkleRoot:  block.Result.MerkleRoot,
		Version:     block.Result.Version,
		Nonce:       block.Result.Nonce,
		Bits:        block.Result.Bits,
		Difficulty:  json.Number(strconv.FormatFloat(block.Result.Difficulty, 'e', -1, 64)),
		Txids:       block.Result.Tx,
	}

	return bInfo, nil
}

// GetTransaction returns a transaction by the transaction ID
func (d *DecredRPC) GetTransaction(txid string) (*bchain.Tx, error) {
	r, err := d.getRawTransaction(txid)
	if err != nil {
		return nil, err
	}

	tx, err := d.Parser.ParseTxFromJson(r)
	if err != nil {
		return nil, errors.Annotatef(err, "txid %v", txid)
	}

	return tx, nil
}

// getRawTransaction returns json as returned by backend, with all coin specific data
func (d *DecredRPC) getRawTransaction(txid string) (json.RawMessage, error) {
	if txid == "" {
		return nil, bchain.ErrTxidMissing
	}

	verbose := 1
	getTxRequest := GenericCmd{
		ID:     1,
		Method: "getrawtransaction",
		Params: []interface{}{txid, &verbose},
	}

	var getTxResult GetTransactionResult
	if err := d.Call(getTxRequest, &getTxResult); err != nil {
		return nil, err
	}

	if getTxResult.Error.Message != "" {
		return nil, mapToStandardErr("Error fetching transaction: %s", getTxResult.Error)
	}

	bytes, err := json.Marshal(getTxResult.Result)
	if err != nil {
		return nil, errors.Annotatef(err, "txid %v", txid)
	}

	return json.RawMessage(bytes), nil
}

// GetTransactionForMempool returns the full tx information identified by the
// provided txid.
func (d *DecredRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
	return d.GetTransaction(txid)
}

// GetMempoolTransactions returns a slice of regular transactions currently in
// the mempool. The block whose validation is still undecided will have its txs,
// listed like they are still in the mempool till the block is confirmed.
func (d *DecredRPC) GetMempoolTransactions() ([]string, error) {
	verbose := false
	txType := "regular"
	mempoolRequest := GenericCmd{
		ID:     1,
		Method: "getrawmempool",
		Params: []interface{}{&verbose, &txType},
	}

	var mempool MempoolTxsResult
	if err := d.Call(mempoolRequest, &mempool); err != nil {
		return []string{}, err
	}

	if mempool.Error.Message != "" {
		return nil, mapToStandardErr("Error fetching mempool data: %s", mempool.Error)
	}

	unvalidatedBlockResult, err := d.getChainBestBlock()
	if err != nil {
		return nil, err
	}

	unvalidatedBlock, err := d.getBlock(unvalidatedBlockResult.Result.Hash)
	if err != nil {
		return nil, err
	}

	mempool.Result = append(mempool.Result, unvalidatedBlock.Result.Tx...)

	return mempool.Result, nil
}

// GetTransactionSpecific returns the json raw message for the tx identified by
// the provided txid.
func (d *DecredRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
	return d.getRawTransaction(tx.Txid)
}

// EstimateSmartFee returns fee estimation
func (d *DecredRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) {
	estimateSmartFeeRequest := GenericCmd{
		ID:     1,
		Method: "estimatesmartfee",
		Params: []interface{}{blocks},
	}

	var smartFeeEstimate EstimateSmartFeeResult
	if err := d.Call(estimateSmartFeeRequest, &smartFeeEstimate); err != nil {
		return *big.NewInt(0), err
	}

	if smartFeeEstimate.Error.Message != "" {
		return *big.NewInt(0), mapToStandardErr("Error fetching smart fee estimate: %s", smartFeeEstimate.Error)
	}

	return *big.NewInt(int64(smartFeeEstimate.Result.FeeRate)), nil
}

// EstimateFee returns fee estimation.
func (d *DecredRPC) EstimateFee(blocks int) (big.Int, error) {
	estimateFeeRequest := GenericCmd{
		ID:     1,
		Method: "estimatefee",
		Params: []interface{}{blocks},
	}

	var feeEstimate EstimateFeeResult
	if err := d.Call(estimateFeeRequest, &feeEstimate); err != nil {
		return *big.NewInt(0), err
	}

	if feeEstimate.Error.Message != "" {
		return *big.NewInt(0), mapToStandardErr("Error fetching fee estimate: %s", feeEstimate.Error)
	}

	r, err := d.Parser.AmountToBigInt(feeEstimate.Result)
	if err != nil {
		return r, err
	}

	return r, nil
}

func (d *DecredRPC) SendRawTransaction(tx string) (string, error) {
	sendRawTxRequest := &GenericCmd{
		ID:     1,
		Method: "sendrawtransaction",
		Params: []interface{}{tx},
	}

	var sendRawTxResult SendRawTransactionResult
	err := d.Call(sendRawTxRequest, &sendRawTxResult)
	if err != nil {
		return "", err
	}

	if sendRawTxResult.Error.Message != "" {
		return "", mapToStandardErr("error sending raw transaction: %s", sendRawTxResult.Error)
	}

	return sendRawTxResult.Result, nil
}

// Call calls Backend RPC interface, using RPCMarshaler interface to marshall the request
func (d *DecredRPC) Call(req interface{}, res interface{}) error {
	httpData, err := json.Marshal(req)
	if err != nil {
		return err
	}

	httpReq, err := http.NewRequest("POST", d.rpcURL, bytes.NewBuffer(httpData))
	if err != nil {
		return err
	}
	httpReq.SetBasicAuth(d.rpcUser, d.rpcPassword)
	httpRes, err := d.client.Do(httpReq)
	// in some cases the httpRes can contain data even if it returns error
	// see http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/
	if httpRes != nil {
		defer httpRes.Body.Close()
	}
	if err != nil {
		return err
	}

	// if server returns HTTP error code it might not return json with response
	// handle both cases
	if httpRes.StatusCode != 200 {
		if err = safeDecodeResponse(httpRes.Body, &res); err != nil {
			return errors.Errorf("%v %v", httpRes.Status, err)
		}
		return nil
	}
	return safeDecodeResponse(httpRes.Body, &res)
}

func safeDecodeResponse(body io.ReadCloser, res *interface{}) (err error) {
	var data []byte
	defer func() {
		if r := recover(); r != nil {
			glog.Error("unmarshal json recovered from panic: ", r, "; data: ", string(data))
			debug.PrintStack()
			if len(data) > 0 && len(data) < 2048 {
				err = errors.Errorf("Error: %v", string(data))
			} else {
				err = errors.New("Internal error")
			}
		}
	}()
	data, err = ioutil.ReadAll(body)
	if err != nil {
		return err
	}

	error := json.Unmarshal(data, res)
	return error
}

// mapToStandardErr map the dcrd API Message errors to the standard error messages
// supported by trezor. Dcrd errors to be mapped are listed here:
// https://github.com/decred/dcrd/blob/2f5e47371263b996bb99e8dc3484f659309bd83a/dcrjson/jsonerr.go
func mapToStandardErr(customPrefix string, err Error) error {
	switch {
	case strings.Contains(err.Message, dcrjson.ErrBlockNotFound.Message) || // Block not found
		strings.Contains(err.Message, dcrjson.ErrOutOfRange.Message) || // Block number out of range
		strings.Contains(err.Message, dcrjson.ErrBestBlockHash.Message): // Error getting best block hash
		return bchain.ErrBlockNotFound
	case strings.Contains(err.Message, dcrjson.ErrNoTxInfo.Message): // No information available about transaction
		return bchain.ErrTxNotFound
	case strings.Contains(err.Message, dcrjson.ErrInvalidTxVout.Message): // Output index number (vout) does not exist for transaction
		return bchain.ErrTxidMissing
	default:
		return fmt.Errorf(customPrefix, err.Message)
	}
}
