package collection

import (
	. "gitea.zaclys.com/bvaudour/gob/option"
)

// List représente une collection d’élément qui peut être modifiée (ajout/suppression) par chaque bout.
// Elle peut donc se comporter à la fois comme une pile et une file.
type List[T any] struct {
	first *dualChain[T]
	last  *dualChain[T]
	size  uint
}

// Len retourne le nombre d’éléments de la liste.
func (l *List[T]) Len() uint {
	return l.size
}

func (l *List[T]) push_front(e T) {
	var (
		c        = newDual(e)
		oldFirst Option[*dualChain[T]]
	)

	if l.size == 0 {
		l.last = c
	} else {
		oldFirst = Some(l.first)
	}

	link(Some(c), oldFirst)
	l.first = c
	l.size++
}

func (l *List[T]) push_back(e T) {
	var (
		c       = newDual(e)
		oldLast Option[*dualChain[T]]
	)

	if l.size == 0 {
		l.first = c
	} else {
		oldLast = Some(l.last)
	}
	link(oldLast, Some(c))

	l.last = c
	l.size++
}

// PushFront ajoute tous les éléments d’entrée en début de liste.
func (l *List[T]) PushFront(elems ...T) {
	for _, e := range elems {
		l.push_front(e)
	}
}

// PushBack ajoute tous les éléments d’entrée en fin de liste.
func (l *List[T]) PushBack(elems ...T) {
	for _, e := range elems {
		l.push_back(e)
	}
}

// PopFront supprime le premier élément de la liste.
func (l *List[T]) PopFront() (e Option[T]) {
	if l.size > 0 {
		e = Some(l.first.value)
		l.first = l.first.next
		l.size--
		link(None[*dualChain[T]](), Some(l.first))
		if l.size == 0 {
			l.last = l.first
		}
	}

	return
}

// PopLast supprime le dernier élément de la liste.
func (l *List[T]) PopLast() (e Option[T]) {
	if l.size > 0 {
		e = Some(l.last.value)
		l.last = l.last.prev
		l.size--
		link(Some(l.last), None[*dualChain[T]]())
		if l.size == 0 {
			l.first = l.last
		}
	}

	return
}

// Concatenate concatène deux listes.
func (q1 *List[T]) Concatenate(l2 *List[T]) {
	c, l := l2.first, l2.size
	for i := uint(0); i < l; i++ {
		q1.push_back(c.value)
		c = c.next
	}
}

// ToSlice convertit une liste en slice.
func (l *List[T]) ToSlice() (sl []T) {
	sl = make([]T, l.size)
	c := l.first
	for i := uint(0); i < l.size; i++ {
		sl[i] = c.value
		c = c.next
	}

	return
}

// NewList retourne une liste initialisée à partir des données d’entrées.
func NewList[T any](elems ...T) (l *List[T]) {
	l = new(List[T])
	l.PushBack(elems...)

	return
}
