// 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 (
	"fmt"
	"net"
	"reflect"
	"testing"
	"time"
)

func TestVal2nodeDefault(t *testing.T) {
	err := fmt.Errorf("err")
	var errNil error

	tests := []struct {
		desc string
		raw  interface{}
		want node
	}{
		{
			desc: "nil",
			raw:  nil,
			want: rawVal("nil"),
		},
		{
			desc: "nil ptr",
			raw:  (*int)(nil),
			want: rawVal("nil"),
		},
		{
			desc: "nil slice",
			raw:  []string(nil),
			want: list{},
		},
		{
			desc: "nil map",
			raw:  map[string]string(nil),
			want: keyvals{},
		},
		{
			desc: "string",
			raw:  "zaphod",
			want: stringVal("zaphod"),
		},
		{
			desc: "slice",
			raw:  []string{"a", "b"},
			want: list{stringVal("a"), stringVal("b")},
		},
		{
			desc: "map",
			raw: map[string]string{
				"zaphod": "beeblebrox",
				"ford":   "prefect",
			},
			want: keyvals{
				{"ford", stringVal("prefect")},
				{"zaphod", stringVal("beeblebrox")},
			},
		},
		{
			desc: "map of [2]int",
			raw: map[[2]int]string{
				[2]int{-1, 2}: "school",
				[2]int{0, 0}:  "origin",
				[2]int{1, 3}:  "home",
			},
			want: keyvals{
				{"[-1,2]", stringVal("school")},
				{"[0,0]", stringVal("origin")},
				{"[1,3]", stringVal("home")},
			},
		},
		{
			desc: "struct",
			raw:  struct{ Zaphod, Ford string }{"beeblebrox", "prefect"},
			want: keyvals{
				{"Zaphod", stringVal("beeblebrox")},
				{"Ford", stringVal("prefect")},
			},
		},
		{
			desc: "int",
			raw:  3,
			want: rawVal("3"),
		},
		{
			desc: "time.Time",
			raw:  time.Unix(1257894000, 0).UTC(),
			want: rawVal("2009-11-10 23:00:00 +0000 UTC"),
		},
		{
			desc: "net.IP",
			raw:  net.IPv4(127, 0, 0, 1),
			want: rawVal("127.0.0.1"),
		},
		{
			desc: "error",
			raw:  &err,
			want: rawVal("err"),
		},
		{
			desc: "nil error",
			raw:  &errNil,
			want: rawVal("<nil>"),
		},
	}

	for _, test := range tests {
		ref := &reflector{
			Config: DefaultConfig,
		}
		if got, want := ref.val2node(reflect.ValueOf(test.raw)), test.want; !reflect.DeepEqual(got, want) {
			t.Errorf("%s: got %#v, want %#v", test.desc, got, want)
		}
	}
}

func TestVal2node(t *testing.T) {
	tests := []struct {
		desc string
		raw  interface{}
		cfg  *Config
		want node
	}{
		{
			desc: "struct default",
			raw:  struct{ Zaphod, Ford, foo string }{"beeblebrox", "prefect", "BAD"},
			cfg:  DefaultConfig,
			want: keyvals{
				{"Zaphod", stringVal("beeblebrox")},
				{"Ford", stringVal("prefect")},
			},
		},
		{
			desc: "struct w/ IncludeUnexported",
			raw:  struct{ Zaphod, Ford, foo string }{"beeblebrox", "prefect", "GOOD"},
			cfg: &Config{
				IncludeUnexported: true,
			},
			want: keyvals{
				{"Zaphod", stringVal("beeblebrox")},
				{"Ford", stringVal("prefect")},
				{"foo", stringVal("GOOD")},
			},
		},
		{
			desc: "time default",
			raw:  struct{ Date time.Time }{time.Unix(1234567890, 0).UTC()},
			cfg:  DefaultConfig,
			want: keyvals{
				{"Date", rawVal("2009-02-13 23:31:30 +0000 UTC")},
			},
		},
		{
			desc: "time w/ nil Formatter",
			raw:  struct{ Date time.Time }{time.Unix(1234567890, 0).UTC()},
			cfg: &Config{
				PrintStringers: true,
				Formatter: map[reflect.Type]interface{}{
					reflect.TypeOf(time.Time{}): nil,
				},
			},
			want: keyvals{
				{"Date", keyvals{}},
			},
		},
		{
			desc: "time w/ PrintTextMarshalers",
			raw:  struct{ Date time.Time }{time.Unix(1234567890, 0).UTC()},
			cfg: &Config{
				PrintTextMarshalers: true,
			},
			want: keyvals{
				{"Date", stringVal("2009-02-13T23:31:30Z")},
			},
		},
		{
			desc: "time w/ PrintStringers",
			raw:  struct{ Date time.Time }{time.Unix(1234567890, 0).UTC()},
			cfg: &Config{
				PrintStringers: true,
			},
			want: keyvals{
				{"Date", stringVal("2009-02-13 23:31:30 +0000 UTC")},
			},
		},
		{
			desc: "circular list",
			raw:  circular(3),
			cfg:  CycleTracker,
			want: target{1, keyvals{
				{"Value", rawVal("1")},
				{"Next", keyvals{
					{"Value", rawVal("2")},
					{"Next", keyvals{
						{"Value", rawVal("3")},
						{"Next", ref{1}},
					}},
				}},
			}},
		},
		{
			desc: "self referential maps",
			raw:  selfRef(),
			cfg:  CycleTracker,
			want: target{1, keyvals{
				{"ID", rawVal("1")},
				{"Child", keyvals{
					{"2", target{2, keyvals{
						{"ID", rawVal("2")},
						{"Child", keyvals{
							{"3", target{3, keyvals{
								{"ID", rawVal("3")},
								{"Child", keyvals{
									{"1", ref{1}},
									{"2", ref{2}},
									{"3", ref{3}},
								}},
							}}},
						}},
					}}},
				}},
			}},
		},
		{
			desc: "maps of cycles",
			raw: map[string]*ListNode{
				"1. one":   circular(1),
				"2. two":   circular(2),
				"3. three": circular(1),
			},
			cfg: CycleTracker,
			want: keyvals{
				{"1. one", target{1, keyvals{
					{"Value", rawVal("1")},
					{"Next", ref{1}},
				}}},
				{"2. two", target{2, keyvals{
					{"Value", rawVal("1")},
					{"Next", keyvals{
						{"Value", rawVal("2")},
						{"Next", ref{2}},
					}},
				}}},
				{"3. three", target{3, keyvals{
					{"Value", rawVal("1")},
					{"Next", ref{3}},
				}}},
			},
		},
	}

	for _, test := range tests {
		ref := &reflector{
			Config: test.cfg,
		}
		if test.cfg.TrackCycles {
			ref.pointerTracker = new(pointerTracker)
		}
		if got, want := ref.val2node(reflect.ValueOf(test.raw)), test.want; !reflect.DeepEqual(got, want) {
			t.Run(test.desc, func(t *testing.T) {
				t.Errorf(" got %#v", got)
				t.Errorf("want %#v", want)
				t.Errorf("Diff: (-got +want)\n%s", Compare(got, want))
			})
		}
	}
}

type ListNode struct {
	Value int
	Next  *ListNode
}

func circular(nodes int) *ListNode {
	final := &ListNode{
		Value: nodes,
	}
	final.Next = final

	recent := final
	for i := nodes - 1; i > 0; i-- {
		n := &ListNode{
			Value: i,
			Next:  recent,
		}
		final.Next = n
		recent = n
	}
	return recent
}

type SelfReferential struct {
	ID    int
	Child map[int]*SelfReferential
}

func selfRef() *SelfReferential {
	sr1 := &SelfReferential{
		ID:    1,
		Child: make(map[int]*SelfReferential),
	}
	sr2 := &SelfReferential{
		ID:    2,
		Child: make(map[int]*SelfReferential),
	}
	sr3 := &SelfReferential{
		ID:    3,
		Child: make(map[int]*SelfReferential),
	}

	// Build a cycle
	sr1.Child[2] = sr2
	sr2.Child[3] = sr3
	sr3.Child[1] = sr1

	// Throw in some other stuff for funzies
	sr3.Child[2] = sr2
	sr3.Child[3] = sr3
	return sr1
}

func BenchmarkVal2node(b *testing.B) {
	benchmarks := []struct {
		desc string
		cfg  *Config
		raw  interface{}
	}{
		{
			desc: "struct",
			cfg:  DefaultConfig,
			raw:  struct{ Zaphod, Ford string }{"beeblebrox", "prefect"},
		},
		{
			desc: "map",
			cfg:  DefaultConfig,
			raw: map[[2]int]string{
				[2]int{-1, 2}: "school",
				[2]int{0, 0}:  "origin",
				[2]int{1, 3}:  "home",
			},
		},
		{
			desc: "track/struct",
			cfg:  CycleTracker,
			raw:  struct{ Zaphod, Ford string }{"beeblebrox", "prefect"},
		},
		{
			desc: "track/map",
			cfg:  CycleTracker,
			raw: map[[2]int]string{
				[2]int{-1, 2}: "school",
				[2]int{0, 0}:  "origin",
				[2]int{1, 3}:  "home",
			},
		},
		{
			desc: "circlist/small",
			cfg:  CycleTracker,
			raw:  circular(3),
		},
		{
			desc: "circlist/med",
			cfg:  CycleTracker,
			raw:  circular(300),
		},
		{
			desc: "circlist/large",
			cfg:  CycleTracker,
			raw:  circular(3000),
		},
		{
			desc: "mapofcirc/small",
			cfg:  CycleTracker,
			raw: map[string]*ListNode{
				"1. one":   circular(1),
				"2. two":   circular(2),
				"3. three": circular(1),
			},
		},
		{
			desc: "selfrefmap/small",
			cfg:  CycleTracker,
			raw:  selfRef,
		},
	}

	for _, bench := range benchmarks {
		b.Run(bench.desc, func(b *testing.B) {
			b.ReportAllocs()
			for i := 0; i < b.N; i++ {
				ref := &reflector{
					Config: bench.cfg,
				}
				if bench.cfg.TrackCycles {
					ref.pointerTracker = new(pointerTracker)
				}
				ref.val2node(reflect.ValueOf(bench.raw))
			}
		})
	}
}
