package worker

import (
	"time"

	"github.com/go-logr/logr"
	"github.com/pkg/errors"
)

type Worker interface {
	Stop()
	Start()
}

type PeriodWorker struct {
	runnable     func() error
	initialDelay time.Duration
	interval     time.Duration
	log          logr.Logger

	fistTime bool
	stopped  bool
	stopCh   chan bool
	doneCh   chan bool
}

func NewPeriodWorker(log logr.Logger, interval time.Duration, f func() error) *PeriodWorker {
	return NewPeriodWorkerWithDelay(log, time.Second*0, interval, f)
}

func NewPeriodWorkerWithDelay(log logr.Logger, delay time.Duration, interval time.Duration, f func() error) *PeriodWorker {
	return &PeriodWorker{
		runnable:     f,
		initialDelay: delay,
		interval:     interval,
		log:          log,
		fistTime:     true,
		stopped:      false,
		stopCh:       make(chan bool),
		doneCh:       make(chan bool),
	}
}

func (worker *PeriodWorker) Stop() {
	worker.log.Info("stopping worker")
	worker.stopCh <- true
	<-worker.doneCh
	worker.log.Info("worker is stopped")
}

func (worker *PeriodWorker) Start() {
	failed := 0
	go func() {
		for !worker.stopped {
			var t *time.Timer
			if worker.fistTime {
				t = time.NewTimer(worker.initialDelay)
				worker.fistTime = false
			} else {
				t = time.NewTimer(worker.interval)
			}
			func() {
				defer func() {
					if r := recover(); r != nil {
						failed += 1
						worker.log.Error(errors.New("run worker panic"), "recover", "err", r)
					}
				}()

				select {
				case <-worker.stopCh:
					worker.stopped = true
					worker.doneCh <- true
					return
				case <-t.C:
					if err := worker.runnable(); err != nil {
						failed += 1
						worker.log.Error(err, "recover")
					} else {
						failed = 0
					}
				}
			}()
		}
	}()
}
