package objects_test

import (
	"context"
	"testing"

	"github.com/MontFerret/ferret/pkg/runtime/values"
	"github.com/MontFerret/ferret/pkg/stdlib/objects"

	. "github.com/smartystreets/goconvey/convey"
)

func TestValues(t *testing.T) {
	Convey("Invalid arguments", t, func() {
		Convey("When there is no arguments", func() {
			actual, err := objects.Values(context.Background())

			So(err, ShouldBeError)
			So(actual.Compare(values.None), ShouldEqual, 0)
		})

		Convey("When 2 arguments", func() {
			obj := values.NewObjectWith(
				values.NewObjectProperty("k1", values.NewInt(0)),
				values.NewObjectProperty("k2", values.NewInt(1)),
			)

			actual, err := objects.Values(context.Background(), obj, obj)

			So(err, ShouldBeError)
			So(actual.Compare(values.None), ShouldEqual, 0)

			actual, err = objects.Values(context.Background(), obj, values.NewInt(0))

			So(err, ShouldBeError)
			So(actual.Compare(values.None), ShouldEqual, 0)
		})

		Convey("When there is not object argument", func() {
			actual, err := objects.Values(context.Background(), values.NewInt(0))

			So(err, ShouldBeError)
			So(actual.Compare(values.None), ShouldEqual, 0)
		})
	})

	Convey("When simple type attributes (same type)", t, func() {
		obj := values.NewObjectWith(
			values.NewObjectProperty("k1", values.NewInt(0)),
			values.NewObjectProperty("k2", values.NewInt(1)),
		)
		expected := values.NewArrayWith(
			values.NewInt(0), values.NewInt(1),
		).Sort()

		actual, err := objects.Values(context.Background(), obj)
		actualSorted := actual.(*values.Array).Sort()

		So(err, ShouldBeNil)
		So(actualSorted.Compare(expected), ShouldEqual, 0)
	})

	Convey("When simple type attributes (different types)", t, func() {
		obj := values.NewObjectWith(
			values.NewObjectProperty("k1", values.NewInt(0)),
			values.NewObjectProperty("k2", values.NewString("v2")),
		)
		expected := values.NewArrayWith(
			values.NewInt(0), values.NewString("v2"),
		).Sort()

		actual, err := objects.Values(context.Background(), obj)
		actualSorted := actual.(*values.Array).Sort()

		So(err, ShouldBeNil)
		So(actualSorted.Compare(expected), ShouldEqual, 0)
	})

	Convey("When complex type attributes (array)", t, func() {
		arr1 := values.NewArrayWith(
			values.NewInt(0), values.NewInt(1),
		)
		arr2 := values.NewArrayWith(
			values.NewInt(2), values.NewInt(3),
		)
		obj := values.NewObjectWith(
			values.NewObjectProperty("k1", arr1),
			values.NewObjectProperty("k2", arr2),
		)
		expected := values.NewArrayWith(arr1, arr2).Sort()

		actual, err := objects.Values(context.Background(), obj)
		actualSorted := actual.(*values.Array).Sort()

		So(err, ShouldBeNil)
		So(actualSorted.Compare(expected), ShouldEqual, 0)
	})

	Convey("When complex type attributes (object)", t, func() {
		obj1 := values.NewObjectWith(
			values.NewObjectProperty("int0", values.NewInt(0)),
		)
		obj2 := values.NewObjectWith(
			values.NewObjectProperty("int1", values.NewInt(1)),
		)
		obj := values.NewObjectWith(
			values.NewObjectProperty("k1", obj1),
			values.NewObjectProperty("k2", obj2),
		)
		expected := values.NewArrayWith(obj1, obj2).Sort()

		actual, err := objects.Values(context.Background(), obj)
		actualSorted := actual.(*values.Array).Sort()

		So(err, ShouldBeNil)
		So(actualSorted.Compare(expected), ShouldEqual, 0)
	})

	Convey("When complex type attributes (object and array)", t, func() {
		obj1 := values.NewObjectWith(
			values.NewObjectProperty("k1", values.NewInt(0)),
		)
		arr1 := values.NewArrayWith(
			values.NewInt(0), values.NewInt(1),
		)
		obj := values.NewObjectWith(
			values.NewObjectProperty("obj", obj1),
			values.NewObjectProperty("arr", arr1),
		)
		expected := values.NewArrayWith(obj1, arr1).Sort()

		actual, err := objects.Values(context.Background(), obj)
		actualSorted := actual.(*values.Array).Sort()

		So(err, ShouldBeNil)
		So(actualSorted.Compare(expected), ShouldEqual, 0)
	})

	Convey("When both type attributes", t, func() {
		obj1 := values.NewObjectWith(
			values.NewObjectProperty("k1", values.NewInt(0)),
		)
		arr1 := values.NewArrayWith(
			values.NewInt(0), values.NewInt(1),
		)
		int1 := values.NewInt(0)
		obj := values.NewObjectWith(
			values.NewObjectProperty("obj", obj1),
			values.NewObjectProperty("arr", arr1),
			values.NewObjectProperty("int", int1),
		)
		expected := values.NewArrayWith(obj1, arr1, int1).Sort()

		actual, err := objects.Values(context.Background(), obj)
		actualSorted := actual.(*values.Array).Sort()

		So(err, ShouldBeNil)
		So(actualSorted.Compare(expected), ShouldEqual, 0)
	})

	Convey("Result is independent on the source object (array)", t, func() {
		arr := values.NewArrayWith(values.NewInt(0))
		obj := values.NewObjectWith(
			values.NewObjectProperty("arr", arr),
		)
		expected := values.NewArrayWith(
			values.NewArrayWith(
				values.NewInt(0),
			),
		)

		actual, err := objects.Values(context.Background(), obj)
		actualSorted := actual.(*values.Array).Sort()

		So(err, ShouldBeNil)

		arr.Push(values.NewInt(1))

		So(actualSorted.Compare(expected), ShouldEqual, 0)
	})

	Convey("Result is independent on the source object (object)", t, func() {
		nested := values.NewObjectWith(
			values.NewObjectProperty("int", values.NewInt(0)),
		)
		obj := values.NewObjectWith(
			values.NewObjectProperty("nested", nested),
		)
		expected := values.NewArrayWith(
			values.NewObjectWith(
				values.NewObjectProperty("int", values.NewInt(0)),
			),
		)

		actual, err := objects.Values(context.Background(), obj)
		actualSorted := actual.(*values.Array).Sort()

		So(err, ShouldBeNil)

		nested.Set("new", values.NewInt(1))

		So(actualSorted.Compare(expected), ShouldEqual, 0)
	})
}

func TestValuesStress(t *testing.T) {
	Convey("Stress", t, func() {
		for i := 0; i < 100; i++ {
			obj1 := values.NewObjectWith(
				values.NewObjectProperty("int0", values.NewInt(0)),
			)
			obj2 := values.NewObjectWith(
				values.NewObjectProperty("int1", values.NewInt(1)),
			)
			obj := values.NewObjectWith(
				values.NewObjectProperty("k1", obj1),
				values.NewObjectProperty("k2", obj2),
			)
			expected := values.NewArrayWith(obj2, obj1).Sort()

			actual, err := objects.Values(context.Background(), obj)
			actualSorted := actual.(*values.Array).Sort()

			So(err, ShouldBeNil)
			So(actualSorted.Length(), ShouldEqual, expected.Length())
			So(actualSorted.Compare(expected), ShouldEqual, 0)
		}
	})
}
