package sync

import (
	"fmt"
	"math"
	"time"

	"github.com/micro/go-micro/sync/leader/etcd"
	"github.com/micro/go-micro/sync/task"
	"github.com/micro/go-micro/sync/task/local"
	"github.com/micro/go-micro/util/log"
)

type syncCron struct {
	opts Options
}

func backoff(attempts int) time.Duration {
	if attempts == 0 {
		return time.Duration(0)
	}
	return time.Duration(math.Pow(10, float64(attempts))) * time.Millisecond
}

func (c *syncCron) Schedule(s task.Schedule, t task.Command) error {
	id := fmt.Sprintf("%s-%s", s.String(), t.String())

	go func() {
		// run the scheduler
		tc := s.Run()

		var i int

		for {
			// leader election
			e, err := c.opts.Leader.Elect(id)
			if err != nil {
				log.Logf("[cron] leader election error: %v", err)
				time.Sleep(backoff(i))
				i++
				continue
			}

			i = 0
			r := e.Revoked()

			// execute the task
		Tick:
			for {
				select {
				// schedule tick
				case _, ok := <-tc:
					// ticked once
					if !ok {
						break Tick
					}

					log.Logf("[cron] executing command %s", t.Name)
					if err := c.opts.Task.Run(t); err != nil {
						log.Logf("[cron] error executing command %s: %v", t.Name, err)
					}
				// leader revoked
				case <-r:
					break Tick
				}
			}

			// resign
			e.Resign()
		}
	}()

	return nil
}

func NewCron(opts ...Option) Cron {
	var options Options
	for _, o := range opts {
		o(&options)
	}

	if options.Leader == nil {
		options.Leader = etcd.NewLeader()
	}

	if options.Task == nil {
		options.Task = local.NewTask()
	}

	return &syncCron{
		opts: options,
	}
}