package objects_test

import (
	"context"
	"testing"

	"github.com/MontFerret/ferret/pkg/runtime/core"
	"github.com/MontFerret/ferret/pkg/runtime/values"
	"github.com/MontFerret/ferret/pkg/stdlib/objects"
	. "github.com/smartystreets/goconvey/convey"
)

func TestKeepKeys(t *testing.T) {
	Convey("When not enought arguments)", t, func() {
		// there is no object
		obj, err := objects.KeepKeys(context.Background())

		So(err, ShouldBeError)
		So(obj, ShouldEqual, values.None)

		// there are no keys
		obj, err = objects.KeepKeys(context.Background(), values.NewObject())

		So(err, ShouldBeError)
		So(obj, ShouldEqual, values.None)
	})

	Convey("When first argument isn't object", t, func() {
		obj, err := objects.KeepKeys(context.Background(), values.NewInt(0))

		So(err, ShouldBeError)
		So(obj, ShouldEqual, values.None)
	})

	Convey("When wrong keys arguments", t, func() {
		obj, err := objects.KeepKeys(context.Background(), values.NewObject(), values.NewInt(0))

		So(err, ShouldBeError)
		So(obj, ShouldEqual, values.None)

		// looks like a valid case
		// but there is another argument besides an array
		obj, err = objects.KeepKeys(context.Background(), values.NewObject(), values.NewArray(0), values.NewInt(0))

		So(err, ShouldBeError)
		So(obj, ShouldEqual, values.None)
	})

	Convey("Result object is independent of the source object", t, func() {
		arr := values.NewArrayWith(values.Int(0))
		obj := values.NewObjectWith(
			values.NewObjectProperty("a", arr),
		)
		resultObj := values.NewObjectWith(
			values.NewObjectProperty("a", values.NewArrayWith(values.Int(0))),
		)

		afterKeepKeys, err := objects.KeepKeys(context.Background(), obj, values.NewString("a"))

		So(err, ShouldBeNil)

		arr.Push(values.NewInt(1))

		So(afterKeepKeys.Compare(resultObj), ShouldEqual, 0)
	})
}

func TestKeepKeysStrings(t *testing.T) {
	Convey("KeepKeys key 'a'", t, func() {
		obj := values.NewObjectWith(
			values.NewObjectProperty("a", values.NewInt(1)),
			values.NewObjectProperty("b", values.NewString("string")),
		)
		resultObj := values.NewObjectWith(
			values.NewObjectProperty("a", values.NewInt(1)),
		)

		afterKeepKeys, err := objects.KeepKeys(context.Background(), obj, values.NewString("a"))

		So(err, ShouldEqual, nil)
		So(afterKeepKeys.Compare(resultObj), ShouldEqual, 0)
	})

	Convey("KeepKeys key doesn't exists", t, func() {
		obj := values.NewObjectWith(
			values.NewObjectProperty("a", values.NewInt(1)),
			values.NewObjectProperty("b", values.NewString("string")),
		)
		resultObj := values.NewObject()

		afterKeepKeys, err := objects.KeepKeys(context.Background(), obj, values.NewString("c"))

		So(err, ShouldEqual, nil)
		So(isEqualObjects(afterKeepKeys.(*values.Object), resultObj), ShouldEqual, true)
	})

	Convey("KeepKeys when there are more keys than object properties", t, func() {
		obj := values.NewObjectWith(
			values.NewObjectProperty("a", values.NewInt(1)),
			values.NewObjectProperty("b", values.NewString("string")),
		)
		resultObj := values.NewObjectWith(
			values.NewObjectProperty("a", values.NewInt(1)),
			values.NewObjectProperty("b", values.NewString("string")),
		)

		afterKeepKeys, err := objects.KeepKeys(context.Background(), obj,
			values.NewString("a"), values.NewString("b"), values.NewString("c"),
		)

		So(err, ShouldEqual, nil)
		So(isEqualObjects(afterKeepKeys.(*values.Object), resultObj), ShouldEqual, true)
	})
}

func TestKeepKeysArray(t *testing.T) {
	Convey("KeepKeys array", t, func() {
		obj := values.NewObjectWith(
			values.NewObjectProperty("a", values.NewInt(1)),
			values.NewObjectProperty("b", values.NewString("string")),
		)
		keys := values.NewArrayWith(values.NewString("a"))
		resultObj := values.NewObjectWith(
			values.NewObjectProperty("a", values.NewInt(1)),
		)

		afterKeepKeys, err := objects.KeepKeys(context.Background(), obj, keys)

		So(err, ShouldEqual, nil)
		So(isEqualObjects(afterKeepKeys.(*values.Object), resultObj), ShouldEqual, true)
	})

	Convey("KeepKeys empty array", t, func() {
		obj := values.NewObjectWith(
			values.NewObjectProperty("a", values.NewInt(1)),
			values.NewObjectProperty("b", values.NewString("string")),
		)
		keys := values.NewArray(0)
		resultObj := values.NewObject()

		afterKeepKeys, err := objects.KeepKeys(context.Background(), obj, keys)

		So(err, ShouldEqual, nil)
		So(isEqualObjects(afterKeepKeys.(*values.Object), resultObj), ShouldEqual, true)
	})

	Convey("KeepKeys when there are more keys than object properties", t, func() {
		obj := values.NewObjectWith(
			values.NewObjectProperty("a", values.NewInt(1)),
			values.NewObjectProperty("b", values.NewString("string")),
		)
		keys := values.NewArrayWith(
			values.NewString("a"), values.NewString("b"), values.NewString("c"),
		)
		resultObj := values.NewObjectWith(
			values.NewObjectProperty("a", values.NewInt(1)),
			values.NewObjectProperty("b", values.NewString("string")),
		)

		afterKeepKeys, err := objects.KeepKeys(context.Background(), obj, keys)

		So(err, ShouldEqual, nil)
		So(isEqualObjects(afterKeepKeys.(*values.Object), resultObj), ShouldEqual, true)
	})

	Convey("When there is not string key", t, func() {
		obj := values.NewObjectWith(
			values.NewObjectProperty("a", values.NewInt(1)),
			values.NewObjectProperty("b", values.NewString("string")),
		)
		keys := values.NewArrayWith(
			values.NewString("a"),
			values.NewInt(0),
		)

		afterKeepKeys, err := objects.KeepKeys(context.Background(), obj, keys)

		So(err, ShouldBeError)
		So(afterKeepKeys, ShouldEqual, values.None)
	})
}

func isEqualObjects(obj1 *values.Object, obj2 *values.Object) bool {
	var val1 core.Value
	var val2 core.Value

	for _, key := range obj1.Keys() {
		val1, _ = obj1.Get(key)
		val2, _ = obj2.Get(key)
		if val1.Compare(val2) != 0 {
			return false
		}
	}
	for _, key := range obj2.Keys() {
		val1, _ = obj1.Get(key)
		val2, _ = obj2.Get(key)
		if val2.Compare(val1) != 0 {
			return false
		}
	}
	return true
}
