package collection

// Slice crée un slice générique contenant les valeurs d’entrée.
func Slice[T any](in ...T) (out []any) {
	out = make([]any, len(in))

	for i, e := range in {
		out[i] = e
	}

	return
}

// Add ajoute les valeurs d’entrée en fin du slice.
func Add[T any](s *([]T), in ...T) {
	*s = append(*s, in...)
}

// Insert ajoute les valeurs d’entrée en début du slice.
func Insert[T any](s *([]T), in ...T) {
	*s = append(in, (*s)...)
}

// InsertAt ajoute les valeurs d’entrée à l’index i du slice.
// - Si l’index est inférieur à 0, agit comme Insert.
// - Si l’index est supérieur ou égal à la taille du slice, agit comme Add.
func InsertAt[T any](s *([]T), i int, in ...T) {
	if i <= 0 {
		Insert(s, in...)
		return
	} else if i >= len(*s) {
		Add(s, in...)
		return
	}

	l1, l2 := len(*s), len(in)
	result := make([]T, l1+l2)
	copy(result[:i], (*s)[:i])
	copy(result[i:i+l2], in)
	copy(result[i+l2:], (*s)[i:])

	*s = result
}

// Diff retourne un slice contenant les entrées de s1 qui ne sont pas dans s2.
func Diff[T comparable](s1, s2 []T) (out []T) {
	ss := NewSet(s2...)

	for _, e := range s1 {
		if !ss.Contains(e) {
			Add(&out, e)
		}
	}

	return
}

// Intersect retourne un slice contenant les entrées contenues à la fois dans s1 et s2.
func Intersect[T comparable](s1, s2 []T) (out []T) {
	ss := NewSet(s2...)

	for _, e := range s1 {
		if ss.Contains(e) {
			Add(&out, e)
		}
	}

	return
}

// Uniq supprime les valeurs redondantes du slice.
func Uniq[T comparable](s []T) (out []T) {
	ss := NewSet[T]()

	for _, e := range s {
		if !ss.Contains(e) {
			ss.Add(e)
			Add(&out, e)
		}
	}

	return
}

// AddUniq ajoute les entrées en fin de slice si elles n’existent pas dans le slice.
func AddUniq[T comparable](s *([]T), in ...T) {
	in2 := Uniq(Diff(in, *s))
	Add(s, in2...)
}

// InsertUniq ajoute les entrées en début de slice si elles n’existent pas dans le slice.
func InsertUniq[T comparable](s *([]T), in ...T) {
	in2 := Uniq(Diff(in, *s))
	Insert(s, in2...)
}

// InsertAtUniq ajoute les entrées dans le slice à la position donnée si elles n’existent pas dans le slice.
func InsertAtUniq[T comparable](s *([]T), i int, in ...T) {
	in2 := Uniq(Diff(in, *s))
	InsertAt(s, i, in2...)
}

// Contains retourne vrai si le slice contient la valeur d’entrée.
func Contains[T comparable](s []T, in T) bool {
	for _, e := range s {
		if e == in {
			return true
		}
	}

	return false
}

// ContainsOneOf retourne vrai si le slice contient une des valeurs d’entrée.
func ContainsOneOf[T comparable](s []T, in ...T) bool {
	return NewSet(s...).ContainsOneOf(in...)
}

// ContainsAll retourne vrai si le slice contient toutes les valeurs d’entrée.
func ContainsAll[T comparable](s []T, in ...T) bool {
	return NewSet(s...).ContainsAll(in...)
}

// Maps retourne un slice à partir d’un autre slice en appliquant une fonction de transformation pour chaque valeur.
func Maps[T1, T2 any](s []T1, f func(T1) T2) (out []T2) {
	out = make([]T2, len(s))

	for i, e := range s {
		out[i] = f(e)
	}

	return
}

// Filter retourne un nouveau slice dont les valeurs passent la fonction.
func Filter[T any](s []T, f func(T) bool) (out []T) {
	for _, e := range s {
		if f(e) {
			Add(&out, e)
		}
	}

	return
}

// Reduce applique une fonction à tous les éléments dont le résultat sert à la prochaine itération.
func Reduce[T any](s []T, f func(T, T) T, initial ...T) (out T) {
	if len(initial) > 0 {
		out = initial[0]
	}

	for _, e := range s {
		out = f(out, e)
	}

	return
}

// Reverse inverse l’ordre du slice.
func Reverse[T any](s []T) (out []T) {
	l := len(s)
	out = make([]T, l)
	for i, e := range s {
		out[l-1-i] = e
	}

	return
}

// Zip retourne une map dont les clés sont les valeurs du premier slice et les valeurs, les valeurs du second.
func Zip[K comparable, V any](s1 []K, s2 []V) (out map[K]V) {
	out = make(map[K]V)
	l := min(len(s1), len(s2))

	for i, k := range s1[:l] {
		out[k] = s2[i]
	}

	return
}

// Unzip sépare les clés et les valeurs de la map en 2 slices distincts.
func Unzip[K comparable, V any](m map[K]V) (keys []K, values []V) {
	for k, v := range m {
		Add(&keys, k)
		Add(&values, v)
	}

	return
}

// Equal retourne vrai si les slices sont identiques.
func Equal[T comparable](sl1, sl2 []T) bool {
	if len(sl1) != len(sl2) {
		return false
	}

	for i, e1 := range sl1 {
		if e1 != sl2[i] {
			return false
		}
	}

	return true
}
