package collections_test

import (
	"context"
	"github.com/MontFerret/ferret/pkg/runtime/collections"
	"github.com/MontFerret/ferret/pkg/runtime/core"
	"github.com/MontFerret/ferret/pkg/runtime/values"
	. "github.com/smartystreets/goconvey/convey"
	"testing"
)

func tapIterator(values collections.Iterator, predicate core.Expression) collections.Iterator {
	iter, _ := collections.NewTapIterator(values, predicate)

	return iter
}

type TestExpression struct {
	fn func(ctx context.Context, scope *core.Scope) (core.Value, error)
}

func (exp *TestExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
	return exp.fn(ctx, scope)
}

type ErrorIterator struct{}

func (iterator *ErrorIterator) Next(ctx context.Context, scope *core.Scope) (*core.Scope, error) {
	return nil, core.ErrInvalidOperation
}

func TestTapIterator(t *testing.T) {
	Convey("Should iterate over a given iterator and execute a predicate", t, func() {
		arr := values.NewArrayWith(
			values.NewInt(1),
			values.NewInt(2),
			values.NewInt(3),
			values.NewInt(4),
			values.NewInt(5),
		)

		counter := 0

		iter := tapIterator(arrayIterator(arr), &TestExpression{
			fn: func(ctx context.Context, scope *core.Scope) (core.Value, error) {
				counter++

				return values.None, nil
			},
		})

		ctx := context.Background()
		scope, _ := core.NewRootScope()
		res, err := collections.ToSlice(ctx, scope, iter)

		So(err, ShouldBeNil)
		So(res, ShouldHaveLength, int(arr.Length()))
		So(counter, ShouldEqual, int(arr.Length()))
	})

	Convey("Should stop when a predicate return an error", t, func() {
		arr := values.NewArrayWith(
			values.NewInt(1),
			values.NewInt(2),
			values.NewInt(3),
			values.NewInt(4),
			values.NewInt(5),
		)

		counter := 0

		iter := tapIterator(arrayIterator(arr), &TestExpression{
			fn: func(ctx context.Context, scope *core.Scope) (core.Value, error) {
				counter++

				return values.None, core.ErrInvalidOperation
			},
		})

		ctx := context.Background()
		scope, _ := core.NewRootScope()
		_, err := collections.ToSlice(ctx, scope, iter)

		So(err, ShouldNotBeNil)
		So(counter, ShouldEqual, 1)
	})

	Convey("Should not invoke a predicate when underlying iterator returns error", t, func() {
		counter := 0

		iter := tapIterator(&ErrorIterator{}, &TestExpression{
			fn: func(ctx context.Context, scope *core.Scope) (core.Value, error) {
				counter++

				return values.None, core.ErrInvalidOperation
			},
		})

		ctx := context.Background()
		scope, _ := core.NewRootScope()
		_, err := collections.ToSlice(ctx, scope, iter)

		So(err, ShouldNotBeNil)
		So(counter, ShouldEqual, 0)
	})

	Convey("Should not invoke a predicate when underlying iterator is empty", t, func() {
		arr := values.NewArray(0)

		counter := 0

		iter := tapIterator(arrayIterator(arr), &TestExpression{
			fn: func(ctx context.Context, scope *core.Scope) (core.Value, error) {
				counter++

				return values.None, core.ErrInvalidOperation
			},
		})

		ctx := context.Background()
		scope, _ := core.NewRootScope()
		res, err := collections.ToSlice(ctx, scope, iter)

		So(err, ShouldBeNil)
		So(res, ShouldHaveLength, 0)
		So(counter, ShouldEqual, 0)
	})
}
