// Package repeater call fun till it returns no error, up to repeat some number of iterations and delays defined by strategy.
// Repeats number and delays defined by strategy.Interface. Terminates immediately on err from
// provided, optional list of critical errors
package repeater

import (
	"context"
	"time"

	"github.com/go-pkgz/repeater/strategy"
)

// Repeater is the main object, should be made by New or NewDefault, embeds strategy
type Repeater struct {
	strategy.Interface
}

// New repeater with a given strategy. If strategy=nil initializes with FixedDelay 5sec, 10 times.
func New(strtg strategy.Interface) *Repeater {
	if strtg == nil {
		strtg = &strategy.FixedDelay{Repeats: 10, Delay: time.Second * 5}
	}
	result := Repeater{Interface: strtg}
	return &result
}

// NewDefault makes repeater with FixedDelay strategy
func NewDefault(repeats int, delay time.Duration) *Repeater {
	return New(&strategy.FixedDelay{Repeats: repeats, Delay: delay})
}

// Do repeats fun till no error. Predefined (optional) errors terminate immediately
func (r Repeater) Do(ctx context.Context, fun func() error, errors ...error) (err error) {

	ctx, cancelFunc := context.WithCancel(ctx)
	defer cancelFunc() // ensure strategy's channel termination

	inErrors := func(err error) bool {
		for _, e := range errors {
			if e == err {
				return true
			}
		}
		return false
	}

	ch := r.Start(ctx) // channel of ticks-like events provided by strategy
	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case _, ok := <-ch:
			if !ok { // closed channel indicates completion or early termination, set by strategy
				return err
			}
			if err = fun(); err == nil {
				return nil
			}
			if err != nil && inErrors(err) { // terminate on critical error from provided list
				return err
			}
		}
	}
}
