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 (
	
	

	
)
numContextRecords is the number of surrounding equal records to print.
emitType always prints the type.
elideType never prints the type.
autoType prints the type only for composite kinds (i.e., structs, slices, arrays, and maps).
DiffMode controls the output mode of FormatDiff. If diffUnknown, then produce a diff of the x and y values. If diffIdentical, then emit values as if they were equal. If diffRemoved, then only emit x values (ignoring y values). If diffInserted, then only emit y values (ignoring x values).
TypeMode controls whether to print the type for the current node. As a general rule of thumb, we always print the type of the next node after an interface, and always elide the type of the next node after a slice or map node.
formatValueOptions are options specific to printing reflect.Values.
	formatValueOptions
}

func ( formatOptions) ( diffMode) formatOptions {
	.DiffMode = 
	return 
}
func ( formatOptions) ( typeMode) formatOptions {
	.TypeMode = 
	return 
}
func ( formatOptions) ( int) formatOptions {
	.VerbosityLevel = 
	.LimitVerbosity = true
	return 
}
func ( formatOptions) () uint {
	switch {
	case .VerbosityLevel < 0:
		return 0
	case .VerbosityLevel > 16:
		return 16 // some reasonable maximum to avoid shift overflow
	default:
		return uint(.VerbosityLevel)
	}
}

const maxVerbosityPreset = 3
verbosityPreset modifies the verbosity settings given an index between 0 and maxVerbosityPreset, inclusive.
func ( formatOptions,  int) formatOptions {
	.VerbosityLevel = int(.verbosity()) + 2*
	if  > 0 {
		.AvoidStringer = true
	}
	if  >= maxVerbosityPreset {
		.PrintAddresses = true
		.QualifiedNames = true
	}
	return 
}
FormatDiff converts a valueNode tree into a textNode tree, where the later is a textual representation of the differences detected in the former.
func ( formatOptions) ( *valueNode,  *pointerReferences) ( textNode) {
	if .DiffMode == diffIdentical {
		 = .WithVerbosity(1)
	} else {
		 = .WithVerbosity(3)
	}
Check whether we have specialized formatting for this node. This is not necessary, but helpful for producing more readable outputs.
	if .CanFormatDiffSlice() {
		return .FormatDiffSlice()
	}

	var  reflect.Kind
	if .parent != nil && .parent.TransformerName == "" {
		 = .parent.Type.Kind()
	}
For leaf nodes, format the value based on the reflect.Values alone.
	if .MaxDepth == 0 {
		switch .DiffMode {
Format Equal.
			if .NumDiff == 0 {
				 := .FormatValue(.ValueX, , )
				 := .FormatValue(.ValueY, , )
				if .NumIgnored > 0 && .NumSame == 0 {
					return textEllipsis
				} else if .Len() < .Len() {
					return 
				} else {
					return 
				}
			}
Format unequal.
			assert(.DiffMode == diffUnknown)
			var  textList
			 := .WithTypeMode(elideType).FormatValue(.ValueX, , )
			 := .WithTypeMode(elideType).FormatValue(.ValueY, , )
			for  := 0;  <= maxVerbosityPreset &&  != nil &&  != nil && .Equal(); ++ {
				 := verbosityPreset(, ).WithTypeMode(elideType)
				 = .FormatValue(.ValueX, , )
				 = .FormatValue(.ValueY, , )
			}
			if  != nil {
				 = append(, textRecord{Diff: '-', Value: })
			}
			if  != nil {
				 = append(, textRecord{Diff: '+', Value: })
			}
			return .WithTypeMode(emitType).FormatType(.Type, )
		case diffRemoved:
			return .FormatValue(.ValueX, , )
		case diffInserted:
			return .FormatValue(.ValueY, , )
		default:
			panic("invalid diff mode")
		}
	}
Register slice element to support cycle detection.
	if  == reflect.Slice {
		 := .PushPair(.ValueX, .ValueY, .DiffMode, true)
		defer .Pop()
		defer func() {  = wrapTrunkReferences(, ) }()
	}
Descend into the child value node.
	if .TransformerName != "" {
		 := .WithTypeMode(emitType).(.Value, )
		 = &textWrap{Prefix: "Inverse(" + .TransformerName + ", ", Value: , Suffix: ")"}
		return .FormatType(.Type, )
	} else {
		switch  := .Type.Kind();  {
		case reflect.Struct, reflect.Array, reflect.Slice:
			 = .formatDiffList(.Records, , )
			 = .FormatType(.Type, )
Register map to support cycle detection.
			 := .PushPair(.ValueX, .ValueY, .DiffMode, false)
			defer .Pop()

			 = .formatDiffList(.Records, , )
			 = wrapTrunkReferences(, )
			 = .FormatType(.Type, )
Register pointer to support cycle detection.
			 := .PushPair(.ValueX, .ValueY, .DiffMode, false)
			defer .Pop()

			 = .(.Value, )
			 = wrapTrunkReferences(, )
			 = &textWrap{Prefix: "&", Value: }
		case reflect.Interface:
			 = .WithTypeMode(emitType).(.Value, )
		default:
			panic(fmt.Sprintf("%v cannot have children", ))
		}
		return 
	}
}

Derive record name based on the data structure kind.
	var  string
	var  func(reflect.Value) string
	switch  {
	case reflect.Struct:
		 = "field"
		 = .WithTypeMode(autoType)
		 = func( reflect.Value) string { return .String() }
	case reflect.Slice, reflect.Array:
		 = "element"
		 = .WithTypeMode(elideType)
		 = func(reflect.Value) string { return "" }
	case reflect.Map:
		 = "entry"
		 = .WithTypeMode(elideType)
		 = func( reflect.Value) string { return formatMapKey(, false, ) }
	}

	 := -1
	if .LimitVerbosity {
		if .DiffMode == diffIdentical {
			 = ((1 << .verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
		} else {
			 = (1 << .verbosity()) << 1 // 2, 4, 8, 16, 32, 64, etc...
		}
		.VerbosityLevel--
	}
Handle unification.
	switch .DiffMode {
	case diffIdentical, diffRemoved, diffInserted:
		var  textList
		var  bool // Add final "..." to indicate records were dropped
		for ,  := range  {
			if len() ==  {
				 = true
				break
			}
Elide struct fields that are zero value.
			if  == reflect.Struct {
				var  bool
				switch .DiffMode {
				case diffIdentical:
					 = value.IsZero(.Value.ValueX) || value.IsZero(.Value.ValueY)
				case diffRemoved:
					 = value.IsZero(.Value.ValueX)
				case diffInserted:
					 = value.IsZero(.Value.ValueY)
				}
				if  {
					continue
				}
Elide ignored nodes.
			if .Value.NumIgnored > 0 && .Value.NumSame+.Value.NumDiff == 0 {
				 = !( == reflect.Slice ||  == reflect.Array)
				if ! {
					.AppendEllipsis(diffStats{})
				}
				continue
			}
			if  := .FormatDiff(.Value, );  != nil {
				 = append(, textRecord{Key: (.Key), Value: })
			}
		}
		if  {
			.AppendEllipsis(diffStats{})
		}
		return &textWrap{Prefix: "{", Value: , Suffix: "}"}
	case diffUnknown:
	default:
		panic("invalid diff mode")
	}
Handle differencing.
	var  int
	var  textList
	var  []reflect.Value // invariant: len(list) == len(keys)
	 := coalesceAdjacentRecords(, )
	 := diffStats{Name: }
	for ,  := range  {
		if  >= 0 &&  >=  {
			 = .Append()
			continue
		}
Handle equal records.
Compute the number of leading and trailing records to print.
			var ,  int
			 := .NumIgnored + .NumIdentical
			for  < numContextRecords && + <  &&  != 0 {
				if  := [].Value; .NumIgnored > 0 && .NumSame+.NumDiff == 0 {
					break
				}
				++
			}
			for  < numContextRecords && + <  &&  != len()-1 {
				if  := [--1].Value; .NumIgnored > 0 && .NumSame+.NumDiff == 0 {
					break
				}
				++
			}
			if -(+) == 1 && .NumIgnored == 0 {
				++ // Avoid pointless coalescing of a single equal record
			}
Format the equal values.
			for ,  := range [:] {
				 := .WithDiffMode(diffIdentical).FormatDiff(.Value, )
				 = append(, textRecord{Key: (.Key), Value: })
				 = append(, .Key)
			}
			if  > + {
				.NumIdentical -=  + 
				.AppendEllipsis()
				for len() < len() {
					 = append(, reflect.Value{})
				}
			}
			for ,  := range [- : ] {
				 := .WithDiffMode(diffIdentical).FormatDiff(.Value, )
				 = append(, textRecord{Key: (.Key), Value: })
				 = append(, .Key)
			}
			 = [:]
			continue
		}
Handle unequal records.
		for ,  := range [:.NumDiff()] {
			switch {
			case .CanFormatDiffSlice(.Value):
				 := .FormatDiffSlice(.Value)
				 = append(, textRecord{Key: (.Key), Value: })
				 = append(, .Key)
			case .Value.NumChildren == .Value.MaxDepth:
				 := .WithDiffMode(diffRemoved).FormatDiff(.Value, )
				 := .WithDiffMode(diffInserted).FormatDiff(.Value, )
				for  := 0;  <= maxVerbosityPreset &&  != nil &&  != nil && .Equal(); ++ {
					 := verbosityPreset(, )
					 = .WithDiffMode(diffRemoved).FormatDiff(.Value, )
					 = .WithDiffMode(diffInserted).FormatDiff(.Value, )
				}
				if  != nil {
					 = append(, textRecord{Diff: diffRemoved, Key: (.Key), Value: })
					 = append(, .Key)
				}
				if  != nil {
					 = append(, textRecord{Diff: diffInserted, Key: (.Key), Value: })
					 = append(, .Key)
				}
			default:
				 := .FormatDiff(.Value, )
				 = append(, textRecord{Key: (.Key), Value: })
				 = append(, .Key)
			}
		}
		 = [.NumDiff():]
		 += .NumDiff()
	}
	if .IsZero() {
		assert(len() == 0)
	} else {
		.AppendEllipsis()
		for len() < len() {
			 = append(, reflect.Value{})
		}
	}
	assert(len() == len())
For maps, the default formatting logic uses fmt.Stringer which may produce ambiguous output. Avoid calling String to disambiguate.
	if  == reflect.Map {
		var  bool
		 := map[string]reflect.Value{}
		for ,  := range  {
			if .IsValid() {
				 := [].Key
				,  := []
				if  && .CanInterface() && .CanInterface() {
					 = .Interface() != .Interface()
					if  {
						break
					}
				}
				[] = 
			}
		}
		if  {
			for ,  := range  {
				if .IsValid() {
					[].Key = formatMapKey(, true, )
				}
			}
		}
	}

	return &textWrap{Prefix: "{", Value: , Suffix: "}"}
}
coalesceAdjacentRecords coalesces the list of records into groups of adjacent equal, or unequal counts.
func ( string,  []reportRecord) ( []diffStats) {
	var  int // Arbitrary index into which case last occurred
	 := func( int) *diffStats {
		if  !=  {
			 = append(, diffStats{Name: })
			 = 
		}
		return &[len()-1]
	}
	for ,  := range  {
		switch  := .Value; {
		case .NumIgnored > 0 && .NumSame+.NumDiff == 0:
			(1).NumIgnored++
		case .NumDiff == 0:
			(1).NumIdentical++
		case .NumDiff > 0 && !.ValueY.IsValid():
			(2).NumRemoved++
		case .NumDiff > 0 && !.ValueX.IsValid():
			(2).NumInserted++
		default:
			(2).NumModified++
		}
	}
	return