// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information

package sync2

import (
	"sync"
)

// WorkGroup implements waitable and closable group of workers.
type WorkGroup struct {
	noCopy noCopy //nolint:structcheck

	mu   sync.Mutex
	cond sync.Cond

	closed  bool
	workers int
}

// init initializes work group.
func (group *WorkGroup) init() {
	if group.cond.L == nil {
		group.cond.L = &group.mu
	}
}

// Go starts func and tracks the execution.
// Returns false when WorkGroup has been closed.
func (group *WorkGroup) Go(fn func()) bool {
	if !group.Start() {
		return false
	}
	go func() {
		defer group.Done()
		fn()
	}()
	return true
}

// Start returns true when work can be started.
func (group *WorkGroup) Start() bool {
	group.mu.Lock()
	defer group.mu.Unlock()

	group.init()
	if group.closed {
		return false
	}
	group.workers++
	return true
}

// Done finishes a pending work item.
func (group *WorkGroup) Done() {
	group.mu.Lock()
	defer group.mu.Unlock()

	group.workers--
	if group.workers < 0 {
		panic("worker count below zero")
	}
	if group.workers == 0 {
		group.cond.Broadcast()
	}
}

// Wait waits for all workers to finish.
func (group *WorkGroup) Wait() {
	group.mu.Lock()
	defer group.mu.Unlock()

	group.init()

	for group.workers != 0 {
		group.cond.Wait()
	}
}

// Close prevents from new work being started.
func (group *WorkGroup) Close() {
	group.mu.Lock()
	defer group.mu.Unlock()

	group.init()
	group.closed = true
}
