package toml

import (
	"bytes"
	"errors"
	"fmt"
	"reflect"
	"strings"
	"testing"
	"time"
)

type failingWriter struct {
	failAt  int
	written int
	buffer  bytes.Buffer
}

func (f *failingWriter) Write(p []byte) (n int, err error) {
	count := len(p)
	toWrite := f.failAt - (count + f.written)
	if toWrite < 0 {
		toWrite = 0
	}
	if toWrite > count {
		f.written += count
		f.buffer.Write(p)
		return count, nil
	}

	f.buffer.Write(p[:toWrite])
	f.written = f.failAt
	return toWrite, fmt.Errorf("failingWriter failed after writing %d bytes", f.written)
}

func assertErrorString(t *testing.T, expected string, err error) {
	expectedErr := errors.New(expected)
	if err == nil || err.Error() != expectedErr.Error() {
		t.Errorf("expecting error %s, but got %s instead", expected, err)
	}
}

func TestTreeWriteToEmptyTable(t *testing.T) {
	doc := `[[empty-tables]]
[[empty-tables]]`

	toml, err := Load(doc)
	if err != nil {
		t.Fatal("Unexpected Load error:", err)
	}
	tomlString, err := toml.ToTomlString()
	if err != nil {
		t.Fatal("Unexpected ToTomlString error:", err)
	}

	expected := `
[[empty-tables]]

[[empty-tables]]
`

	if tomlString != expected {
		t.Fatalf("Expected:\n%s\nGot:\n%s", expected, tomlString)
	}
}

func TestTreeWriteToTomlString(t *testing.T) {
	toml, err := Load(`name = { first = "Tom", last = "Preston-Werner" }
points = { x = 1, y = 2 }`)

	if err != nil {
		t.Fatal("Unexpected error:", err)
	}

	tomlString, _ := toml.ToTomlString()
	reparsedTree, err := Load(tomlString)

	assertTree(t, reparsedTree, err, map[string]interface{}{
		"name": map[string]interface{}{
			"first": "Tom",
			"last":  "Preston-Werner",
		},
		"points": map[string]interface{}{
			"x": int64(1),
			"y": int64(2),
		},
	})
}

func TestTreeWriteToTomlStringSimple(t *testing.T) {
	tree, err := Load("[foo]\n\n[[foo.bar]]\na = 42\n\n[[foo.bar]]\na = 69\n")
	if err != nil {
		t.Errorf("Test failed to parse: %v", err)
		return
	}
	result, err := tree.ToTomlString()
	if err != nil {
		t.Errorf("Unexpected error: %s", err)
	}
	expected := "\n[foo]\n\n  [[foo.bar]]\n    a = 42\n\n  [[foo.bar]]\n    a = 69\n"
	if result != expected {
		t.Errorf("Expected got '%s', expected '%s'", result, expected)
	}
}

func TestTreeWriteToTomlStringKeysOrders(t *testing.T) {
	for i := 0; i < 100; i++ {
		tree, _ := Load(`
		foobar = true
		bar = "baz"
		foo = 1
		[qux]
		  foo = 1
		  bar = "baz2"`)

		stringRepr, _ := tree.ToTomlString()

		t.Log("Intermediate string representation:")
		t.Log(stringRepr)

		r := strings.NewReader(stringRepr)
		toml, err := LoadReader(r)

		if err != nil {
			t.Fatal("Unexpected error:", err)
		}

		assertTree(t, toml, err, map[string]interface{}{
			"foobar": true,
			"bar":    "baz",
			"foo":    1,
			"qux": map[string]interface{}{
				"foo": 1,
				"bar": "baz2",
			},
		})
	}
}

func testMaps(t *testing.T, actual, expected map[string]interface{}) {
	if !reflect.DeepEqual(actual, expected) {
		t.Fatal("trees aren't equal.\n", "Expected:\n", expected, "\nActual:\n", actual)
	}
}

func TestTreeWriteToMapSimple(t *testing.T) {
	tree, _ := Load("a = 42\nb = 17")

	expected := map[string]interface{}{
		"a": int64(42),
		"b": int64(17),
	}

	testMaps(t, tree.ToMap(), expected)
}

func TestTreeWriteToInvalidTreeSimpleValue(t *testing.T) {
	tree := Tree{values: map[string]interface{}{"foo": int8(1)}}
	_, err := tree.ToTomlString()
	assertErrorString(t, "invalid value type at foo: int8", err)
}

func TestTreeWriteToInvalidTreeTomlValue(t *testing.T) {
	tree := Tree{values: map[string]interface{}{"foo": &tomlValue{value: int8(1), comment: "", position: Position{}}}}
	_, err := tree.ToTomlString()
	assertErrorString(t, "unsupported value type int8: 1", err)
}

func TestTreeWriteToInvalidTreeTomlValueArray(t *testing.T) {
	tree := Tree{values: map[string]interface{}{"foo": &tomlValue{value: int8(1), comment: "", position: Position{}}}}
	_, err := tree.ToTomlString()
	assertErrorString(t, "unsupported value type int8: 1", err)
}

func TestTreeWriteToFailingWriterInSimpleValue(t *testing.T) {
	toml, _ := Load(`a = 2`)
	writer := failingWriter{failAt: 0, written: 0}
	_, err := toml.WriteTo(&writer)
	assertErrorString(t, "failingWriter failed after writing 0 bytes", err)
}

func TestTreeWriteToFailingWriterInTable(t *testing.T) {
	toml, _ := Load(`
[b]
a = 2`)
	writer := failingWriter{failAt: 2, written: 0}
	_, err := toml.WriteTo(&writer)
	assertErrorString(t, "failingWriter failed after writing 2 bytes", err)

	writer = failingWriter{failAt: 13, written: 0}
	_, err = toml.WriteTo(&writer)
	assertErrorString(t, "failingWriter failed after writing 13 bytes", err)
}

func TestTreeWriteToFailingWriterInArray(t *testing.T) {
	toml, _ := Load(`
[[b]]
a = 2`)
	writer := failingWriter{failAt: 2, written: 0}
	_, err := toml.WriteTo(&writer)
	assertErrorString(t, "failingWriter failed after writing 2 bytes", err)

	writer = failingWriter{failAt: 15, written: 0}
	_, err = toml.WriteTo(&writer)
	assertErrorString(t, "failingWriter failed after writing 15 bytes", err)
}

func TestTreeWriteToMapExampleFile(t *testing.T) {
	tree, _ := LoadFile("example.toml")
	expected := map[string]interface{}{
		"title": "TOML Example",
		"owner": map[string]interface{}{
			"name":         "Tom Preston-Werner",
			"organization": "GitHub",
			"bio":          "GitHub Cofounder & CEO\nLikes tater tots and beer.",
			"dob":          time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC),
		},
		"database": map[string]interface{}{
			"server":         "192.168.1.1",
			"ports":          []interface{}{int64(8001), int64(8001), int64(8002)},
			"connection_max": int64(5000),
			"enabled":        true,
		},
		"servers": map[string]interface{}{
			"alpha": map[string]interface{}{
				"ip": "10.0.0.1",
				"dc": "eqdc10",
			},
			"beta": map[string]interface{}{
				"ip": "10.0.0.2",
				"dc": "eqdc10",
			},
		},
		"clients": map[string]interface{}{
			"data": []interface{}{
				[]interface{}{"gamma", "delta"},
				[]interface{}{int64(1), int64(2)},
			},
		},
	}
	testMaps(t, tree.ToMap(), expected)
}

func TestTreeWriteToMapWithTablesInMultipleChunks(t *testing.T) {
	tree, _ := Load(`
	[[menu.main]]
        a = "menu 1"
        b = "menu 2"
        [[menu.main]]
        c = "menu 3"
        d = "menu 4"`)
	expected := map[string]interface{}{
		"menu": map[string]interface{}{
			"main": []interface{}{
				map[string]interface{}{"a": "menu 1", "b": "menu 2"},
				map[string]interface{}{"c": "menu 3", "d": "menu 4"},
			},
		},
	}
	treeMap := tree.ToMap()

	testMaps(t, treeMap, expected)
}

func TestTreeWriteToMapWithArrayOfInlineTables(t *testing.T) {
	tree, _ := Load(`
    	[params]
	language_tabs = [
    		{ key = "shell", name = "Shell" },
    		{ key = "ruby", name = "Ruby" },
    		{ key = "python", name = "Python" }
	]`)

	expected := map[string]interface{}{
		"params": map[string]interface{}{
			"language_tabs": []interface{}{
				map[string]interface{}{
					"key":  "shell",
					"name": "Shell",
				},
				map[string]interface{}{
					"key":  "ruby",
					"name": "Ruby",
				},
				map[string]interface{}{
					"key":  "python",
					"name": "Python",
				},
			},
		},
	}

	treeMap := tree.ToMap()
	testMaps(t, treeMap, expected)
}

func TestTreeWriteToFloat(t *testing.T) {
	tree, err := Load(`a = 3.0`)
	if err != nil {
		t.Fatal(err)
	}
	str, err := tree.ToTomlString()
	if err != nil {
		t.Fatal(err)
	}
	expected := `a = 3.0`
	if strings.TrimSpace(str) != strings.TrimSpace(expected) {
		t.Fatalf("Expected:\n%s\nGot:\n%s", expected, str)
	}
}

func BenchmarkTreeToTomlString(b *testing.B) {
	toml, err := Load(sampleHard)
	if err != nil {
		b.Fatal("Unexpected error:", err)
	}

	for i := 0; i < b.N; i++ {
		_, err := toml.ToTomlString()
		if err != nil {
			b.Fatal(err)
		}
	}
}

var sampleHard = `# Test file for TOML
# Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate
# This part you'll really hate

[the]
test_string = "You'll hate me after this - #"          # " Annoying, isn't it?

    [the.hard]
    test_array = [ "] ", " # "]      # ] There you go, parse this!
    test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ]
    # You didn't think it'd as easy as chucking out the last #, did you?
    another_test_string = " Same thing, but with a string #"
    harder_test_string = " And when \"'s are in the string, along with # \""   # "and comments are there too"
    # Things will get harder

        [the.hard."bit#"]
        "what?" = "You don't think some user won't do that?"
        multi_line_array = [
            "]",
            # ] Oh yes I did
            ]

# Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test

#[error]   if you didn't catch this, your parser is broken
#string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment"   like this
#array = [
#         "This might most likely happen in multiline arrays",
#         Like here,
#         "or here,
#         and here"
#         ]     End of array comment, forgot the #
#number = 3.14  pi <--again forgot the #         `
