package bitcoinRpc

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
)

//CoinDefaultProto  default coin protp
const CoinDefaultProto string = "http"

//CoinDefaultProto  default coin protp
const CoinDefaultjsonrpc string = "1.0"

//CoinDefaultid  default coin id
const CoinDefaultid string = "curltest"

// litecoin Host address
var Host string = "localhost"

//litecoin  port
var Port int = 8332

// litecoin address
var User string = "fuzamei"

//litecoin  Password
var Password string = "fuzamei"

//Coin RPC struct
type Coin struct {
	// Configuration options
	username string
	password string
	proto    string
	host     string
	port     int
	url      string
	jsonrpc  string
	// rawResponse string
	responseData []byte
	id           string
	client       *http.Client
}

//NewCoin   create a new RPC instance
func NewCoin(coinUser, coinPasswd, coinHost, coinURL string, coinPort int) (cn *Coin, err error) {
	cn = &Coin{
		username: coinUser,
		password: coinPasswd,
		host:     coinHost,
		port:     coinPort,
		url:      coinURL,
		proto:    CoinDefaultProto,
		jsonrpc:  CoinDefaultjsonrpc,
		id:       CoinDefaultid,
	}
	if len(coinHost) == 0 {
		cn.host = "localhost"
	}
	if coinPort < 0 || coinPort > 65535 {
		cn.port = Port
	}
	cn.client = &http.Client{}
	cn.client.Transport = &http.Transport{}

	return cn, nil
}

func (cn *Coin) access(data map[string]interface{}) (err error) {
	if len(data) != 2 {
		err = errors.New("params count error")
		return
	}
	if cn.client == nil {
		err = errors.New("http client error")
		return
	}

	cn.responseData = nil

	var (
		jbuf []byte
		req  *http.Request
		resp *http.Response
	)
	if jbuf, err = json.Marshal(data); err != nil {
		return
	}

	addr := cn.proto + "://" + cn.username + ":" + cn.password + "@" + cn.host + ":" + strconv.Itoa(cn.port) + "/" + cn.url

	if req, err = http.NewRequest("POST", addr, bytes.NewReader(jbuf)); err != nil {
		fmt.Println(" http.NewRequest:", err.Error())
		return
	}
	req.Header.Set("Content-Type", "application/json")

	if resp, err = cn.client.Do(req); err != nil {
		fmt.Println(" cn.client.Do fail:", err.Error())
		return
	}

	if resp.StatusCode != http.StatusOK {
		err = errors.New(resp.Status)
		fmt.Println(" resp.StatusCode:", err.Error())
		//return
	}

	defer resp.Body.Close()
	if cn.responseData, err = ioutil.ReadAll(resp.Body); err != nil {
		fmt.Println("ioutil.ReadAll(resp.Body):", err.Error())
		return
	}
	if len(cn.responseData) == 0 {
		err = errors.New("response data is empty")
		return
	}
	//fmt.Println("cn.responseData:", string(cn.responseData))
	return
}

//Call run RPC command
func (cn *Coin) Call(method string, args ...interface{}) (data []byte, err error) {
	if method == "" {
		err = errors.New("method is not set")
		return
	}

	requestData := make(map[string]interface{})
	requestData["method"] = method
	requestData["params"] = args
	if err = cn.access(requestData); err == nil {
		data = cn.responseData
	}
	return
}

//Call run RPC command
func RunCall(method string, args ...interface{}) (data []byte, err error) {

	if method == "" {
		err = errors.New("method is not set")
		return
	}

	//create a new RPC instance
	liteCoin, err := NewCoin(User, Password, Host, "", Port)
	if err != nil {
		fmt.Println("liteRpc.NewCoin fail:", err.Error())
		return nil, err
	}

	requestData := make(map[string]interface{})
	requestData["method"] = method
	requestData["params"] = args
	if err = liteCoin.access(requestData); err == nil {
		data = liteCoin.responseData
	}
	return
}

func InitRpcConfig(rpcuser string, rpcpassword string, rpchost string, rpcport int) bool {
	fmt.Println("default rpc config:", User, Password, Host, Port)
	if len(rpcuser) != 0 {
		User = rpcuser
	}
	if len(rpcpassword) != 0 {
		Password = rpcpassword
	}
	if len(rpchost) != 0 {
		Host = rpchost
	}
	if rpcport != 0 {
		Port = rpcport
	}
	fmt.Println("current rpc config:", User, Password, Host, Port)
	return true
}
