package locker_test

import (
	"context"
	"errors"
	"testing"

	"github.com/matryer/is"

	"go.sour.is/pkg/locker"
)

type config struct {
	Value   string
	Counter int
}

func TestLocker(t *testing.T) {
	is := is.New(t)

	value := locker.New(&config{})

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	err := value.Use(ctx, func(ctx context.Context, c *config) error {
		c.Value = "one"
		c.Counter++
		return nil
	})
	is.NoErr(err)

	c, err := value.Copy(context.Background())

	is.NoErr(err)
	is.Equal(c.Value, "one")
	is.Equal(c.Counter, 1)

	wait := make(chan struct{})

	go value.Use(ctx, func(ctx context.Context, c *config) error {
		c.Value = "two"
		c.Counter++
		close(wait)
		return nil
	})

	<-wait
	cancel()

	err = value.Use(ctx, func(ctx context.Context, c *config) error {
		c.Value = "three"
		c.Counter++
		return nil
	})
	is.True(err != nil)

	c, err = value.Copy(context.Background())

	is.NoErr(err)
	is.Equal(c.Value, "two")
	is.Equal(c.Counter, 2)
}

func TestNestedLocker(t *testing.T) {
	is := is.New(t)

	value := locker.New(&config{})
	other := locker.New(&config{})

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	err := value.Use(ctx, func(ctx context.Context, c *config) error {
		return value.Use(ctx, func(ctx context.Context, t *config) error {
			return nil
		})
	})
	is.True(errors.Is(err, locker.ErrNested))

	err = value.Use(ctx, func(ctx context.Context, c *config) error {
		return other.Use(ctx, func(ctx context.Context, t *config) error {
			return nil
		})
	})
	is.NoErr(err)

	err = value.Use(ctx, func(ctx context.Context, c *config) error {
		return other.Use(ctx, func(ctx context.Context, t *config) error {
			return value.Use(ctx, func(ctx context.Context, x *config) error {
				return nil
			})
		})
	})
	is.True(errors.Is(err, locker.ErrNested))
}
