package set

import (
	"sort"
)

// Add inserts the given value into the receiving Set.
//
// This mutates the set in-place. This operation is not thread-safe.
func (s Set) Add(val interface{}) {
	hv := s.rules.Hash(val)
	if _, ok := s.vals[hv]; !ok {
		s.vals[hv] = make([]interface{}, 0, 1)
	}
	bucket := s.vals[hv]

	// See if an equivalent value is already present
	for _, ev := range bucket {
		if s.rules.Equivalent(val, ev) {
			return
		}
	}

	s.vals[hv] = append(bucket, val)
}

// Remove deletes the given value from the receiving set, if indeed it was
// there in the first place. If the value is not present, this is a no-op.
func (s Set) Remove(val interface{}) {
	hv := s.rules.Hash(val)
	bucket, ok := s.vals[hv]
	if !ok {
		return
	}

	for i, ev := range bucket {
		if s.rules.Equivalent(val, ev) {
			newBucket := make([]interface{}, 0, len(bucket)-1)
			newBucket = append(newBucket, bucket[:i]...)
			newBucket = append(newBucket, bucket[i+1:]...)
			if len(newBucket) > 0 {
				s.vals[hv] = newBucket
			} else {
				delete(s.vals, hv)
			}
			return
		}
	}
}

// Has returns true if the given value is in the receiving set, or false if
// it is not.
func (s Set) Has(val interface{}) bool {
	hv := s.rules.Hash(val)
	bucket, ok := s.vals[hv]
	if !ok {
		return false
	}

	for _, ev := range bucket {
		if s.rules.Equivalent(val, ev) {
			return true
		}
	}
	return false
}

// Copy performs a shallow copy of the receiving set, returning a new set
// with the same rules and elements.
func (s Set) Copy() Set {
	ret := NewSet(s.rules)
	for k, v := range s.vals {
		ret.vals[k] = v
	}
	return ret
}

// Iterator returns an iterator over values in the set, in an undefined order
// that callers should not depend on.
//
// The pattern for using the returned iterator is:
//
//     it := set.Iterator()
//     for it.Next() {
//         val := it.Value()
//         // ...
//     }
//
// Once an iterator has been created for a set, the set *must not* be mutated
// until the iterator is no longer in use.
func (s Set) Iterator() *Iterator {
	// Sort the bucketIds to ensure that we always traverse in a
	// consistent order.
	bucketIds := make([]int, 0, len(s.vals))
	for id := range s.vals {
		bucketIds = append(bucketIds, id)
	}
	sort.Ints(bucketIds)

	return &Iterator{
		bucketIds: bucketIds,
		vals:      s.vals,
		bucketIdx: -1,
	}
}

// EachValue calls the given callback once for each value in the set, in an
// undefined order that callers should not depend on.
func (s Set) EachValue(cb func(interface{})) {
	it := s.Iterator()
	for it.Next() {
		cb(it.Value())
	}
}

// Values returns a slice of all of the values in the set in no particular
// order. This is just a wrapper around EachValue that accumulates the results
// in a slice for caller convenience.
//
// The returned slice will be nil if there are no values in the set.
func (s Set) Values() []interface{} {
	var ret []interface{}
	s.EachValue(func(v interface{}) {
		ret = append(ret, v)
	})
	return ret
}

// Length returns the number of values in the set.
func (s Set) Length() int {
	var count int
	for _, bucket := range s.vals {
		count = count + len(bucket)
	}
	return count
}

// Union returns a new set that contains all of the members of both the
// receiving set and the given set. Both sets must have the same rules, or
// else this function will panic.
func (s1 Set) Union(s2 Set) Set {
	mustHaveSameRules(s1, s2)
	rs := NewSet(s1.rules)
	s1.EachValue(func(v interface{}) {
		rs.Add(v)
	})
	s2.EachValue(func(v interface{}) {
		rs.Add(v)
	})
	return rs
}

// Intersection returns a new set that contains the values that both the
// receiver and given sets have in common. Both sets must have the same rules,
// or else this function will panic.
func (s1 Set) Intersection(s2 Set) Set {
	mustHaveSameRules(s1, s2)
	rs := NewSet(s1.rules)
	s1.EachValue(func(v interface{}) {
		if s2.Has(v) {
			rs.Add(v)
		}
	})
	return rs
}

// Subtract returns a new set that contains all of the values from the receiver
// that are not also in the given set. Both sets must have the same rules,
// or else this function will panic.
func (s1 Set) Subtract(s2 Set) Set {
	mustHaveSameRules(s1, s2)
	rs := NewSet(s1.rules)
	s1.EachValue(func(v interface{}) {
		if !s2.Has(v) {
			rs.Add(v)
		}
	})
	return rs
}

// SymmetricDifference returns a new set that contains all of the values from
// both the receiver and given sets, except those that both sets have in
// common. Both sets must have the same rules, or else this function will
// panic.
func (s1 Set) SymmetricDifference(s2 Set) Set {
	mustHaveSameRules(s1, s2)
	rs := NewSet(s1.rules)
	s1.EachValue(func(v interface{}) {
		if !s2.Has(v) {
			rs.Add(v)
		}
	})
	s2.EachValue(func(v interface{}) {
		if !s1.Has(v) {
			rs.Add(v)
		}
	})
	return rs
}
