package engine

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"strings"
	"testing"
)

func TestOutputAddString(t *testing.T) {
	var testInputs = [][2]string{
		{
			"hello, world!",
			"hello, world!",
		},

		{
			"One\nTwo\nThree",
			"Three",
		},

		{
			"",
			"",
		},

		{
			"A line\nThen another nl-terminated line\n",
			"Then another nl-terminated line",
		},

		{
			"A line followed by an empty line\n\n",
			"",
		},
	}
	for _, testData := range testInputs {
		input := testData[0]
		expectedOutput := testData[1]
		o := NewOutput()
		var output string
		if err := o.AddString(&output); err != nil {
			t.Error(err)
		}
		if n, err := o.Write([]byte(input)); err != nil {
			t.Error(err)
		} else if n != len(input) {
			t.Errorf("Expected %d, got %d", len(input), n)
		}
		o.Close()
		if output != expectedOutput {
			t.Errorf("Last line is not stored as return string.\nInput:   '%s'\nExpected: '%s'\nGot:       '%s'", input, expectedOutput, output)
		}
	}
}

type sentinelWriteCloser struct {
	calledWrite bool
	calledClose bool
}

func (w *sentinelWriteCloser) Write(p []byte) (int, error) {
	w.calledWrite = true
	return len(p), nil
}

func (w *sentinelWriteCloser) Close() error {
	w.calledClose = true
	return nil
}

func TestOutputAddEnv(t *testing.T) {
	input := "{\"foo\": \"bar\", \"answer_to_life_the_universe_and_everything\": 42}"
	o := NewOutput()
	result, err := o.AddEnv()
	if err != nil {
		t.Fatal(err)
	}
	o.Write([]byte(input))
	o.Close()
	if v := result.Get("foo"); v != "bar" {
		t.Errorf("Expected %v, got %v", "bar", v)
	}
	if v := result.GetInt("answer_to_life_the_universe_and_everything"); v != 42 {
		t.Errorf("Expected %v, got %v", 42, v)
	}
	if v := result.Get("this-value-doesnt-exist"); v != "" {
		t.Errorf("Expected %v, got %v", "", v)
	}
}

func TestOutputAddClose(t *testing.T) {
	o := NewOutput()
	var s sentinelWriteCloser
	o.Add(&s)
	if err := o.Close(); err != nil {
		t.Fatal(err)
	}
	// Write data after the output is closed.
	// Write should succeed, but no destination should receive it.
	if _, err := o.Write([]byte("foo bar")); err != nil {
		t.Fatal(err)
	}
	if !s.calledClose {
		t.Fatal("Output.Close() didn't close the destination")
	}
}

func TestOutputAddPipe(t *testing.T) {
	var testInputs = []string{
		"hello, world!",
		"One\nTwo\nThree",
		"",
		"A line\nThen another nl-terminated line\n",
		"A line followed by an empty line\n\n",
	}
	for _, input := range testInputs {
		expectedOutput := input
		o := NewOutput()
		r, err := o.AddPipe()
		if err != nil {
			t.Fatal(err)
		}
		go func(o *Output) {
			if n, err := o.Write([]byte(input)); err != nil {
				t.Error(err)
			} else if n != len(input) {
				t.Errorf("Expected %d, got %d", len(input), n)
			}
			if err := o.Close(); err != nil {
				t.Error(err)
			}
		}(o)
		output, err := ioutil.ReadAll(r)
		if err != nil {
			t.Fatal(err)
		}
		if string(output) != expectedOutput {
			t.Errorf("Last line is not stored as return string.\nExpected: '%s'\nGot:       '%s'", expectedOutput, output)
		}
	}
}

func TestTail(t *testing.T) {
	var tests = make(map[string][][]string)
	tests["hello, world!"] = [][]string{
		{},
		{"hello, world!"},
		{"hello, world!"},
		{"hello, world!"},
	}
	tests["One\nTwo\nThree"] = [][]string{
		{},
		{"Three"},
		{"Two", "Three"},
		{"One", "Two", "Three"},
	}
	for input, outputs := range tests {
		for n, expectedOutput := range outputs {
			var output []string
			Tail(strings.NewReader(input), n, &output)
			if fmt.Sprintf("%v", output) != fmt.Sprintf("%v", expectedOutput) {
				t.Errorf("Tail n=%d returned wrong result.\nExpected: '%s'\nGot     : '%s'", expectedOutput, output)
			}
		}
	}
}

func TestOutputAddTail(t *testing.T) {
	var tests = make(map[string][][]string)
	tests["hello, world!"] = [][]string{
		{},
		{"hello, world!"},
		{"hello, world!"},
		{"hello, world!"},
	}
	tests["One\nTwo\nThree"] = [][]string{
		{},
		{"Three"},
		{"Two", "Three"},
		{"One", "Two", "Three"},
	}
	for input, outputs := range tests {
		for n, expectedOutput := range outputs {
			o := NewOutput()
			var output []string
			if err := o.AddTail(&output, n); err != nil {
				t.Error(err)
			}
			if n, err := o.Write([]byte(input)); err != nil {
				t.Error(err)
			} else if n != len(input) {
				t.Errorf("Expected %d, got %d", len(input), n)
			}
			o.Close()
			if fmt.Sprintf("%v", output) != fmt.Sprintf("%v", expectedOutput) {
				t.Errorf("Tail(%d) returned wrong result.\nExpected: %v\nGot:      %v", n, expectedOutput, output)
			}
		}
	}
}

func lastLine(txt string) string {
	scanner := bufio.NewScanner(strings.NewReader(txt))
	var lastLine string
	for scanner.Scan() {
		lastLine = scanner.Text()
	}
	return lastLine
}

func TestOutputAdd(t *testing.T) {
	o := NewOutput()
	b := &bytes.Buffer{}
	o.Add(b)
	input := "hello, world!"
	if n, err := o.Write([]byte(input)); err != nil {
		t.Fatal(err)
	} else if n != len(input) {
		t.Fatalf("Expected %d, got %d", len(input), n)
	}
	if output := b.String(); output != input {
		t.Fatal("Received wrong data from Add.\nExpected: '%s'\nGot:     '%s'", input, output)
	}
}

func TestOutputWriteError(t *testing.T) {
	o := NewOutput()
	buf := &bytes.Buffer{}
	o.Add(buf)
	r, w := io.Pipe()
	input := "Hello there"
	expectedErr := fmt.Errorf("This is an error")
	r.CloseWithError(expectedErr)
	o.Add(w)
	n, err := o.Write([]byte(input))
	if err != expectedErr {
		t.Fatalf("Output.Write() should return the first error encountered, if any")
	}
	if buf.String() != input {
		t.Fatalf("Output.Write() should attempt write on all destinations, even after encountering an error")
	}
	if n != len(input) {
		t.Fatalf("Output.Write() should return the size of the input if it successfully writes to at least one destination")
	}
}

func TestInputAddEmpty(t *testing.T) {
	i := NewInput()
	var b bytes.Buffer
	if err := i.Add(&b); err != nil {
		t.Fatal(err)
	}
	data, err := ioutil.ReadAll(i)
	if err != nil {
		t.Fatal(err)
	}
	if len(data) > 0 {
		t.Fatalf("Read from empty input shoul yield no data")
	}
}

func TestInputAddTwo(t *testing.T) {
	i := NewInput()
	var b1 bytes.Buffer
	// First add should succeed
	if err := i.Add(&b1); err != nil {
		t.Fatal(err)
	}
	var b2 bytes.Buffer
	// Second add should fail
	if err := i.Add(&b2); err == nil {
		t.Fatalf("Adding a second source should return an error")
	}
}

func TestInputAddNotEmpty(t *testing.T) {
	i := NewInput()
	b := bytes.NewBufferString("hello world\nabc")
	expectedResult := b.String()
	i.Add(b)
	result, err := ioutil.ReadAll(i)
	if err != nil {
		t.Fatal(err)
	}
	if string(result) != expectedResult {
		t.Fatalf("Expected: %v\nReceived: %v", expectedResult, result)
	}
}
