package main

import (
	"container/list"
	"testing"
)

func makeTestWorkList(ary []int) *list.List {
	l := list.New()
	for _, n := range ary {
		l.PushBack(n)
	}
	return l
}

func expectChannelEmpty(t *testing.T, c <-chan interface{}) {
	select {
	case item := <-c:
		t.Fatalf("Received value (%v) from channel that we expected to be empty", item)
	default:
		// no-op
	}
}

func expectChannelNotEmpty(t *testing.T, c <-chan interface{}) {
	if item, ok := <-c; !ok {
		t.Fatal("expected data on a closed channel")
	} else if item == nil {
		t.Fatal("expected data on an empty channel")
	}
}

func expectChannelClosed(t *testing.T, c <-chan interface{}) {
	received, ok := <-c
	if ok {
		t.Fatalf("Expected channel to be closed, but received %v instead", received)
	}
}

func expectFromChannel(t *testing.T, c <-chan interface{}, expected []int) {
	for i := range expected {
		actual, ok := <-c
		t.Logf("received %v", actual)
		if !ok {
			t.Fatalf("Expected %v but channel was closed after receiving the first %d elements correctly.", expected, i)
		} else if actual.(int) != expected[i] {
			t.Fatalf("Expected %v but received '%v' after receiving the first %d elements correctly.", expected[i], actual, i)
		}
	}
}

// Create a WorkQueue, generate a list for it, and instantiate a worker.
func TestWorkQueueReadWrite(t *testing.T) {
	var input = []int{1, 1, 2, 3, 5, 8, 13, 21, 34}

	b := NewWorkQueue()
	b.ReplaceQueue(makeTestWorkList(input))

	expectFromChannel(t, b.NextItem, input)
	expectChannelEmpty(t, b.NextItem)
	b.Close()
}

// Start a worker before the list has any input.
func TestWorkQueueEarlyRead(t *testing.T) {
	var input = []int{1, 1, 2, 3, 5, 8, 13, 21, 34}

	b := NewWorkQueue()

	// First, demonstrate that nothing is available on the NextItem
	// channel.
	expectChannelEmpty(t, b.NextItem)

	// Start a reader in a goroutine. The reader will block until the
	// block work list has been initialized.
	//
	done := make(chan int)
	go func() {
		expectFromChannel(t, b.NextItem, input)
		b.Close()
		done <- 1
	}()

	// Feed the blocklist a new worklist, and wait for the worker to
	// finish.
	b.ReplaceQueue(makeTestWorkList(input))
	<-done

	expectChannelClosed(t, b.NextItem)
}

// Show that a reader may block when the manager's list is exhausted,
// and that the reader resumes automatically when new data is
// available.
func TestWorkQueueReaderBlocks(t *testing.T) {
	var (
		inputBeforeBlock = []int{1, 2, 3, 4, 5}
		inputAfterBlock  = []int{6, 7, 8, 9, 10}
	)

	b := NewWorkQueue()
	sendmore := make(chan int)
	done := make(chan int)
	go func() {
		expectFromChannel(t, b.NextItem, inputBeforeBlock)

		// Confirm that the channel is empty, so a subsequent read
		// on it will block.
		expectChannelEmpty(t, b.NextItem)

		// Signal that we're ready for more input.
		sendmore <- 1
		expectFromChannel(t, b.NextItem, inputAfterBlock)
		b.Close()
		done <- 1
	}()

	// Write a slice of the first five elements and wait for the
	// reader to signal that it's ready for us to send more input.
	b.ReplaceQueue(makeTestWorkList(inputBeforeBlock))
	<-sendmore

	b.ReplaceQueue(makeTestWorkList(inputAfterBlock))

	// Wait for the reader to complete.
	<-done
}

// Replace one active work list with another.
func TestWorkQueueReplaceQueue(t *testing.T) {
	var firstInput = []int{1, 1, 2, 3, 5, 8, 13, 21, 34}
	var replaceInput = []int{1, 4, 9, 16, 25, 36, 49, 64, 81}

	b := NewWorkQueue()
	b.ReplaceQueue(makeTestWorkList(firstInput))

	// Read just the first five elements from the work list.
	// Confirm that the channel is not empty.
	expectFromChannel(t, b.NextItem, firstInput[0:5])
	expectChannelNotEmpty(t, b.NextItem)

	// Replace the work list and read five more elements.
	// The old list should have been discarded and all new
	// elements come from the new list.
	b.ReplaceQueue(makeTestWorkList(replaceInput))
	expectFromChannel(t, b.NextItem, replaceInput[0:5])

	b.Close()
}