package jsonrpc

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"

	. "github.com/onsi/gomega"
)

// needed to retrieve requests that arrived at httpServer for further investigation
var requestChan = make(chan *RequestData, 1)

// the request datastructure that can be retrieved for test assertions
type RequestData struct {
	request *http.Request
	body    string
}

// set the response body the httpServer should return for the next request
var responseBody = ""

var httpServer *httptest.Server

// start the testhttp server and stop it when tests are finished
func TestMain(m *testing.M) {
	httpServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		data, _ := ioutil.ReadAll(r.Body)
		defer r.Body.Close()
		// put request and body to channel for the client to investigate them
		requestChan <- &RequestData{r, string(data)}

		fmt.Fprintf(w, responseBody)
	}))
	defer httpServer.Close()

	os.Exit(m.Run())
}

func TestSimpleRpcCallHeaderCorrect(t *testing.T) {
	RegisterTestingT(t)

	rpcClient := NewClient(httpServer.URL)
	rpcClient.Call("add", 1, 2)

	req := (<-requestChan).request

	Expect(req.Method).To(Equal("POST"))
	Expect(req.Header.Get("Content-Type")).To(Equal("application/json"))
	Expect(req.Header.Get("Accept")).To(Equal("application/json"))
}

// test if the structure of an rpc request is built correctly by validating the data that arrived on the test server
func TestRpcClient_Call(t *testing.T) {
	RegisterTestingT(t)
	rpcClient := NewClient(httpServer.URL)

	person := Person{
		Name:    "Alex",
		Age:     35,
		Country: "Germany",
	}

	drink := Drink{
		Name:        "Cuba Libre",
		Ingredients: []string{"rum", "cola"},
	}

	rpcClient.Call("missingParam")
	Expect((<-requestChan).body).To(Equal(`{"method":"missingParam","id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("nullParam", nil)
	Expect((<-requestChan).body).To(Equal(`{"method":"nullParam","params":[null],"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("nullParams", nil, nil)
	Expect((<-requestChan).body).To(Equal(`{"method":"nullParams","params":[null,null],"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("emptyParams", []interface{}{})
	Expect((<-requestChan).body).To(Equal(`{"method":"emptyParams","params":[],"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("emptyAnyParams", []string{})
	Expect((<-requestChan).body).To(Equal(`{"method":"emptyAnyParams","params":[],"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("emptyObject", struct{}{})
	Expect((<-requestChan).body).To(Equal(`{"method":"emptyObject","params":{},"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("emptyObjectList", []struct{}{{}, {}})
	Expect((<-requestChan).body).To(Equal(`{"method":"emptyObjectList","params":[{},{}],"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("boolParam", true)
	Expect((<-requestChan).body).To(Equal(`{"method":"boolParam","params":[true],"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("boolParams", true, false, true)
	Expect((<-requestChan).body).To(Equal(`{"method":"boolParams","params":[true,false,true],"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("stringParam", "Alex")
	Expect((<-requestChan).body).To(Equal(`{"method":"stringParam","params":["Alex"],"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("stringParams", "JSON", "RPC")
	Expect((<-requestChan).body).To(Equal(`{"method":"stringParams","params":["JSON","RPC"],"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("numberParam", 123)
	Expect((<-requestChan).body).To(Equal(`{"method":"numberParam","params":[123],"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("numberParams", 123, 321)
	Expect((<-requestChan).body).To(Equal(`{"method":"numberParams","params":[123,321],"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("floatParam", 1.23)
	Expect((<-requestChan).body).To(Equal(`{"method":"floatParam","params":[1.23],"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("floatParams", 1.23, 3.21)
	Expect((<-requestChan).body).To(Equal(`{"method":"floatParams","params":[1.23,3.21],"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("manyParams", "Alex", 35, true, nil, 2.34)
	Expect((<-requestChan).body).To(Equal(`{"method":"manyParams","params":["Alex",35,true,null,2.34],"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("emptyMissingPublicFieldObject", struct{ name string }{name: "Alex"})
	Expect((<-requestChan).body).To(Equal(`{"method":"emptyMissingPublicFieldObject","params":{},"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("singleStruct", person)
	Expect((<-requestChan).body).To(Equal(`{"method":"singleStruct","params":{"name":"Alex","age":35,"country":"Germany"},"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("singlePointerToStruct", &person)
	Expect((<-requestChan).body).To(Equal(`{"method":"singlePointerToStruct","params":{"name":"Alex","age":35,"country":"Germany"},"id":0,"jsonrpc":"2.0"}`))

	pp := &person
	rpcClient.Call("doublePointerStruct", &pp)
	Expect((<-requestChan).body).To(Equal(`{"method":"doublePointerStruct","params":{"name":"Alex","age":35,"country":"Germany"},"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("multipleStructs", person, &drink)
	Expect((<-requestChan).body).To(Equal(`{"method":"multipleStructs","params":[{"name":"Alex","age":35,"country":"Germany"},{"name":"Cuba Libre","ingredients":["rum","cola"]}],"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("singleStructInArray", []interface{}{person})
	Expect((<-requestChan).body).To(Equal(`{"method":"singleStructInArray","params":[{"name":"Alex","age":35,"country":"Germany"}],"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("namedParameters", map[string]interface{}{
		"name": "Alex",
		"age":  35,
	})
	Expect((<-requestChan).body).To(Equal(`{"method":"namedParameters","params":{"age":35,"name":"Alex"},"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("anonymousStructNoTags", struct {
		Name string
		Age  int
	}{"Alex", 33})
	Expect((<-requestChan).body).To(Equal(`{"method":"anonymousStructNoTags","params":{"Name":"Alex","Age":33},"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("anonymousStructWithTags", struct {
		Name string `json:"name"`
		Age  int    `json:"age"`
	}{"Alex", 33})
	Expect((<-requestChan).body).To(Equal(`{"method":"anonymousStructWithTags","params":{"name":"Alex","age":33},"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("structWithNullField", struct {
		Name    string  `json:"name"`
		Address *string `json:"address"`
	}{"Alex", nil})
	Expect((<-requestChan).body).To(Equal(`{"method":"structWithNullField","params":{"name":"Alex","address":null},"id":0,"jsonrpc":"2.0"}`))

	rpcClient.Call("nestedStruct",
		Planet{
			Name: "Mars",
			Properties: Properties{
				Distance: 54600000,
				Color:    "red",
			},
		})
	Expect((<-requestChan).body).To(Equal(`{"method":"nestedStruct","params":{"name":"Mars","properties":{"distance":54600000,"color":"red"}},"id":0,"jsonrpc":"2.0"}`))
}

func TestRpcClient_CallBatch(t *testing.T) {
	RegisterTestingT(t)
	rpcClient := NewClient(httpServer.URL)

	person := Person{
		Name:    "Alex",
		Age:     35,
		Country: "Germany",
	}

	drink := Drink{
		Name:        "Cuba Libre",
		Ingredients: []string{"rum", "cola"},
	}

	// invalid parameters are possible by manually defining *RPCRequest
	rpcClient.CallBatch(RPCRequests{
		{
			Method: "singleRequest",
			Params: 3, // invalid, should be []int{3}
		},
	})
	Expect((<-requestChan).body).To(Equal(`[{"method":"singleRequest","params":3,"id":0,"jsonrpc":"2.0"}]`))

	// better use Params() unless you know what you are doing
	rpcClient.CallBatch(RPCRequests{
		{
			Method: "singleRequest",
			Params: Params(3), // always valid json rpc
		},
	})
	Expect((<-requestChan).body).To(Equal(`[{"method":"singleRequest","params":[3],"id":0,"jsonrpc":"2.0"}]`))

	// even better, use NewRequest()
	rpcClient.CallBatch(RPCRequests{
		NewRequest("multipleRequests1", 1),
		NewRequest("multipleRequests2", 2),
		NewRequest("multipleRequests3", 3),
	})
	Expect((<-requestChan).body).To(Equal(`[{"method":"multipleRequests1","params":[1],"id":0,"jsonrpc":"2.0"},{"method":"multipleRequests2","params":[2],"id":1,"jsonrpc":"2.0"},{"method":"multipleRequests3","params":[3],"id":2,"jsonrpc":"2.0"}]`))

	// test a huge batch request
	requests := RPCRequests{
		NewRequest("nullParam", nil),
		NewRequest("nullParams", nil, nil),
		NewRequest("emptyParams", []interface{}{}),
		NewRequest("emptyAnyParams", []string{}),
		NewRequest("emptyObject", struct{}{}),
		NewRequest("emptyObjectList", []struct{}{{}, {}}),
		NewRequest("boolParam", true),
		NewRequest("boolParams", true, false, true),
		NewRequest("stringParam", "Alex"),
		NewRequest("stringParams", "JSON", "RPC"),
		NewRequest("numberParam", 123),
		NewRequest("numberParams", 123, 321),
		NewRequest("floatParam", 1.23),
		NewRequest("floatParams", 1.23, 3.21),
		NewRequest("manyParams", "Alex", 35, true, nil, 2.34),
		NewRequest("emptyMissingPublicFieldObject", struct{ name string }{name: "Alex"}),
		NewRequest("singleStruct", person),
		NewRequest("singlePointerToStruct", &person),
		NewRequest("multipleStructs", person, &drink),
		NewRequest("singleStructInArray", []interface{}{person}),
		NewRequest("namedParameters", map[string]interface{}{
			"name": "Alex",
			"age":  35,
		}),
		NewRequest("anonymousStructNoTags", struct {
			Name string
			Age  int
		}{"Alex", 33}),
		NewRequest("anonymousStructWithTags", struct {
			Name string `json:"name"`
			Age  int    `json:"age"`
		}{"Alex", 33}),
		NewRequest("structWithNullField", struct {
			Name    string  `json:"name"`
			Address *string `json:"address"`
		}{"Alex", nil}),
	}
	rpcClient.CallBatch(requests)

	Expect((<-requestChan).body).To(Equal(`[{"method":"nullParam","params":[null],"id":0,"jsonrpc":"2.0"},` +
		`{"method":"nullParams","params":[null,null],"id":1,"jsonrpc":"2.0"},` +
		`{"method":"emptyParams","params":[],"id":2,"jsonrpc":"2.0"},` +
		`{"method":"emptyAnyParams","params":[],"id":3,"jsonrpc":"2.0"},` +
		`{"method":"emptyObject","params":{},"id":4,"jsonrpc":"2.0"},` +
		`{"method":"emptyObjectList","params":[{},{}],"id":5,"jsonrpc":"2.0"},` +
		`{"method":"boolParam","params":[true],"id":6,"jsonrpc":"2.0"},` +
		`{"method":"boolParams","params":[true,false,true],"id":7,"jsonrpc":"2.0"},` +
		`{"method":"stringParam","params":["Alex"],"id":8,"jsonrpc":"2.0"},` +
		`{"method":"stringParams","params":["JSON","RPC"],"id":9,"jsonrpc":"2.0"},` +
		`{"method":"numberParam","params":[123],"id":10,"jsonrpc":"2.0"},` +
		`{"method":"numberParams","params":[123,321],"id":11,"jsonrpc":"2.0"},` +
		`{"method":"floatParam","params":[1.23],"id":12,"jsonrpc":"2.0"},` +
		`{"method":"floatParams","params":[1.23,3.21],"id":13,"jsonrpc":"2.0"},` +
		`{"method":"manyParams","params":["Alex",35,true,null,2.34],"id":14,"jsonrpc":"2.0"},` +
		`{"method":"emptyMissingPublicFieldObject","params":{},"id":15,"jsonrpc":"2.0"},` +
		`{"method":"singleStruct","params":{"name":"Alex","age":35,"country":"Germany"},"id":16,"jsonrpc":"2.0"},` +
		`{"method":"singlePointerToStruct","params":{"name":"Alex","age":35,"country":"Germany"},"id":17,"jsonrpc":"2.0"},` +
		`{"method":"multipleStructs","params":[{"name":"Alex","age":35,"country":"Germany"},{"name":"Cuba Libre","ingredients":["rum","cola"]}],"id":18,"jsonrpc":"2.0"},` +
		`{"method":"singleStructInArray","params":[{"name":"Alex","age":35,"country":"Germany"}],"id":19,"jsonrpc":"2.0"},` +
		`{"method":"namedParameters","params":{"age":35,"name":"Alex"},"id":20,"jsonrpc":"2.0"},` +
		`{"method":"anonymousStructNoTags","params":{"Name":"Alex","Age":33},"id":21,"jsonrpc":"2.0"},` +
		`{"method":"anonymousStructWithTags","params":{"name":"Alex","age":33},"id":22,"jsonrpc":"2.0"},` +
		`{"method":"structWithNullField","params":{"name":"Alex","address":null},"id":23,"jsonrpc":"2.0"}]`))

	// create batch manually
	requests = []*RPCRequest{
		{
			Method:  "myMethod1",
			Params:  []int{1},
			ID:      123,   // will be forced to requests[i].ID == i unless you use CallBatchRaw
			JSONRPC: "7.0", // will be forced to "2.0"  unless you use CallBatchRaw
		},
		{
			Method:  "myMethod2",
			Params:  &person,
			ID:      321,     // will be forced to requests[i].ID == i unless you use CallBatchRaw
			JSONRPC: "wrong", // will be forced to "2.0" unless you use CallBatchRaw
		},
	}
	rpcClient.CallBatch(requests)

	Expect((<-requestChan).body).To(Equal(`[{"method":"myMethod1","params":[1],"id":0,"jsonrpc":"2.0"},` +
		`{"method":"myMethod2","params":{"name":"Alex","age":35,"country":"Germany"},"id":1,"jsonrpc":"2.0"}]`))

	// use raw batch
	requests = []*RPCRequest{
		{
			Method:  "myMethod1",
			Params:  []int{1},
			ID:      123,
			JSONRPC: "7.0",
		},
		{
			Method:  "myMethod2",
			Params:  &person,
			ID:      321,
			JSONRPC: "wrong",
		},
	}
	rpcClient.CallBatchRaw(requests)

	Expect((<-requestChan).body).To(Equal(`[{"method":"myMethod1","params":[1],"id":123,"jsonrpc":"7.0"},` +
		`{"method":"myMethod2","params":{"name":"Alex","age":35,"country":"Germany"},"id":321,"jsonrpc":"wrong"}]`))
}

// test if the result of an an rpc request is parsed correctly and if errors are thrown correctly
func TestRpcJsonResponseStruct(t *testing.T) {
	RegisterTestingT(t)
	rpcClient := NewClient(httpServer.URL)

	// empty return body is an error
	responseBody = ``
	res, err := rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).NotTo(BeNil())
	Expect(res).To(BeNil())

	// not a json body is an error
	responseBody = `{ "not": "a", "json": "object"`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).NotTo(BeNil())
	Expect(res).To(BeNil())

	// field "anotherField" not allowed in rpc response is an error
	responseBody = `{ "anotherField": "norpc"}`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).NotTo(BeNil())
	Expect(res).To(BeNil())

	// TODO: result must contain one of "result", "error"
	// TODO: is there an efficient way to do this?
	/*responseBody = `{}`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).NotTo(BeNil())
	Expect(res).To(BeNil())*/

	// result null is ok
	responseBody = `{"result": null}`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Result).To(BeNil())
	Expect(res.Error).To(BeNil())

	// error null is ok
	responseBody = `{"error": null}`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Result).To(BeNil())
	Expect(res.Error).To(BeNil())

	// result and error null is ok
	responseBody = `{"result": null, "error": null}`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Result).To(BeNil())
	Expect(res.Error).To(BeNil())

	// TODO: result must not contain both of "result", "error" != null
	// TODO: is there an efficient way to do this?
	/*responseBody = `{ "result": 123, "error": {"code": 123, "message": "something wrong"}}`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).NotTo(BeNil())
	Expect(res).To(BeNil())*/

	// result string is ok
	responseBody = `{"result": "ok"}`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Result).To(Equal("ok"))

	// result with error null is ok
	responseBody = `{"result": "ok", "error": null}`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Result).To(Equal("ok"))

	// error with result null is ok
	responseBody = `{"error": {"code": 123, "message": "something wrong"}, "result": null}`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Result).To(BeNil())
	Expect(res.Error.Code).To(Equal(123))
	Expect(res.Error.Message).To(Equal("something wrong"))

	// TODO: empty error is not ok, must at least contain code and message
	/*responseBody = `{ "error": {}}`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Result).To(BeNil())
	Expect(res.Error).NotTo(BeNil())*/

	// TODO: only code in error is not ok, must at least contain code and message
	/*responseBody = `{ "error": {"code": 123}}`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Result).To(BeNil())
	Expect(res.Error).NotTo(BeNil())*/

	// TODO: only message in error is not ok, must at least contain code and message
	/*responseBody = `{ "error": {"message": "something wrong"}}`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Result).To(BeNil())
	Expect(res.Error).NotTo(BeNil())*/

	// error with code and message is ok
	responseBody = `{ "error": {"code": 123, "message": "something wrong"}}`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Result).To(BeNil())
	Expect(res.Error.Code).To(Equal(123))
	Expect(res.Error.Message).To(Equal("something wrong"))

	// check results

	// should return int correctly
	responseBody = `{ "result": 1 }`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Error).To(BeNil())
	i, err := res.GetInt()
	Expect(err).To(BeNil())
	Expect(i).To(Equal(int64(1)))

	// error on wrong type
	i = 3
	responseBody = `{ "result": "notAnInt" }`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Error).To(BeNil())
	i, err = res.GetInt()
	Expect(err).NotTo(BeNil())
	Expect(i).To(Equal(int64(0)))

	// error on result null
	i = 3
	responseBody = `{ "result": null }`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Error).To(BeNil())
	i, err = res.GetInt()
	Expect(err).NotTo(BeNil())
	Expect(i).To(Equal(int64(0)))

	b := false
	responseBody = `{ "result": true }`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Error).To(BeNil())
	b, err = res.GetBool()
	Expect(err).To(BeNil())
	Expect(b).To(Equal(true))

	b = true
	responseBody = `{ "result": 123 }`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Error).To(BeNil())
	b, err = res.GetBool()
	Expect(err).NotTo(BeNil())
	Expect(b).To(Equal(false))

	var p *Person
	responseBody = `{ "result": {"name": "Alex", "age": 35, "anotherField": "something"} }`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Error).To(BeNil())
	err = res.GetObject(&p)
	Expect(err).To(BeNil())
	Expect(p.Name).To(Equal("Alex"))
	Expect(p.Age).To(Equal(35))
	Expect(p.Country).To(Equal(""))

	// TODO: How to check if result could be parsed or if it is default?
	p = nil
	responseBody = `{ "result": {"anotherField": "something"} }`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Error).To(BeNil())
	err = res.GetObject(&p)
	Expect(err).To(BeNil())
	Expect(p).NotTo(BeNil())

	// TODO: HERE######
	var pp *PointerFieldPerson
	responseBody = `{ "result": {"anotherField": "something", "country": "Germany"} }`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Error).To(BeNil())
	err = res.GetObject(&pp)
	Expect(err).To(BeNil())
	Expect(pp.Name).To(BeNil())
	Expect(pp.Age).To(BeNil())
	Expect(*pp.Country).To(Equal("Germany"))

	p = nil
	responseBody = `{ "result": null }`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Error).To(BeNil())
	err = res.GetObject(&p)
	Expect(err).To(BeNil())
	Expect(p).To(BeNil())

	// passing nil is an error
	p = nil
	responseBody = `{ "result": null }`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Error).To(BeNil())
	err = res.GetObject(p)
	Expect(err).NotTo(BeNil())
	Expect(p).To(BeNil())

	p2 := &Person{
		Name: "Alex",
	}
	responseBody = `{ "result": null }`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Error).To(BeNil())
	err = res.GetObject(&p2)
	Expect(err).To(BeNil())
	Expect(p2).To(BeNil())

	p2 = &Person{
		Name: "Alex",
	}
	responseBody = `{ "result": {"age": 35} }`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Error).To(BeNil())
	err = res.GetObject(p2)
	Expect(err).To(BeNil())
	Expect(p2.Name).To(Equal("Alex"))
	Expect(p2.Age).To(Equal(35))

	// prefilled struct is kept on no result
	p3 := Person{
		Name: "Alex",
	}
	responseBody = `{ "result": null }`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Error).To(BeNil())
	err = res.GetObject(&p3)
	Expect(err).To(BeNil())
	Expect(p3.Name).To(Equal("Alex"))

	// prefilled struct is extended / overwritten
	p3 = Person{
		Name: "Alex",
		Age:  123,
	}
	responseBody = `{ "result": {"age": 35, "country": "Germany"} }`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Error).To(BeNil())
	err = res.GetObject(&p3)
	Expect(err).To(BeNil())
	Expect(p3.Name).To(Equal("Alex"))
	Expect(p3.Age).To(Equal(35))
	Expect(p3.Country).To(Equal("Germany"))

	// nil is an error
	responseBody = `{ "result": {"age": 35} }`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.Error).To(BeNil())
	err = res.GetObject(nil)
	Expect(err).NotTo(BeNil())
}

func TestRpcBatchJsonResponseStruct(t *testing.T) {
	RegisterTestingT(t)
	rpcClient := NewClient(httpServer.URL)

	// empty return body is an error
	responseBody = ``
	res, err := rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})
	<-requestChan
	Expect(err).NotTo(BeNil())
	Expect(res).To(BeNil())

	// not a json body is an error
	responseBody = `{ "not": "a", "json": "object"`
	res, err = rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})
	<-requestChan
	Expect(err).NotTo(BeNil())
	Expect(res).To(BeNil())

	// field "anotherField" not allowed in rpc response is an error
	responseBody = `{ "anotherField": "norpc"}`
	res, err = rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})
	<-requestChan
	Expect(err).NotTo(BeNil())
	Expect(res).To(BeNil())

	// TODO: result must contain one of "result", "error"
	// TODO: is there an efficient way to do this?
	/*responseBody = `[{}]`
	res, err = rpcClient.Call("something", 1, 2, 3)
	<-requestChan
	Expect(err).NotTo(BeNil())
	Expect(res).To(BeNil())*/

	// result must be wrapped in array on batch request
	responseBody = `{"result": null}`
	res, err = rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})
	<-requestChan
	Expect(err.Error()).NotTo(BeNil())

	// result ok since in array
	responseBody = `[{"result": null}]`
	res, err = rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})
	<-requestChan
	Expect(err).To(BeNil())
	Expect(len(res)).To(Equal(1))
	Expect(res[0].Result).To(BeNil())

	// error null is ok
	responseBody = `[{"error": null}]`
	res, err = rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res[0].Result).To(BeNil())
	Expect(res[0].Error).To(BeNil())

	// result and error null is ok
	responseBody = `[{"result": null, "error": null}]`
	res, err = rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res[0].Result).To(BeNil())
	Expect(res[0].Error).To(BeNil())

	// TODO: result must not contain both of "result", "error" != null
	// TODO: is there an efficient way to do this?
	/*responseBody = `[{ "result": 123, "error": {"code": 123, "message": "something wrong"}}]`
	res, err = rpcClient.CallBatch(RPCRequests{
	NewRequest("something",1, 2, 3),
	})
	<-requestChan
	Expect(err).NotTo(BeNil())
	Expect(res).To(BeNil())*/

	// result string is ok
	responseBody = `[{"result": "ok","id":0}]`
	res, err = rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res[0].Result).To(Equal("ok"))
	Expect(res[0].ID).To(Equal(0))

	// result with error null is ok
	responseBody = `[{"result": "ok", "error": null}]`
	res, err = rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res[0].Result).To(Equal("ok"))

	// error with result null is ok
	responseBody = `[{"error": {"code": 123, "message": "something wrong"}, "result": null}]`
	res, err = rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res[0].Result).To(BeNil())
	Expect(res[0].Error.Code).To(Equal(123))
	Expect(res[0].Error.Message).To(Equal("something wrong"))

	// TODO: empty error is not ok, must at least contain code and message
	/*responseBody = `[{ "error": {}}]`
	res, err = rpcClient.CallBatch(RPCRequests{
	NewRequest("something",1, 2, 3),
	})
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res[0].Result).To(BeNil())
	Expect(res[0].Error).NotTo(BeNil())*/ /*

		// TODO: only code in error is not ok, must at least contain code and message
	*/ /*responseBody = `[{ "error": {"code": 123}}]`
	res, err = rpcClient.CallBatch(RPCRequests{
	NewRequest("something",1, 2, 3),
	})
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res[0].Result).To(BeNil())
	Expect(res[0].Error).NotTo(BeNil())*/ /*

		// TODO: only message in error is not ok, must at least contain code and message
	*/ /*responseBody = `[{ "error": {"message": "something wrong"}}]`
	res, err = rpcClient.CallBatch(RPCRequests{
	NewRequest("something",1, 2, 3),
	})
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res[0].Result).To(BeNil())
	Expect(res[0].Error).NotTo(BeNil())*/

	// error with code and message is ok
	responseBody = `[{ "error": {"code": 123, "message": "something wrong"}}]`
	res, err = rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res[0].Result).To(BeNil())
	Expect(res[0].Error.Code).To(Equal(123))
	Expect(res[0].Error.Message).To(Equal("something wrong"))

	// check results

	// should return int correctly
	responseBody = `[{ "result": 1 }]`
	res, err = rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res[0].Error).To(BeNil())
	i, err := res[0].GetInt()
	Expect(err).To(BeNil())
	Expect(i).To(Equal(int64(1)))

	// error on wrong type
	i = 3
	responseBody = `[{ "result": "notAnInt" }]`
	res, err = rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res[0].Error).To(BeNil())
	i, err = res[0].GetInt()
	Expect(err).NotTo(BeNil())
	Expect(i).To(Equal(int64(0)))

	var p *Person
	responseBody = `[{"id":0, "result": {"name": "Alex", "age": 35}}, {"id":2, "result": {"name": "Lena", "age": 2}}]`
	res, err = rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})

	<-requestChan
	Expect(err).To(BeNil())

	Expect(res[0].Error).To(BeNil())
	Expect(res[0].ID).To(Equal(0))

	Expect(res[1].Error).To(BeNil())
	Expect(res[1].ID).To(Equal(2))

	err = res[0].GetObject(&p)
	Expect(p.Name).To(Equal("Alex"))
	Expect(p.Age).To(Equal(35))

	err = res[1].GetObject(&p)
	Expect(p.Name).To(Equal("Lena"))
	Expect(p.Age).To(Equal(2))

	// check if error occurred
	responseBody = `[{ "result": "someresult", "error": null}, { "result": null, "error": {"code": 123, "message": "something wrong"}}]`
	res, err = rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.HasError()).To(BeTrue())

	// check if error occurred
	responseBody = `[{ "result": null, "error": {"code": 123, "message": "something wrong"}}]`
	res, err = rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.HasError()).To(BeTrue())
	// check if error occurred
	responseBody = `[{ "result": null, "error": {"code": 123, "message": "something wrong"}}]`
	res, err = rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.HasError()).To(BeTrue())

	// check if response mapping works
	responseBody = `[{ "id":123,"result": 123},{ "id":1,"result": 1}]`
	res, err = rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.HasError()).To(BeFalse())
	resMap := res.AsMap()

	int1, _ := resMap[1].GetInt()
	int123, _ := resMap[123].GetInt()
	Expect(int1).To(Equal(int64(1)))
	Expect(int123).To(Equal(int64(123)))

	// check if getByID works
	int123, _ = res.GetByID(123).GetInt()
	Expect(int123).To(Equal(int64(123)))

	// check if error occurred
	responseBody = `[{ "result": null, "error": {"code": 123, "message": "something wrong"}}]`
	res, err = rpcClient.CallBatch(RPCRequests{
		NewRequest("something", 1, 2, 3),
	})
	<-requestChan
	Expect(err).To(BeNil())
	Expect(res.HasError()).To(BeTrue())

	/*
		// TODO: How to check if result could be parsed or if it is default?
		p = nil
		responseBody = `{ "result": {"anotherField": "something"} }`
		res, err = rpcClient.CallBatch(RPCRequests{
			{"something", Params(1, 2, 3)},
		})
		<-requestChan
		Expect(err).To(BeNil())
		Expect(res.Error).To(BeNil())
		err = res.GetObject(&p)
		Expect(err).To(BeNil())
		Expect(p).NotTo(BeNil())

		// TODO: HERE######
		var pp *PointerFieldPerson
		responseBody = `{ "result": {"anotherField": "something", "country": "Germany"} }`
		res, err = rpcClient.CallBatch(RPCRequests{
			{"something", Params(1, 2, 3)},
		})
		<-requestChan
		Expect(err).To(BeNil())
		Expect(res.Error).To(BeNil())
		err = res.GetObject(&pp)
		Expect(err).To(BeNil())
		Expect(pp.Name).To(BeNil())
		Expect(pp.Age).To(BeNil())
		Expect(*pp.Country).To(Equal("Germany"))

		p = nil
		responseBody = `{ "result": null }`
		res, err = rpcClient.CallBatch(RPCRequests{
			{"something", Params(1, 2, 3)},
		})
		<-requestChan
		Expect(err).To(BeNil())
		Expect(res.Error).To(BeNil())
		err = res.GetObject(&p)
		Expect(err).To(BeNil())
		Expect(p).To(BeNil())

		// passing nil is an error
		p = nil
		responseBody = `{ "result": null }`
		res, err = rpcClient.CallBatch(RPCRequests{
			{"something", Params(1, 2, 3)},
		})
		<-requestChan
		Expect(err).To(BeNil())
		Expect(res.Error).To(BeNil())
		err = res.GetObject(p)
		Expect(err).NotTo(BeNil())
		Expect(p).To(BeNil())

		p2 := &Person{
			Name: "Alex",
		}
		responseBody = `{ "result": null }`
		res, err = rpcClient.CallBatch(RPCRequests{
			{"something", Params(1, 2, 3)},
		})
		<-requestChan
		Expect(err).To(BeNil())
		Expect(res.Error).To(BeNil())
		err = res.GetObject(&p2)
		Expect(err).To(BeNil())
		Expect(p2).To(BeNil())

		p2 = &Person{
			Name: "Alex",
		}
		responseBody = `{ "result": {"age": 35} }`
		res, err = rpcClient.CallBatch(RPCRequests{
			{"something", Params(1, 2, 3)},
		})
		<-requestChan
		Expect(err).To(BeNil())
		Expect(res.Error).To(BeNil())
		err = res.GetObject(p2)
		Expect(err).To(BeNil())
		Expect(p2.Name).To(Equal("Alex"))
		Expect(p2.Age).To(Equal(35))

		// prefilled struct is kept on no result
		p3 := Person{
			Name: "Alex",
		}
		responseBody = `{ "result": null }`
		res, err = rpcClient.CallBatch(RPCRequests{
			{"something", Params(1, 2, 3)},
		})
		<-requestChan
		Expect(err).To(BeNil())
		Expect(res.Error).To(BeNil())
		err = res.GetObject(&p3)
		Expect(err).To(BeNil())
		Expect(p3.Name).To(Equal("Alex"))

		// prefilled struct is extended / overwritten
		p3 = Person{
			Name: "Alex",
			Age:  123,
		}
		responseBody = `{ "result": {"age": 35, "country": "Germany"} }`
		res, err = rpcClient.CallBatch(RPCRequests{
			{"something", Params(1, 2, 3)},
		})
		<-requestChan
		Expect(err).To(BeNil())
		Expect(res.Error).To(BeNil())
		err = res.GetObject(&p3)
		Expect(err).To(BeNil())
		Expect(p3.Name).To(Equal("Alex"))
		Expect(p3.Age).To(Equal(35))
		Expect(p3.Country).To(Equal("Germany"))

		// nil is an error
		responseBody = `{ "result": {"age": 35} }`
		res, err = rpcClient.CallBatch(RPCRequests{
			{"something", Params(1, 2, 3)},
		})
		<-requestChan
		Expect(err).To(BeNil())
		Expect(res.Error).To(BeNil())
		err = res.GetObject(nil)
		Expect(err).NotTo(BeNil())
	*/
}

func TestRpcClient_CallFor(t *testing.T) {
	RegisterTestingT(t)
	rpcClient := NewClient(httpServer.URL)

	i := 0
	responseBody = `{"result":3,"id":0,"jsonrpc":"2.0"}`
	err := rpcClient.CallFor(&i, "something", 1, 2, 3)
	<-requestChan
	Expect(err).To(BeNil())
	Expect(i).To(Equal(3))

	/*
		i = 3
		responseBody = `{"result":null,"id":0,"jsonrpc":"2.0"}`
		err = rpcClient.CallFor(&i, "something", 1, 2, 3)
		<-requestChan
		Expect(err).To(BeNil())
		// i is not modified when result is empty since null (nil) value cannot be stored in int
		Expect(i).To(Equal(3))

		var pi *int
		responseBody = `{"result":4,"id":0,"jsonrpc":"2.0"}`
		err = rpcClient.CallFor(pi, "something", 1, 2, 3)
		<-requestChan
		Expect(err).NotTo(BeNil())
		Expect(pi).To(BeNil())

		responseBody = `{"result":4,"id":0,"jsonrpc":"2.0"}`
		err = rpcClient.CallFor(&pi, "something", 1, 2, 3)
		<-requestChan
		Expect(err).To(BeNil())
		Expect(*pi).To(Equal(4))

		*pi = 3
		responseBody = `{"result":null,"id":0,"jsonrpc":"2.0"}`
		err = rpcClient.CallFor(&pi, "something", 1, 2, 3)
		<-requestChan
		Expect(err).To(BeNil())
		// since pi has a value it is not overwritten by null result
		Expect(pi).To(BeNil())

		p := &Person{}
		responseBody = `{"result":null,"id":0,"jsonrpc":"2.0"}`
		err = rpcClient.CallFor(p, "something", 1, 2, 3)
		<-requestChan
		Expect(err).To(BeNil())
		// p is not changed since it has a value and result is null
		Expect(p).NotTo(BeNil())

		var p2 *Person
		responseBody = `{"result":null,"id":0,"jsonrpc":"2.0"}`
		err = rpcClient.CallFor(p2, "something", 1, 2, 3)
		<-requestChan
		Expect(err).NotTo(BeNil())
		// p is not changed since it has a value and result is null
		Expect(p2).To(BeNil())

		p3 := Person{}
		responseBody = `{"result":null,"id":0,"jsonrpc":"2.0"}`
		err = rpcClient.CallFor(&p3, "something", 1, 2, 3)
		<-requestChan
		Expect(err).To(BeNil())
		// p is not changed since it has a value and result is null
		Expect(p).NotTo(BeNil())

		p = &Person{Age: 35}
		responseBody = `{"result":{"name":"Alex"},"id":0,"jsonrpc":"2.0"}`
		err = rpcClient.CallFor(p, "something", 1, 2, 3)
		<-requestChan
		Expect(err).To(BeNil())
		// p is not changed since it has a value and result is null
		Expect(p.Name).To(Equal("Alex"))
		Expect(p.Age).To(Equal(35))

		p2 = nil
		responseBody = `{"result":{"name":"Alex"},"id":0,"jsonrpc":"2.0"}`
		err = rpcClient.CallFor(p2, "something", 1, 2, 3)
		<-requestChan
		Expect(err).NotTo(BeNil())
		// p is not changed since it has a value and result is null
		Expect(p2).To(BeNil())

		p2 = nil
		responseBody = `{"result":{"name":"Alex"},"id":0,"jsonrpc":"2.0"}`
		err = rpcClient.CallFor(&p2, "something", 1, 2, 3)
		<-requestChan
		Expect(err).To(BeNil())
		// p is not changed since it has a value and result is null
		Expect(p2).NotTo(BeNil())
		Expect(p2.Name).To(Equal("Alex"))

		p3 = Person{Age: 35}
		responseBody = `{"result":{"name":"Alex"},"id":0,"jsonrpc":"2.0"}`
		err = rpcClient.CallFor(&p3, "something", 1, 2, 3)
		<-requestChan
		Expect(err).To(BeNil())
		// p is not changed since it has a value and result is null
		Expect(p.Name).To(Equal("Alex"))
		Expect(p.Age).To(Equal(35))

		p3 = Person{Age: 35}
		responseBody = `{"result":{"name":"Alex"},"id":0,"jsonrpc":"2.0"}`
		err = rpcClient.CallFor(&p3, "something", 1, 2, 3)
		<-requestChan
		Expect(err).To(BeNil())
		// p is not changed since it has a value and result is null
		Expect(p.Name).To(Equal("Alex"))
		Expect(p.Age).To(Equal(35))

		var intArray []int
		responseBody = `{"result":[1, 2, 3],"id":0,"jsonrpc":"2.0"}`
		err = rpcClient.CallFor(&intArray, "something", 1, 2, 3)
		<-requestChan
		Expect(err).To(BeNil())
		// p is not changed since it has a value and result is null
		Expect(intArray).To(ContainElement(1))
		Expect(intArray).To(ContainElement(2))
		Expect(intArray).To(ContainElement(3))*/
}

type Person struct {
	Name    string `json:"name"`
	Age     int    `json:"age"`
	Country string `json:"country"`
}

type PointerFieldPerson struct {
	Name    *string `json:"name"`
	Age     *int    `json:"age"`
	Country *string `json:"country"`
}

type Drink struct {
	Name        string   `json:"name"`
	Ingredients []string `json:"ingredients"`
}

type Planet struct {
	Name       string     `json:"name"`
	Properties Properties `json:"properties"`
}

type Properties struct {
	Distance int    `json:"distance"`
	Color    string `json:"color"`
}