package fastjson

import (
	"fmt"
	"testing"
	"time"
)

func TestGetStringConcurrent(t *testing.T) {
	const concurrency = 4
	data := []byte(largeFixture)

	ch := make(chan error, concurrency)

	for range concurrency {
		go func() {
			s := GetString(data, "non-existing-key")
			if s != "" {
				ch <- fmt.Errorf("unexpected non-empty string got: %q", s)
			}
			ch <- nil
		}()
	}

	for range concurrency {
		select {
		case <-time.After(time.Second * 5):
			t.Fatalf("timeout")
		case err := <-ch:
			if err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
		}
	}
}

func TestGetBytesConcurrent(t *testing.T) {
	const concurrency = 4
	data := []byte(largeFixture)

	ch := make(chan error, concurrency)

	for range concurrency {
		go func() {
			b := GetBytes(data, "non-existing-key")
			if b != nil {
				ch <- fmt.Errorf("unexpected non-empty string got: %q", b)
			}
			ch <- nil
		}()
	}

	for range concurrency {
		select {
		case <-time.After(time.Second * 5):
			t.Fatalf("timeout")
		case err := <-ch:
			if err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
		}
	}
}

func TestGetString(t *testing.T) {
	data := []byte(`{"foo":"bar", "baz": 1234}`)

	// normal path
	s := GetString(data, "foo")
	if s != "bar" {
		t.Fatalf("unexpected value obtained; got %q; want %q", s, "bar")
	}

	// non-existing path
	s = GetString(data, "foo", "zzz")
	if s != "" {
		t.Fatalf("unexpected non-empty value obtained: %q", s)
	}

	// invalid type
	s = GetString(data, "baz")
	if s != "" {
		t.Fatalf("unexpected non-empty value obtained: %q", s)
	}

	// invalid json
	s = GetString([]byte("invalid json"), "foobar", "baz")
	if s != "" {
		t.Fatalf("unexpected non-empty value obtained: %q", s)
	}
}

func TestGetBytes(t *testing.T) {
	data := []byte(`{"foo":"bar", "baz": 1234}`)

	// normal path
	b := GetBytes(data, "foo")
	if string(b) != "bar" {
		t.Fatalf("unexpected value obtained; got %q; want %q", b, "bar")
	}

	// non-existing path
	b = GetBytes(data, "foo", "zzz")
	if b != nil {
		t.Fatalf("unexpected non-empty value obtained: %q", b)
	}

	// invalid type
	b = GetBytes(data, "baz")
	if b != nil {
		t.Fatalf("unexpected non-empty value obtained: %q", b)
	}

	// invalid json
	b = GetBytes([]byte("invalid json"), "foobar", "baz")
	if b != nil {
		t.Fatalf("unexpected non-empty value obtained: %q", b)
	}
}

func TestGetInt(t *testing.T) {
	data := []byte(`{"foo":"bar", "baz": 1234}`)

	// normal path
	n := GetInt(data, "baz")
	if n != 1234 {
		t.Fatalf("unexpected value obtained; got %d; want %d", n, 1234)
	}

	// non-existing path
	n = GetInt(data, "foo", "zzz")
	if n != 0 {
		t.Fatalf("unexpected non-zero value obtained: %d", n)
	}

	// invalid type
	n = GetInt(data, "foo")
	if n != 0 {
		t.Fatalf("unexpected non-zero value obtained: %d", n)
	}

	// invalid json
	n = GetInt([]byte("invalid json"), "foobar", "baz")
	if n != 0 {
		t.Fatalf("unexpected non-empty value obtained: %d", n)
	}
}

func TestGetFloat64(t *testing.T) {
	data := []byte(`{"foo":"bar", "baz": 12.34}`)

	// normal path
	f := GetFloat64(data, "baz")
	if f != 12.34 {
		t.Fatalf("unexpected value obtained; got %f; want %f", f, 12.34)
	}

	// non-existing path
	f = GetFloat64(data, "foo", "zzz")
	if f != 0 {
		t.Fatalf("unexpected non-zero value obtained: %f", f)
	}

	// invalid type
	f = GetFloat64(data, "foo")
	if f != 0 {
		t.Fatalf("unexpected non-zero value obtained: %f", f)
	}

	// invalid json
	f = GetFloat64([]byte("invalid json"), "foobar", "baz")
	if f != 0 {
		t.Fatalf("unexpected non-empty value obtained: %f", f)
	}
}

func TestGetBool(t *testing.T) {
	data := []byte(`{"foo":"bar", "baz": true}`)

	// normal path
	b := GetBool(data, "baz")
	if !b {
		t.Fatalf("unexpected value obtained; got %v; want %v", b, true)
	}

	// non-existing path
	b = GetBool(data, "foo", "zzz")
	if b {
		t.Fatalf("unexpected true value obtained")
	}

	// invalid type
	b = GetBool(data, "foo")
	if b {
		t.Fatalf("unexpected true value obtained")
	}

	// invalid json
	b = GetBool([]byte("invalid json"), "foobar", "baz")
	if b {
		t.Fatalf("unexpected true value obtained")
	}
}

func TestExists(t *testing.T) {
	data := []byte(`{"foo": [{"bar": 1234, "baz": 0}]}`)

	if !Exists(data, "foo") {
		t.Fatalf("cannot find foo")
	}
	if !Exists(data, "foo", "0") {
		t.Fatalf("cannot find foo[0]")
	}
	if !Exists(data, "foo", "0", "baz") {
		t.Fatalf("cannot find foo[0].baz")
	}

	if Exists(data, "foobar") {
		t.Fatalf("found unexpected foobar")
	}
	if Exists(data, "foo", "1") {
		t.Fatalf("found unexpected foo[1]")
	}
	if Exists(data, "foo", "0", "234") {
		t.Fatalf("found unexpected foo[0][234]")
	}
	if Exists(data, "foo", "bar") {
		t.Fatalf("found unexpected foo.bar")
	}

	if Exists([]byte(`invalid JSON`), "foo", "bar") {
		t.Fatalf("Exists returned true on invalid json")
	}
}

func TestParse(t *testing.T) {
	v, err := Parse(`{"foo": "bar"}`)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	str := v.String()
	if str != `{"foo":"bar"}` {
		t.Fatalf("unexpected value parsed: %q; want %q", str, `{"foo":"bar"}`)
	}
}

func TestParseBytes(t *testing.T) {
	v, err := ParseBytes([]byte(`{"foo": "bar"}`))
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	str := v.String()
	if str != `{"foo":"bar"}` {
		t.Fatalf("unexpected value parsed: %q; want %q", str, `{"foo":"bar"}`)
	}
}

func TestMustParse(t *testing.T) {
	s := `{"foo":"bar"}`
	v := MustParse(s)
	str := v.String()
	if str != s {
		t.Fatalf("unexpected value parsed; %q; want %q", str, s)
	}

	v = MustParseBytes([]byte(s))
	if str != s {
		t.Fatalf("unexpected value parsed; %q; want %q", str, s)
	}

	if !causesPanic(func() { v = MustParse(`[`) }) {
		t.Fatalf("expected MustParse to panic")
	}

	if !causesPanic(func() { v = MustParseBytes([]byte(`[`)) }) {
		t.Fatalf("expected MustParse to panic")
	}
}

func causesPanic(fn func()) (p bool) {
	defer func() {
		if r := recover(); r != nil {
			p = true
		}
	}()
	fn()
	return
}
