package main

import (
	"fmt"
	"reflect"

	"github.com/hashicorp/hcl/v2"
)

func findTraversalSpec(got hcl.Traversal, candidates []*TestFileExpectTraversal) *TestFileExpectTraversal {
	for _, candidate := range candidates {
		if traversalsAreEquivalent(candidate.Traversal, got) {
			return candidate
		}
	}
	return nil
}

func findTraversalForSpec(want *TestFileExpectTraversal, have []hcl.Traversal) hcl.Traversal {
	for _, candidate := range have {
		if traversalsAreEquivalent(candidate, want.Traversal) {
			return candidate
		}
	}
	return nil
}

func traversalsAreEquivalent(a, b hcl.Traversal) bool {
	if len(a) != len(b) {
		return false
	}
	for i := range a {
		aStep := a[i]
		bStep := b[i]

		if reflect.TypeOf(aStep) != reflect.TypeOf(bStep) {
			return false
		}

		// We can now assume that both are of the same type.
		switch ts := aStep.(type) {

		case hcl.TraverseRoot:
			if bStep.(hcl.TraverseRoot).Name != ts.Name {
				return false
			}

		case hcl.TraverseAttr:
			if bStep.(hcl.TraverseAttr).Name != ts.Name {
				return false
			}

		case hcl.TraverseIndex:
			if !bStep.(hcl.TraverseIndex).Key.RawEquals(ts.Key) {
				return false
			}

		default:
			return false
		}
	}
	return true
}

// checkTraversalsMatch determines if a given traversal matches the given
// expectation, which must've been produced by an earlier call to
// findTraversalSpec for the same traversal.
func checkTraversalsMatch(got hcl.Traversal, filename string, match *TestFileExpectTraversal) hcl.Diagnostics {
	var diags hcl.Diagnostics

	gotRng := got.SourceRange()
	wantRng := match.Range

	if got, want := gotRng.Filename, filename; got != want {
		diags = append(diags, &hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Incorrect filename in detected traversal",
			Detail: fmt.Sprintf(
				"Filename was reported as %q, but was expecting %q.",
				got, want,
			),
			Subject: match.Traversal.SourceRange().Ptr(),
		})
		return diags
	}

	// If we have the expected filename then we'll use that to construct the
	// full "want range" here so that we can use it to point to the appropriate
	// location in the remaining diagnostics.
	wantRng.Filename = filename

	if got, want := gotRng.Start, wantRng.Start; got != want {
		diags = append(diags, &hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Incorrect start position in detected traversal",
			Detail: fmt.Sprintf(
				"Start position was reported as line %d column %d byte %d, but was expecting line %d column %d byte %d.",
				got.Line, got.Column, got.Byte,
				want.Line, want.Column, want.Byte,
			),
			Subject: &wantRng,
		})
	}
	if got, want := gotRng.End, wantRng.End; got != want {
		diags = append(diags, &hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Incorrect end position in detected traversal",
			Detail: fmt.Sprintf(
				"End position was reported as line %d column %d byte %d, but was expecting line %d column %d byte %d.",
				got.Line, got.Column, got.Byte,
				want.Line, want.Column, want.Byte,
			),
			Subject: &wantRng,
		})
	}
	return diags
}