// Copyright 2013 Google Inc.  All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pretty

import (
	"bytes"
	"strings"
	"testing"
)

func TestFormat(t *testing.T) {
	tests := []struct {
		desc string
		node node

		// All strings have a leading newline trimmed before comparison:
		normal   string
		diffable string
	}{
		{
			desc:     "string",
			node:     stringVal("zaphod"),
			normal:   `"zaphod"`,
			diffable: `"zaphod"`,
		},
		{
			desc:     "raw",
			node:     rawVal("42"),
			normal:   `42`,
			diffable: `42`,
		},
		{
			desc: "keyvals",
			node: keyvals{
				{"name", stringVal("zaphod")},
				{"age", rawVal("42")},
			},
			normal: `
{name: "zaphod",
 age:  42}`,
			diffable: `
{
 name: "zaphod",
 age: 42,
}`,
		},
		{
			desc: "empty list",
			node: list{},
			normal: `
[]`,
			diffable: `
[
]`,
		},
		{
			desc: "empty nested list",
			node: list{list{}},
			normal: `
[[]]`,
			diffable: `
[
 [
 ],
]`,
		},
		{
			desc: "list",
			node: list{
				stringVal("zaphod"),
				rawVal("42"),
			},
			normal: `
["zaphod",
 42]`,
			diffable: `
[
 "zaphod",
 42,
]`,
		},
		{
			desc: "empty keyvals",
			node: keyvals{},
			normal: `
{}`,
			diffable: `
{
}`,
		},
		{
			desc: "empty nested keyvals",
			node: keyvals{{"k", keyvals{}}},
			normal: `
{k: {}}`,
			diffable: `
{
 k: {
 },
}`,
		},
		{
			desc: "nested",
			node: list{
				stringVal("first"),
				list{rawVal("1"), rawVal("2"), rawVal("3")},
				keyvals{
					{"trillian", keyvals{
						{"race", stringVal("human")},
						{"age", rawVal("36")},
					}},
					{"zaphod", keyvals{
						{"occupation", stringVal("president of the galaxy")},
						{"features", stringVal("two heads")},
					}},
				},
				keyvals{},
			},
			normal: `
["first",
 [1,
  2,
  3],
 {trillian: {race: "human",
             age:  36},
  zaphod:   {occupation: "president of the galaxy",
             features:   "two heads"}},
 {}]`,
			diffable: `
[
 "first",
 [
  1,
  2,
  3,
 ],
 {
  trillian: {
   race: "human",
   age: 36,
  },
  zaphod: {
   occupation: "president of the galaxy",
   features: "two heads",
  },
 },
 {
 },
]`,
		},
		{
			desc: "recursive",
			node: target{1, keyvals{
				{"Value", rawVal("1")},
				{"Next", keyvals{
					{"Value", rawVal("2")},
					{"Next", keyvals{
						{"Value", rawVal("3")},
						{"Next", ref{1}},
					}},
				}},
			}},
			normal: `
<#1> {Value: 1,
      Next:  {Value: 2,
              Next:  {Value: 3,
                      Next:  <see #1>}}}`,
			diffable: `
<#1> {
 Value: 1,
 Next: {
  Value: 2,
  Next: {
   Value: 3,
   Next: <see #1>,
  },
 },
}`,
		},
		{
			desc: "print in order",
			node: list{
				target{2, keyvals{
					{"Next", ref{1}},
				}},
				target{1, keyvals{
					{"Next", ref{2}},
				}},
			},
			normal: `
[<#1> {Next: <see #2>},
 <#2> {Next: <see #1>}]`,
			diffable: `
[
 <#1> {
  Next: <see #2>,
 },
 <#2> {
  Next: <see #1>,
 },
]`,
		},
	}

	normal := &Config{}
	diffable := &Config{Diffable: true}
	for _, test := range tests {
		// For readability, we have a newline that won't be there in the output
		test.normal = strings.TrimPrefix(test.normal, "\n")
		test.diffable = strings.TrimPrefix(test.diffable, "\n")

		buf := new(bytes.Buffer)
		newFormatter(normal, buf).write(test.node)
		if got, want := buf.String(), test.normal; got != want {
			t.Errorf("%s: normal rendendered incorrectly\ngot:\n%s\nwant:\n%s", test.desc, got, want)
		}
		buf.Reset()

		newFormatter(diffable, buf).write(test.node)
		if got, want := buf.String(), test.diffable; got != want {
			t.Errorf("%s: diffable rendendered incorrectly\ngot:\n%s\nwant:\n%s", test.desc, got, want)
		}
	}
}

func TestCompactString(t *testing.T) {
	tests := []struct {
		node
		compact string
	}{
		{
			stringVal("abc"),
			"abc",
		},
		{
			rawVal("2"),
			"2",
		},
		{
			list{
				rawVal("2"),
				rawVal("3"),
			},
			"[2,3]",
		},
		{
			keyvals{
				{"name", stringVal("zaphod")},
				{"age", rawVal("42")},
			},
			`{name:"zaphod",age:42}`,
		},
		{
			list{
				list{
					rawVal("0"),
					rawVal("1"),
					rawVal("2"),
					rawVal("3"),
				},
				list{
					rawVal("1"),
					rawVal("2"),
					rawVal("3"),
					rawVal("0"),
				},
				list{
					rawVal("2"),
					rawVal("3"),
					rawVal("0"),
					rawVal("1"),
				},
			},
			`[[0,1,2,3],[1,2,3,0],[2,3,0,1]]`,
		},
	}

	for _, test := range tests {
		if got, want := new(formatter).compactString(test.node), test.compact; got != want {
			t.Errorf("%#v: compact = %q, want %q", test.node, got, want)
		}
	}
}

func TestShortList(t *testing.T) {
	cfg := &Config{
		ShortList: 16,
	}

	tests := []struct {
		node
		want string
	}{
		{
			list{
				list{
					rawVal("0"),
					rawVal("1"),
					rawVal("2"),
					rawVal("3"),
				},
				list{
					rawVal("1"),
					rawVal("2"),
					rawVal("3"),
					rawVal("0"),
				},
				list{
					rawVal("2"),
					rawVal("3"),
					rawVal("0"),
					rawVal("1"),
				},
			},
			`[[0,1,2,3],
 [1,2,3,0],
 [2,3,0,1]]`,
		},
	}

	for _, test := range tests {
		buf := new(bytes.Buffer)
		newFormatter(cfg, buf).write(test.node)
		if got, want := buf.String(), test.want; got != want {
			t.Errorf("%#v:\ngot:\n%s\nwant:\n%s", test.node, got, want)
		}
	}
}

var benchNode = keyvals{
	{"list", list{
		rawVal("0"),
		rawVal("1"),
		rawVal("2"),
		rawVal("3"),
	}},
	{"keyvals", keyvals{
		{"a", stringVal("b")},
		{"c", stringVal("e")},
		{"d", stringVal("f")},
	}},
}

func benchOpts(b *testing.B, cfg *Config) {
	buf := new(bytes.Buffer)
	newFormatter(cfg, buf).write(benchNode)
	b.SetBytes(int64(buf.Len()))
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		buf.Reset()
		newFormatter(cfg, buf).write(benchNode)
	}
}

func BenchmarkWriteDefault(b *testing.B)   { benchOpts(b, DefaultConfig) }
func BenchmarkWriteShortList(b *testing.B) { benchOpts(b, &Config{ShortList: 16}) }
func BenchmarkWriteCompact(b *testing.B)   { benchOpts(b, &Config{Compact: true}) }
func BenchmarkWriteDiffable(b *testing.B)  { benchOpts(b, &Config{Diffable: true}) }
