package cty

import (
	"fmt"
	"strings"
)

// marker is an internal wrapper type used to add special "marks" to values.
//
// A "mark" is an annotation that can be used to represent additional
// characteristics of values that propagate through operation methods to
// result values. However, a marked value cannot be used with integration
// methods normally associated with its type, in order to ensure that
// calling applications don't inadvertently drop marks as they round-trip
// values out of cty and back in again.
//
// Marked values are created only explicitly by the calling application, so
// an application that never marks a value does not need to worry about
// encountering marked values.
type marker struct {
	realV interface{}
	marks ValueMarks
}

// ValueMarks is a map, representing a set, of "mark" values associated with
// a Value. See Value.Mark for more information on the usage of mark values.
type ValueMarks map[interface{}]struct{}

// NewValueMarks constructs a new ValueMarks set with the given mark values.
//
// If any of the arguments are already ValueMarks values then they'll be merged
// into the result, rather than used directly as individual marks.
func NewValueMarks(marks ...interface{}) ValueMarks {
	if len(marks) == 0 {
		return nil
	}
	ret := make(ValueMarks, len(marks))
	for _, v := range marks {
		if vm, ok := v.(ValueMarks); ok {
			// Constructing a new ValueMarks with an existing ValueMarks
			// implements a merge operation. (This can cause our result to
			// have a larger size than we expected, but that's okay.)
			for v := range vm {
				ret[v] = struct{}{}
			}
			continue
		}
		ret[v] = struct{}{}
	}
	if len(ret) == 0 {
		// If we were merging ValueMarks values together and they were all
		// empty then we'll avoid returning a zero-length map and return a
		// nil instead, as is conventional.
		return nil
	}
	return ret
}

// Equal returns true if the receiver and the given ValueMarks both contain
// the same marks.
func (m ValueMarks) Equal(o ValueMarks) bool {
	if len(m) != len(o) {
		return false
	}
	for v := range m {
		if _, ok := o[v]; !ok {
			return false
		}
	}
	return true
}

func (m ValueMarks) GoString() string {
	var s strings.Builder
	s.WriteString("cty.NewValueMarks(")
	i := 0
	for mv := range m {
		if i != 0 {
			s.WriteString(", ")
		}
		s.WriteString(fmt.Sprintf("%#v", mv))
		i++
	}
	s.WriteString(")")
	return s.String()
}

// PathValueMarks is a structure that enables tracking marks
// and the paths where they are located in one type
type PathValueMarks struct {
	Path  Path
	Marks ValueMarks
}

func (p PathValueMarks) Equal(o PathValueMarks) bool {
	if !p.Path.Equals(o.Path) {
		return false
	}
	if !p.Marks.Equal(o.Marks) {
		return false
	}
	return true
}

// IsMarked returns true if and only if the receiving value carries at least
// one mark. A marked value cannot be used directly with integration methods
// without explicitly unmarking it (and retrieving the markings) first.
func (val Value) IsMarked() bool {
	_, ok := val.v.(marker)
	return ok
}

// HasMark returns true if and only if the receiving value has the given mark.
func (val Value) HasMark(mark interface{}) bool {
	if mr, ok := val.v.(marker); ok {
		_, ok := mr.marks[mark]
		return ok
	}
	return false
}

// ContainsMarked returns true if the receiving value or any value within it
// is marked.
//
// This operation is relatively expensive. If you only need a shallow result,
// use IsMarked instead.
func (val Value) ContainsMarked() bool {
	ret := false
	Walk(val, func(_ Path, v Value) (bool, error) {
		if v.IsMarked() {
			ret = true
			return false, nil
		}
		return true, nil
	})
	return ret
}

func (val Value) assertUnmarked() {
	if val.IsMarked() {
		panic("value is marked, so must be unmarked first")
	}
}

// Marks returns a map (representing a set) of all of the mark values
// associated with the receiving value, without changing the marks. Returns nil
// if the value is not marked at all.
func (val Value) Marks() ValueMarks {
	if mr, ok := val.v.(marker); ok {
		// copy so that the caller can't mutate our internals
		ret := make(ValueMarks, len(mr.marks))
		for k, v := range mr.marks {
			ret[k] = v
		}
		return ret
	}
	return nil
}

// HasSameMarks returns true if an only if the receiver and the given other
// value have identical marks.
func (val Value) HasSameMarks(other Value) bool {
	vm, vmOK := val.v.(marker)
	om, omOK := other.v.(marker)
	if vmOK != omOK {
		return false
	}
	if vmOK {
		return vm.marks.Equal(om.marks)
	}
	return true
}

// Mark returns a new value that as the same type and underlying value as
// the receiver but that also carries the given value as a "mark".
//
// Marks are used to carry additional application-specific characteristics
// associated with values. A marked value can be used with operation methods,
// in which case the marks are propagated to the operation results. A marked
// value _cannot_ be used with integration methods, so callers of those
// must derive an unmarked value using Unmark (and thus explicitly handle
// the markings) before calling the integration methods.
//
// The mark value can be any value that would be valid to use as a map key.
// The mark value should be of a named type in order to use the type itself
// as a namespace for markings. That type can be unexported if desired, in
// order to ensure that the mark can only be handled through the defining
// package's own functions.
//
// An application that never calls this method does not need to worry about
// handling marked values.
func (val Value) Mark(mark interface{}) Value {
	var newMarker marker
	newMarker.realV = val.v
	if mr, ok := val.v.(marker); ok {
		// It's already a marker, so we'll retain existing marks.
		newMarker.marks = make(ValueMarks, len(mr.marks)+1)
		for k, v := range mr.marks {
			newMarker.marks[k] = v
		}
		// unwrap the inner marked value, so we don't get multiple layers
		// of marking.
		newMarker.realV = mr.realV
	} else {
		// It's not a marker yet, so we're creating the first mark.
		newMarker.marks = make(ValueMarks, 1)
	}
	newMarker.marks[mark] = struct{}{}
	return Value{
		ty: val.ty,
		v:  newMarker,
	}
}

type applyPathValueMarksTransformer struct {
	pvm []PathValueMarks
}

func (t *applyPathValueMarksTransformer) Enter(p Path, v Value) (Value, error) {
	return v, nil
}

func (t *applyPathValueMarksTransformer) Exit(p Path, v Value) (Value, error) {
	for _, path := range t.pvm {
		if p.Equals(path.Path) {
			return v.WithMarks(path.Marks), nil
		}
	}
	return v, nil
}

// MarkWithPaths accepts a slice of PathValueMarks to apply
// markers to particular paths and returns the marked
// Value.
func (val Value) MarkWithPaths(pvm []PathValueMarks) Value {
	ret, _ := TransformWithTransformer(val, &applyPathValueMarksTransformer{pvm})
	return ret
}

// Unmark separates the marks of the receiving value from the value itself,
// removing a new unmarked value and a map (representing a set) of the marks.
//
// If the receiver isn't marked, Unmark returns it verbatim along with a nil
// map of marks.
func (val Value) Unmark() (Value, ValueMarks) {
	if !val.IsMarked() {
		return val, nil
	}
	mr := val.v.(marker)
	marks := val.Marks() // copy so that the caller can't mutate our internals
	return Value{
		ty: val.ty,
		v:  mr.realV,
	}, marks
}

type unmarkTransformer struct {
	pvm []PathValueMarks
}

func (t *unmarkTransformer) Enter(p Path, v Value) (Value, error) {
	unmarkedVal, marks := v.Unmark()
	if len(marks) > 0 {
		path := make(Path, len(p), len(p)+1)
		copy(path, p)
		t.pvm = append(t.pvm, PathValueMarks{path, marks})
	}
	return unmarkedVal, nil
}

func (t *unmarkTransformer) Exit(p Path, v Value) (Value, error) {
	return v, nil
}

// UnmarkDeep is similar to Unmark, but it works with an entire nested structure
// rather than just the given value directly.
//
// The result is guaranteed to contain no nested values that are marked, and
// the returned marks set includes the superset of all of the marks encountered
// during the operation.
func (val Value) UnmarkDeep() (Value, ValueMarks) {
	t := unmarkTransformer{}
	ret, _ := TransformWithTransformer(val, &t)

	marks := make(ValueMarks)
	for _, pvm := range t.pvm {
		for m, s := range pvm.Marks {
			marks[m] = s
		}
	}

	return ret, marks
}

// UnmarkDeepWithPaths is like UnmarkDeep, except it returns a slice
// of PathValueMarks rather than a superset of all marks. This allows
// a caller to know which marks are associated with which paths
// in the Value.
func (val Value) UnmarkDeepWithPaths() (Value, []PathValueMarks) {
	t := unmarkTransformer{}
	ret, _ := TransformWithTransformer(val, &t)
	return ret, t.pvm
}

func (val Value) unmarkForce() Value {
	unw, _ := val.Unmark()
	return unw
}

// WithMarks returns a new value that has the same type and underlying value
// as the receiver and also has the marks from the given maps (representing
// sets).
func (val Value) WithMarks(marks ...ValueMarks) Value {
	if len(marks) == 0 {
		return val
	}
	ownMarks := val.Marks()
	markCount := len(ownMarks)
	for _, s := range marks {
		markCount += len(s)
	}
	if markCount == 0 {
		return val
	}
	newMarks := make(ValueMarks, markCount)
	for m := range ownMarks {
		newMarks[m] = struct{}{}
	}
	for _, s := range marks {
		for m := range s {
			newMarks[m] = struct{}{}
		}
	}
	v := val.v
	if mr, ok := v.(marker); ok {
		v = mr.realV
	}
	return Value{
		ty: val.ty,
		v: marker{
			realV: v,
			marks: newMarks,
		},
	}
}

// WithSameMarks returns a new value that has the same type and underlying
// value as the receiver and also has the marks from the given source values.
//
// Use this if you are implementing your own higher-level operations against
// cty using the integration methods, to re-introduce the marks from the
// source values of the operation.
func (val Value) WithSameMarks(srcs ...Value) Value {
	if len(srcs) == 0 {
		return val
	}
	ownMarks := val.Marks()
	markCount := len(ownMarks)
	for _, sv := range srcs {
		if mr, ok := sv.v.(marker); ok {
			markCount += len(mr.marks)
		}
	}
	if markCount == 0 {
		return val
	}
	newMarks := make(ValueMarks, markCount)
	for m := range ownMarks {
		newMarks[m] = struct{}{}
	}
	for _, sv := range srcs {
		if mr, ok := sv.v.(marker); ok {
			for m := range mr.marks {
				newMarks[m] = struct{}{}
			}
		}
	}
	v := val.v
	if mr, ok := v.(marker); ok {
		v = mr.realV
	}
	return Value{
		ty: val.ty,
		v: marker{
			realV: v,
			marks: newMarks,
		},
	}
}