/*
Copyright (c) Edgeless Systems GmbH

SPDX-License-Identifier: AGPL-3.0-only
*/

// Package clean provides functionality to stop a list of services gracefully and synchronously.
package clean

import (
	"sync"
)

// Cleaner can be used to stop a list of services gracefully.
// To register an arbitrary amount of stoppers either use New or With.
// Start needs to be called to ready the Cleaner, then Clean will activate it.
// Done can be used to wait for Cleaner to run all registered stoppers.
type Cleaner struct {
	stoppers  []stopper
	stopC     chan struct{}
	startOnce sync.Once
	wg        sync.WaitGroup
}

// New creates a new cleaner.
func New(stoppers ...stopper) *Cleaner {
	res := &Cleaner{
		stoppers: stoppers,
		stopC:    make(chan struct{}, 1),
	}
	res.wg.Add(1) // for the Start goroutine
	return res
}

// With adds a new stopper to the cleaner.
func (c *Cleaner) With(stopper stopper) *Cleaner {
	c.stoppers = append(c.stoppers, stopper)
	return c
}

// Start blocks until it receives a stop message, stops all services gracefully and returns.
func (c *Cleaner) Start() {
	c.startOnce.Do(func() {
		defer c.wg.Done()
		// wait for the stop message
		<-c.stopC

		c.wg.Add(len(c.stoppers))
		for _, stopItem := range c.stoppers {
			go func(stopItem stopper) {
				defer c.wg.Done()
				stopItem.Stop()
			}(stopItem)
		}
	})
}

// Clean initiates the cleanup but does not wait for it to complete.
func (c *Cleaner) Clean() {
	// try to enqueue the stop message once
	// if the channel is full, the message is dropped
	select {
	case c.stopC <- struct{}{}:
	default:
	}
}

// Done waits for the cleanup to complete.
func (c *Cleaner) Done() {
	c.wg.Wait()
}

type stopper interface {
	Stop()
}
