package binary

import (
	"bytes"
	"fmt"
	"reflect"
	"testing"
	"time"

	. "github.com/tendermint/tendermint/common"
)

type SimpleStruct struct {
	String string
	Bytes  []byte
	Time   time.Time
}

//-------------------------------------

type Animal interface{}

const (
	AnimalTypeCat   = byte(0x01)
	AnimalTypeDog   = byte(0x02)
	AnimalTypeSnake = byte(0x03)
	AnimalTypeViper = byte(0x04)
)

// Implements Animal
type Cat struct {
	SimpleStruct
}

// Implements Animal
type Dog struct {
	SimpleStruct
}

// Implements Animal
type Snake []byte

// Implements Animal
type Viper struct {
	Bytes []byte
}

var _ = RegisterInterface(
	struct{ Animal }{},
	ConcreteType{Cat{}, AnimalTypeCat},
	ConcreteType{Dog{}, AnimalTypeDog},
	ConcreteType{Snake{}, AnimalTypeSnake},
	ConcreteType{&Viper{}, AnimalTypeViper},
)

func TestAnimalInterface(t *testing.T) {
	var foo Animal

	// Type of pointer to Animal
	rt := reflect.TypeOf(&foo)
	fmt.Printf("rt: %v\n", rt)

	// Type of Animal itself.
	// NOTE: normally this is acquired through other means
	// like introspecting on method signatures, or struct fields.
	rte := rt.Elem()
	fmt.Printf("rte: %v\n", rte)

	// Get a new pointer to the interface
	// NOTE: calling .Interface() is to get the actual value,
	// instead of reflection values.
	ptr := reflect.New(rte).Interface()
	fmt.Printf("ptr: %v", ptr)

	// Make a binary byteslice that represents a *snake.
	foo = Snake([]byte("snake"))
	snakeBytes := BinaryBytes(foo)
	snakeReader := bytes.NewReader(snakeBytes)

	// Now you can read it.
	n, err := new(int64), new(error)
	it := ReadBinary(foo, snakeReader, n, err).(Animal)
	fmt.Println(it, reflect.TypeOf(it))
}

//-------------------------------------

type Constructor func() interface{}
type Instantiator func() (o interface{}, ptr interface{})
type Validator func(o interface{}, t *testing.T)

type TestCase struct {
	Constructor
	Instantiator
	Validator
}

//-------------------------------------

func constructBasic() interface{} {
	cat := Cat{
		SimpleStruct{
			String: "String",
			Bytes:  []byte("Bytes"),
			Time:   time.Unix(123, 0),
		},
	}
	return cat
}

func instantiateBasic() (interface{}, interface{}) {
	return Cat{}, &Cat{}
}

func validateBasic(o interface{}, t *testing.T) {
	cat := o.(Cat)
	if cat.String != "String" {
		t.Errorf("Expected cat.String == 'String', got %v", cat.String)
	}
	if string(cat.Bytes) != "Bytes" {
		t.Errorf("Expected cat.Bytes == 'Bytes', got %X", cat.Bytes)
	}
	if cat.Time.Unix() != 123 {
		t.Errorf("Expected cat.Time == 'Unix(123)', got %v", cat.Time)
	}
}

//-------------------------------------

type NilTestStruct struct {
	IntPtr *int
	CatPtr *Cat
	Animal Animal
}

func constructNilTestStruct() interface{} {
	return NilTestStruct{}
}

func instantiateNilTestStruct() (interface{}, interface{}) {
	return NilTestStruct{}, &NilTestStruct{}
}

func validateNilTestStruct(o interface{}, t *testing.T) {
	nts := o.(NilTestStruct)
	if nts.IntPtr != nil {
		t.Errorf("Expected nts.IntPtr to be nil, got %v", nts.IntPtr)
	}
	if nts.CatPtr != nil {
		t.Errorf("Expected nts.CatPtr to be nil, got %v", nts.CatPtr)
	}
	if nts.Animal != nil {
		t.Errorf("Expected nts.Animal to be nil, got %v", nts.Animal)
	}
}

//-------------------------------------

type ComplexStruct struct {
	Name   string
	Animal Animal
}

func constructComplex() interface{} {
	c := ComplexStruct{
		Name:   "Complex",
		Animal: constructBasic(),
	}
	return c
}

func instantiateComplex() (interface{}, interface{}) {
	return ComplexStruct{}, &ComplexStruct{}
}

func validateComplex(o interface{}, t *testing.T) {
	c2 := o.(ComplexStruct)
	if cat, ok := c2.Animal.(Cat); ok {
		validateBasic(cat, t)
	} else {
		t.Errorf("Expected c2.Animal to be of type cat, got %v", reflect.ValueOf(c2.Animal).Elem().Type())
	}
}

//-------------------------------------

type ComplexStruct2 struct {
	Cat    Cat
	Dog    *Dog
	Snake  Snake
	Snake2 *Snake
	Viper  Viper
	Viper2 *Viper
}

func constructComplex2() interface{} {
	snake_ := Snake([]byte("hiss"))
	snakePtr_ := &snake_

	c := ComplexStruct2{
		Cat: Cat{
			SimpleStruct{
				String: "String",
				Bytes:  []byte("Bytes"),
			},
		},
		Dog: &Dog{
			SimpleStruct{
				String: "Woof",
				Bytes:  []byte("Bark"),
			},
		},
		Snake:  Snake([]byte("hiss")),
		Snake2: snakePtr_,
		Viper:  Viper{Bytes: []byte("hizz")},
		Viper2: &Viper{Bytes: []byte("hizz")},
	}
	return c
}

func instantiateComplex2() (interface{}, interface{}) {
	return ComplexStruct2{}, &ComplexStruct2{}
}

func validateComplex2(o interface{}, t *testing.T) {
	c2 := o.(ComplexStruct2)
	cat := c2.Cat
	if cat.String != "String" {
		t.Errorf("Expected cat.String == 'String', got %v", cat.String)
	}
	if string(cat.Bytes) != "Bytes" {
		t.Errorf("Expected cat.Bytes == 'Bytes', got %X", cat.Bytes)
	}

	dog := c2.Dog
	if dog.String != "Woof" {
		t.Errorf("Expected dog.String == 'Woof', got %v", dog.String)
	}
	if string(dog.Bytes) != "Bark" {
		t.Errorf("Expected dog.Bytes == 'Bark', got %X", dog.Bytes)
	}

	snake := c2.Snake
	if string(snake) != "hiss" {
		t.Errorf("Expected string(snake) == 'hiss', got %v", string(snake))
	}

	snake2 := c2.Snake2
	if string(*snake2) != "hiss" {
		t.Errorf("Expected string(snake2) == 'hiss', got %v", string(*snake2))
	}

	viper := c2.Viper
	if string(viper.Bytes) != "hizz" {
		t.Errorf("Expected string(viper.Bytes) == 'hizz', got %v", string(viper.Bytes))
	}

	viper2 := c2.Viper2
	if string(viper2.Bytes) != "hizz" {
		t.Errorf("Expected string(viper2.Bytes) == 'hizz', got %v", string(viper2.Bytes))
	}
}

//-------------------------------------

type ComplexStructArray struct {
	Animals []Animal
}

func constructComplexArray() interface{} {
	c := ComplexStructArray{
		Animals: []Animal{
			Cat{
				SimpleStruct{
					String: "String",
					Bytes:  []byte("Bytes"),
				},
			},
			Dog{
				SimpleStruct{
					String: "Woof",
					Bytes:  []byte("Bark"),
				},
			},
			Snake([]byte("hiss")),
			&Viper{
				Bytes: []byte("hizz"),
			},
		},
	}
	return c
}

func instantiateComplexArray() (interface{}, interface{}) {
	return ComplexStructArray{}, &ComplexStructArray{}
}

func validateComplexArray(o interface{}, t *testing.T) {
	c2 := o.(ComplexStructArray)
	if cat, ok := c2.Animals[0].(Cat); ok {
		if cat.String != "String" {
			t.Errorf("Expected cat.String == 'String', got %v", cat.String)
		}
		if string(cat.Bytes) != "Bytes" {
			t.Errorf("Expected cat.Bytes == 'Bytes', got %X", cat.Bytes)
		}
	} else {
		t.Errorf("Expected c2.Animals[0] to be of type cat, got %v", reflect.ValueOf(c2.Animals[0]).Elem().Type())
	}

	if dog, ok := c2.Animals[1].(Dog); ok {
		if dog.String != "Woof" {
			t.Errorf("Expected dog.String == 'Woof', got %v", dog.String)
		}
		if string(dog.Bytes) != "Bark" {
			t.Errorf("Expected dog.Bytes == 'Bark', got %X", dog.Bytes)
		}
	} else {
		t.Errorf("Expected c2.Animals[1] to be of type dog, got %v", reflect.ValueOf(c2.Animals[1]).Elem().Type())
	}

	if snake, ok := c2.Animals[2].(Snake); ok {
		if string(snake) != "hiss" {
			t.Errorf("Expected string(snake) == 'hiss', got %v", string(snake))
		}
	} else {
		t.Errorf("Expected c2.Animals[2] to be of type Snake, got %v", reflect.ValueOf(c2.Animals[2]).Elem().Type())
	}

	if viper, ok := c2.Animals[3].(*Viper); ok {
		if string(viper.Bytes) != "hizz" {
			t.Errorf("Expected string(viper.Bytes) == 'hizz', got %v", string(viper.Bytes))
		}
	} else {
		t.Errorf("Expected c2.Animals[3] to be of type *Viper, got %v", reflect.ValueOf(c2.Animals[3]).Elem().Type())
	}
}

//-----------------------------------------------------------------------------

var testCases = []TestCase{}

func init() {
	testCases = append(testCases, TestCase{constructBasic, instantiateBasic, validateBasic})
	testCases = append(testCases, TestCase{constructComplex, instantiateComplex, validateComplex})
	testCases = append(testCases, TestCase{constructComplex2, instantiateComplex2, validateComplex2})
	testCases = append(testCases, TestCase{constructComplexArray, instantiateComplexArray, validateComplexArray})
	testCases = append(testCases, TestCase{constructNilTestStruct, instantiateNilTestStruct, validateNilTestStruct})
}

func TestBinary(t *testing.T) {

	for i, testCase := range testCases {

		log.Info(fmt.Sprintf("Running test case %v", i))

		// Construct an object
		o := testCase.Constructor()

		// Write the object
		data := BinaryBytes(o)
		t.Logf("Binary: %X", data)

		instance, instancePtr := testCase.Instantiator()

		// Read onto a struct
		n, err := new(int64), new(error)
		res := ReadBinary(instance, bytes.NewReader(data), n, err)
		if *err != nil {
			t.Fatalf("Failed to read into instance: %v", *err)
		}

		// Validate object
		testCase.Validator(res, t)

		// Read onto a pointer
		n, err = new(int64), new(error)
		res = ReadBinaryPtr(instancePtr, bytes.NewReader(data), n, err)
		if *err != nil {
			t.Fatalf("Failed to read into instance: %v", *err)
		}

		if res != instancePtr {
			t.Errorf("Expected pointer to pass through")
		}

		// Validate object
		testCase.Validator(reflect.ValueOf(res).Elem().Interface(), t)
	}

}

func TestJSON(t *testing.T) {

	for i, testCase := range testCases {

		log.Info(fmt.Sprintf("Running test case %v", i))

		// Construct an object
		o := testCase.Constructor()

		// Write the object
		data := JSONBytes(o)
		t.Logf("JSON: %v", string(data))

		instance, instancePtr := testCase.Instantiator()

		// Read onto a struct
		err := new(error)
		res := ReadJSON(instance, data, err)
		if *err != nil {
			t.Fatalf("Failed to read cat: %v", *err)
		}

		// Validate object
		testCase.Validator(res, t)

		// Read onto a pointer
		res = ReadJSON(instancePtr, data, err)
		if *err != nil {
			t.Fatalf("Failed to read cat: %v", *err)
		}

		if res != instancePtr {
			t.Errorf("Expected pointer to pass through")
		}

		// Validate object
		testCase.Validator(reflect.ValueOf(res).Elem().Interface(), t)
	}

}

//------------------------------------------------------------------------------

type Foo struct {
	FieldA string `json:"fieldA"` // json field name is "fieldA"
	FieldB string // json field name is "FieldB"
	fieldC string // not exported, not serialized.
}

func TestJSONFieldNames(t *testing.T) {
	for i := 0; i < 20; i++ { // Try to ensure deterministic success.
		foo := Foo{"a", "b", "c"}
		stringified := string(JSONBytes(foo))
		expected := `{"fieldA":"a","FieldB":"b"}`
		if stringified != expected {
			t.Fatalf("JSONFieldNames error: expected %v, got %v",
				expected, stringified)
		}
	}
}

//------------------------------------------------------------------------------

func TestBadAlloc(t *testing.T) {
	n, err := new(int64), new(error)
	instance := new([]byte)
	data := RandBytes(ByteSliceChunk * 100)
	b := new(bytes.Buffer)
	// this slice of data claims to be much bigger than it really is
	WriteUvarint(uint(10000000000000000), b, n, err)
	b.Write(data)
	res := ReadBinary(instance, b, n, err)
	fmt.Println(res, *err)
}