// Copyright 2019, 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.md file.

package cmp

import "reflect"

// valueNode represents a single node within a report, which is a
// structured representation of the value tree, containing information
// regarding which nodes are equal or not.
type valueNode struct {
	parent *valueNode

	Type   reflect.Type
	ValueX reflect.Value
	ValueY reflect.Value

	// NumSame is the number of leaf nodes that are equal.
	// All descendants are equal only if NumDiff is 0.
	NumSame int
	// NumDiff is the number of leaf nodes that are not equal.
	NumDiff int
	// NumIgnored is the number of leaf nodes that are ignored.
	NumIgnored int
	// NumCompared is the number of leaf nodes that were compared
	// using an Equal method or Comparer function.
	NumCompared int
	// NumTransformed is the number of non-leaf nodes that were transformed.
	NumTransformed int
	// NumChildren is the number of transitive descendants of this node.
	// This counts from zero; thus, leaf nodes have no descendants.
	NumChildren int
	// MaxDepth is the maximum depth of the tree. This counts from zero;
	// thus, leaf nodes have a depth of zero.
	MaxDepth int

	// Records is a list of struct fields, slice elements, or map entries.
	Records []reportRecord // If populated, implies Value is not populated

	// Value is the result of a transformation, pointer indirect, of
	// type assertion.
	Value *valueNode // If populated, implies Records is not populated

	// TransformerName is the name of the transformer.
	TransformerName string // If non-empty, implies Value is populated
}
type reportRecord struct {
	Key   reflect.Value // Invalid for slice element
	Value *valueNode
}

func (parent *valueNode) PushStep(ps PathStep) (child *valueNode) {
	vx, vy := ps.Values()
	child = &valueNode{parent: parent, Type: ps.Type(), ValueX: vx, ValueY: vy}
	switch s := ps.(type) {
	case StructField:
		assert(parent.Value == nil)
		parent.Records = append(parent.Records, reportRecord{Key: reflect.ValueOf(s.Name()), Value: child})
	case SliceIndex:
		assert(parent.Value == nil)
		parent.Records = append(parent.Records, reportRecord{Value: child})
	case MapIndex:
		assert(parent.Value == nil)
		parent.Records = append(parent.Records, reportRecord{Key: s.Key(), Value: child})
	case Indirect:
		assert(parent.Value == nil && parent.Records == nil)
		parent.Value = child
	case TypeAssertion:
		assert(parent.Value == nil && parent.Records == nil)
		parent.Value = child
	case Transform:
		assert(parent.Value == nil && parent.Records == nil)
		parent.Value = child
		parent.TransformerName = s.Name()
		parent.NumTransformed++
	default:
		assert(parent == nil) // Must be the root step
	}
	return child
}

func (r *valueNode) Report(rs Result) {
	assert(r.MaxDepth == 0) // May only be called on leaf nodes

	if rs.ByIgnore() {
		r.NumIgnored++
	} else {
		if rs.Equal() {
			r.NumSame++
		} else {
			r.NumDiff++
		}
	}
	assert(r.NumSame+r.NumDiff+r.NumIgnored == 1)

	if rs.ByMethod() {
		r.NumCompared++
	}
	if rs.ByFunc() {
		r.NumCompared++
	}
	assert(r.NumCompared <= 1)
}

func (child *valueNode) PopStep() (parent *valueNode) {
	if child.parent == nil {
		return nil
	}
	parent = child.parent
	parent.NumSame += child.NumSame
	parent.NumDiff += child.NumDiff
	parent.NumIgnored += child.NumIgnored
	parent.NumCompared += child.NumCompared
	parent.NumTransformed += child.NumTransformed
	parent.NumChildren += child.NumChildren + 1
	if parent.MaxDepth < child.MaxDepth+1 {
		parent.MaxDepth = child.MaxDepth + 1
	}
	return parent
}
