package timer

import (
	"context"
	"sync"
	"time"

	"git.simanc.com/software/golib/v4/log"
)

type Handler func() error

type Timer struct {
	idx map[string]context.CancelFunc

	log log.Logger
	mu  sync.Mutex
}

func (t *Timer) Register(name string, handler Handler, d time.Duration) {
	t.mu.Lock()
	if _, ok := t.idx[name]; ok {
		panic("timer: duplicate name:" + name)
	}
	t.log.Info("timer.Register: %s: cycle time: %s with started", name, d)
	ctx, cancel := context.WithCancel(context.Background())
	go t.handleRegister(ctx, name, handler, d)
	t.idx[name] = cancel
	t.mu.Unlock()
}

func (t *Timer) Stop(name string) {
	t.mu.Lock()
	cancel, ok := t.idx[name]
	if ok {
		cancel()
		delete(t.idx, name)
		t.log.Warn("timer.Stop: stopped %s", name)
	}
	t.mu.Unlock()
}

func (t *Timer) StopAll() {
	t.log.Warn("timer.StopAll: starting")
	t.mu.Lock()
	for name, cancel := range t.idx {
		cancel()
		t.log.Warn("timer.StopAll: stopped %s", name)
	}
	t.idx = make(map[string]context.CancelFunc)
	t.mu.Unlock()
	t.log.Warn("timer.StopAll: done")
}

func (t *Timer) handleRegister(ctx context.Context, name string, handler Handler, d time.Duration) {
	tim := time.NewTimer(d)
	for {
		select {
		case <-ctx.Done():
			return
		case <-tim.C:
			t.log.Info("timer.handleRegister: %s: executing", name)
			if err := handler(); err != nil {
				t.log.Error("timer.handleRegister: %s: exec failed: %s", name, err)
			} else {
				t.log.Info("timer.handleRegister: %s: exec succeeded", name)
			}
			tim.Reset(d)
		}
	}
}

func New(logger log.Logger) *Timer {
	t := new(Timer)
	t.idx = make(map[string]context.CancelFunc)
	t.log = logger
	return t
}
