package syncz

import (
	"context"
	"testing"

	"github.com/ash2k/stager"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestRunLayers(t *testing.T) {
	c := make(chan int, 4)
	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	err := RunLayers(ctx,
		LayerFunc(func(stage stager.Stage) {
			stage.Go(func(ctx context.Context) error {
				<-ctx.Done()
				c <- 1
				return nil
			})
		}),
		LayerGoFunc(func(ctx context.Context) error {
			<-ctx.Done()
			c <- 2
			return nil
		}),
		SiblingLayers{
			LayerGoFunc(func(ctx context.Context) error {
				<-ctx.Done()
				c <- 3
				return nil

			}),
			LayerGoFunc(func(ctx context.Context) error {
				<-ctx.Done()
				c <- 3
				return nil
			}),
		},
	)
	require.NoError(t, err)
	assert.Equal(t, 3, <-c)
	assert.Equal(t, 3, <-c)
	assert.Equal(t, 2, <-c)
	assert.Equal(t, 1, <-c)
	assert.Empty(t, c)
}

func TestSiblingLayersAB(t *testing.T) {
	a, a1, a2, b, b1 := setup(t)
	sl := SiblingLayers{a, b}
	ctx, cancel := context.WithCancel(context.Background())
	cancel() // straight away
	err := RunLayers(ctx, sl)
	require.NoError(t, err)
	<-a1
	<-a2
	<-b1
}

func TestSiblingLayersBA(t *testing.T) {
	a, a1, a2, b, b1 := setup(t)
	sl := SiblingLayers{b, a}
	ctx, cancel := context.WithCancel(context.Background())
	cancel() // straight away
	err := RunLayers(ctx, sl)
	require.NoError(t, err)
	<-a1
	<-a2
	<-b1
}

type testLayer []stager.StageFunc

func (l testLayer) ToStageFuncs() ([]stager.StageFunc, error) {
	return l, nil
}

func setup(t *testing.T) (testLayer, chan struct{}, chan struct{}, testLayer, chan struct{}) {
	a1 := make(chan struct{})
	a2 := make(chan struct{})
	signal := make(chan struct{})
	a := testLayer{
		func(stage stager.Stage) {
			stage.Go(func(ctx context.Context) error {
				<-ctx.Done()
				close(a1)
				return nil
			})
		},
		func(stage stager.Stage) {
			stage.Go(func(ctx context.Context) error {
				<-ctx.Done()
				close(a2)
				<-signal
				return nil
			})
		},
	}
	b1 := make(chan struct{})
	b := testLayer{
		func(stage stager.Stage) {
			stage.Go(func(ctx context.Context) error {
				<-ctx.Done()
				close(b1)
				<-a2 // was canceled
				select {
				case <-a1:
					assert.Fail(t, "a1 was unblocked unexpectedly")
				default:
				}
				close(signal) // let a2 finish
				<-a1          // wait for a1 to finish
				return nil
			})
		},
	}
	return a, a1, a2, b, b1
}
