// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gc

import (
	"cmd/compile/internal/types"
	"reflect"
	"sort"
	"testing"
)

func typeWithoutPointers() *types.Type {
	t := types.New(TSTRUCT)
	f := &types.Field{Type: types.New(TINT)}
	t.SetFields([]*types.Field{f})
	return t
}

func typeWithPointers() *types.Type {
	t := types.New(TSTRUCT)
	f := &types.Field{Type: types.NewPtr(types.New(TINT))}
	t.SetFields([]*types.Field{f})
	return t
}

func markUsed(n *Node) *Node {
	n.Name.SetUsed(true)
	return n
}

func markNeedZero(n *Node) *Node {
	n.Name.SetNeedzero(true)
	return n
}

func nodeWithClass(n Node, c Class) *Node {
	n.SetClass(c)
	n.Name = new(Name)
	return &n
}

// Test all code paths for cmpstackvarlt.
func TestCmpstackvar(t *testing.T) {
	testdata := []struct {
		a, b *Node
		lt   bool
	}{
		{
			nodeWithClass(Node{}, PAUTO),
			nodeWithClass(Node{}, PFUNC),
			false,
		},
		{
			nodeWithClass(Node{}, PFUNC),
			nodeWithClass(Node{}, PAUTO),
			true,
		},
		{
			nodeWithClass(Node{Xoffset: 0}, PFUNC),
			nodeWithClass(Node{Xoffset: 10}, PFUNC),
			true,
		},
		{
			nodeWithClass(Node{Xoffset: 20}, PFUNC),
			nodeWithClass(Node{Xoffset: 10}, PFUNC),
			false,
		},
		{
			nodeWithClass(Node{Xoffset: 10}, PFUNC),
			nodeWithClass(Node{Xoffset: 10}, PFUNC),
			false,
		},
		{
			nodeWithClass(Node{Xoffset: 10}, PPARAM),
			nodeWithClass(Node{Xoffset: 20}, PPARAMOUT),
			true,
		},
		{
			nodeWithClass(Node{Xoffset: 10}, PPARAMOUT),
			nodeWithClass(Node{Xoffset: 20}, PPARAM),
			true,
		},
		{
			markUsed(nodeWithClass(Node{}, PAUTO)),
			nodeWithClass(Node{}, PAUTO),
			true,
		},
		{
			nodeWithClass(Node{}, PAUTO),
			markUsed(nodeWithClass(Node{}, PAUTO)),
			false,
		},
		{
			nodeWithClass(Node{Type: typeWithoutPointers()}, PAUTO),
			nodeWithClass(Node{Type: typeWithPointers()}, PAUTO),
			false,
		},
		{
			nodeWithClass(Node{Type: typeWithPointers()}, PAUTO),
			nodeWithClass(Node{Type: typeWithoutPointers()}, PAUTO),
			true,
		},
		{
			markNeedZero(nodeWithClass(Node{Type: &types.Type{}}, PAUTO)),
			nodeWithClass(Node{Type: &types.Type{}, Name: &Name{}}, PAUTO),
			true,
		},
		{
			nodeWithClass(Node{Type: &types.Type{}, Name: &Name{}}, PAUTO),
			markNeedZero(nodeWithClass(Node{Type: &types.Type{}}, PAUTO)),
			false,
		},
		{
			nodeWithClass(Node{Type: &types.Type{Width: 1}, Name: &Name{}}, PAUTO),
			nodeWithClass(Node{Type: &types.Type{Width: 2}, Name: &Name{}}, PAUTO),
			false,
		},
		{
			nodeWithClass(Node{Type: &types.Type{Width: 2}, Name: &Name{}}, PAUTO),
			nodeWithClass(Node{Type: &types.Type{Width: 1}, Name: &Name{}}, PAUTO),
			true,
		},
		{
			nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "abc"}}, PAUTO),
			nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "xyz"}}, PAUTO),
			true,
		},
		{
			nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "abc"}}, PAUTO),
			nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "abc"}}, PAUTO),
			false,
		},
		{
			nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "xyz"}}, PAUTO),
			nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "abc"}}, PAUTO),
			false,
		},
	}
	for _, d := range testdata {
		got := cmpstackvarlt(d.a, d.b)
		if got != d.lt {
			t.Errorf("want %#v < %#v", d.a, d.b)
		}
		// If we expect a < b to be true, check that b < a is false.
		if d.lt && cmpstackvarlt(d.b, d.a) {
			t.Errorf("unexpected %#v < %#v", d.b, d.a)
		}
	}
}

func TestStackvarSort(t *testing.T) {
	inp := []*Node{
		nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PFUNC),
		nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PAUTO),
		nodeWithClass(Node{Xoffset: 0, Type: &types.Type{}, Sym: &types.Sym{}}, PFUNC),
		nodeWithClass(Node{Xoffset: 10, Type: &types.Type{}, Sym: &types.Sym{}}, PFUNC),
		nodeWithClass(Node{Xoffset: 20, Type: &types.Type{}, Sym: &types.Sym{}}, PFUNC),
		markUsed(nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PAUTO)),
		nodeWithClass(Node{Type: typeWithoutPointers(), Sym: &types.Sym{}}, PAUTO),
		nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PAUTO),
		markNeedZero(nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PAUTO)),
		nodeWithClass(Node{Type: &types.Type{Width: 1}, Sym: &types.Sym{}}, PAUTO),
		nodeWithClass(Node{Type: &types.Type{Width: 2}, Sym: &types.Sym{}}, PAUTO),
		nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "abc"}}, PAUTO),
		nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "xyz"}}, PAUTO),
	}
	want := []*Node{
		nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PFUNC),
		nodeWithClass(Node{Xoffset: 0, Type: &types.Type{}, Sym: &types.Sym{}}, PFUNC),
		nodeWithClass(Node{Xoffset: 10, Type: &types.Type{}, Sym: &types.Sym{}}, PFUNC),
		nodeWithClass(Node{Xoffset: 20, Type: &types.Type{}, Sym: &types.Sym{}}, PFUNC),
		markUsed(nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PAUTO)),
		markNeedZero(nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PAUTO)),
		nodeWithClass(Node{Type: &types.Type{Width: 2}, Sym: &types.Sym{}}, PAUTO),
		nodeWithClass(Node{Type: &types.Type{Width: 1}, Sym: &types.Sym{}}, PAUTO),
		nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PAUTO),
		nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{}}, PAUTO),
		nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "abc"}}, PAUTO),
		nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "xyz"}}, PAUTO),
		nodeWithClass(Node{Type: typeWithoutPointers(), Sym: &types.Sym{}}, PAUTO),
	}
	sort.Sort(byStackVar(inp))
	if !reflect.DeepEqual(want, inp) {
		t.Error("sort failed")
		for i := range inp {
			g := inp[i]
			w := want[i]
			eq := reflect.DeepEqual(w, g)
			if !eq {
				t.Log(i, w, g)
			}
		}
	}
}
