package v2

import (
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

type eventA struct {
	priorityNormal
}

var errDone = fmt.Errorf("done")

func simpleHandler(event Event) (Event, error) {
	if _, ok := event.(eventA); ok {
		return noOp, errDone
	}
	return noOp, nil
}

func TestRoutineFinal(t *testing.T) {
	var (
		bufferSize = 10
		routine    = newRoutine("simpleRoutine", simpleHandler, bufferSize)
	)

	assert.False(t, routine.isRunning(),
		"expected an initialized routine to not be running")
	go routine.start()
	<-routine.ready()
	assert.True(t, routine.isRunning(),
		"expected an started routine")

	assert.True(t, routine.send(eventA{}),
		"expected sending to a ready routine to succeed")

	assert.Equal(t, errDone, <-routine.final(),
		"expected the final event to be done")

	assert.False(t, routine.isRunning(),
		"expected an completed routine to no longer be running")
}

func TestRoutineStop(t *testing.T) {
	var (
		bufferSize = 10
		routine    = newRoutine("simpleRoutine", simpleHandler, bufferSize)
	)

	assert.False(t, routine.send(eventA{}),
		"expected sending to an unstarted routine to fail")

	go routine.start()
	<-routine.ready()

	assert.True(t, routine.send(eventA{}),
		"expected sending to a running routine to succeed")

	routine.stop()

	assert.False(t, routine.send(eventA{}),
		"expected sending to a stopped routine to fail")
}

type finalCount struct {
	count int
}

func (f finalCount) Error() string {
	return "end"
}

func genStatefulHandler(maxCount int) handleFunc {
	counter := 0
	return func(event Event) (Event, error) {
		if _, ok := event.(eventA); ok {
			counter++
			if counter >= maxCount {
				return noOp, finalCount{counter}
			}

			return eventA{}, nil
		}
		return noOp, nil
	}
}

func feedback(r *Routine) {
	for event := range r.next() {
		r.send(event)
	}
}

func TestStatefulRoutine(t *testing.T) {
	var (
		count      = 10
		handler    = genStatefulHandler(count)
		bufferSize = 20
		routine    = newRoutine("statefulRoutine", handler, bufferSize)
	)

	go routine.start()
	go feedback(routine)
	<-routine.ready()

	assert.True(t, routine.send(eventA{}),
		"expected sending to a started routine to succeed")

	final := <-routine.final()
	if fnl, ok := final.(finalCount); ok {
		assert.Equal(t, count, fnl.count,
			"expected the routine to count to 10")
	} else {
		t.Fail()
	}
}

type lowPriorityEvent struct {
	priorityLow
}

type highPriorityEvent struct {
	priorityHigh
}

func handleWithPriority(event Event) (Event, error) {
	switch event.(type) {
	case lowPriorityEvent:
		return noOp, nil
	case highPriorityEvent:
		return noOp, errDone
	}
	return noOp, nil
}

func TestPriority(t *testing.T) {
	var (
		bufferSize = 20
		routine    = newRoutine("priorityRoutine", handleWithPriority, bufferSize)
	)

	go routine.start()
	<-routine.ready()
	go func() {
		for {
			routine.send(lowPriorityEvent{})
			time.Sleep(1 * time.Millisecond)
		}
	}()
	time.Sleep(10 * time.Millisecond)

	assert.True(t, routine.isRunning(),
		"expected an started routine")
	assert.True(t, routine.send(highPriorityEvent{}),
		"expected send to succeed even when saturated")

	assert.Equal(t, errDone, <-routine.final())
	assert.False(t, routine.isRunning(),
		"expected an started routine")
}
