package concurrent

import (
	"sync"

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

// Slice fournit une solution pour ajouter des éléments dans un slice de façon concurrente.
type Slice[T any] struct {
	data   []T
	mtx    sync.Mutex
	closed bool
}

func (sl *Slice[T]) checkOpened() {
	if sl.closed {
		panic("The slice cannot be modified anymore.")
	}
}

func (sl *Slice[T]) checkClosed() {
	if !sl.closed {
		panic("The slice needs to be opened.")
	}
}

func (sl *Slice[T]) changeState(closed bool) {
	sl.mtx.Lock()
	defer sl.mtx.Unlock()

	sl.closed = closed
}

func (sl *Slice[T]) get() (out []T) {
	out = make([]T, len(sl.data))
	copy(out, sl.data)

	return
}

func (sl *Slice[T]) checkIdx(i int) bool {
	return i >= 0 && i < len(sl.data)
}

// Insert ajoute les entrées fournies en début de slice.
// Cette méthode nécessite que le slice soit ouvert.
func (sl *Slice[T]) Insert(elems ...T) {
	sl.mtx.Lock()
	defer sl.mtx.Unlock()

	sl.checkOpened()
	sl.data = append(elems, sl.data...)
}

// Insert ajoute les entrées fournies en fin de slice.
// Cette méthode nécessite que le slice soit ouvert.
func (sl *Slice[T]) Append(elems ...T) {
	sl.mtx.Lock()
	defer sl.mtx.Unlock()

	sl.checkOpened()
	sl.data = append(sl.data, elems...)
}

// Clear vide le slice.
// Cette méthode nécessite que le slice soit ouvert.
func (sl *Slice[T]) Clear() {
	sl.mtx.Lock()
	defer sl.mtx.Unlock()

	sl.checkOpened()
	sl.data = []T{}
}

// Close empêche les modifications du slice.
func (sl *Slice[T]) Close() {
	sl.changeState(true)
}

// Open permet les modifications du slice.
func (sl *Slice[T]) Open() {
	sl.changeState(false)
}

// Set modifie l’entrée du slice à l’index donné et retourne vrai si l’entrée a été modifiée.
// Cette méthode nécessite que le slice soit ouvert.
func (sl *Slice[T]) Set(index int, value T) (ok bool) {
	sl.mtx.Lock()
	defer sl.mtx.Unlock()

	sl.checkOpened()
	if ok = sl.checkIdx(index); ok {
		sl.data[index] = value
	}

	return
}

// Get retourne la valeur du slice à l’index donné.
func (sl *Slice[T]) Get(index int) (value Option[T]) {
	sl.mtx.Lock()
	defer sl.mtx.Unlock()

	if sl.checkIdx(index) {
		value = Some(sl.data[index])
	}

	return
}

// Data retourne le slice.
// Cette méthode nécessite que le slice soit fermé.
func (sl *Slice[T]) Data() []T {
	sl.mtx.Lock()
	defer sl.mtx.Unlock()

	sl.checkClosed()

	return sl.get()
}

// CloseData retourne le slice après l’avoir préalablement fermé en écriture.
func (sl *Slice[T]) CloseData() []T {
	sl.mtx.Lock()
	defer sl.mtx.Unlock()

	sl.closed = true

	return sl.get()
}

// NewSlice retourne un slice initialisé avec les valeurs fournies.
func NewSlice[T any](elems ...T) *Slice[T] {
	return &Slice[T]{
		data: elems,
	}
}
