package syncz

import (
	"google.golang.org/protobuf/proto"
)

// SyncWorkerHolder holds a worker and restarts it when configuration changes.
type SyncWorkerHolder[C any] struct {
	run           func(config C) func()
	isEqual       func(config1, config2 C) bool
	currentStop   func()
	currentConfig C
}

func NewSyncWorkerHolder[C any](run func(config C) func(), isEqual func(config1, config2 C) bool) *SyncWorkerHolder[C] {
	return &SyncWorkerHolder[C]{
		run:     run,
		isEqual: isEqual,
	}
}

func NewComparableSyncWorkerHolder[C comparable](run func(config C) func()) *SyncWorkerHolder[C] {
	return &SyncWorkerHolder[C]{
		run: run,
		isEqual: func(config1, config2 C) bool {
			return config1 == config2
		},
	}
}

func NewProtoSyncWorkerHolder[C proto.Message](run func(config C) func()) *SyncWorkerHolder[C] {
	return &SyncWorkerHolder[C]{
		run: run,
		isEqual: func(config1, config2 C) bool {
			return proto.Equal(config1, config2)
		},
	}
}

// ApplyConfig ensures a worker is running with the provided or equal config.
//
// This method starts a worker if it's not running already. If it is running and the config is not equal
// then the worker is stopped, a new worker is started then with the new config.
func (w *SyncWorkerHolder[C]) ApplyConfig(config C) bool {
	if w.currentStop != nil {
		if w.isEqual(config, w.currentConfig) {
			return false
		}
		w.currentStop()
	}
	w.currentConfig = config
	w.currentStop = w.run(config)
	return true
}

func (w *SyncWorkerHolder[C]) Stop() {
	if w.currentStop == nil {
		return // nothing to do
	}
	w.currentStop()
	w.currentStop = nil
	var c C
	w.currentConfig = c
}
