package service

import (
	"fmt"
	"log"
	"time"

	"github.com/bazelbuild/continuous-integration/metrics/metrics"
	"github.com/bazelbuild/continuous-integration/metrics/publishers"
)

type metricJob struct {
	ticker     *time.Ticker
	metric     metrics.Metric
	publishers []publishers.Publisher
}

func (job *metricJob) start(handler ErrorHandler) {
	err := job.initialize()
	if err != nil {
		handler(job.metric.Name(), err)
		return
	}

	go func(currentJob metricJob) {
		for ; true; <-currentJob.ticker.C {
			currentJob.run(handler)
		}
	}(*job)
}

func (job *metricJob) initialize() error {
	for _, publisher := range job.publishers {
		err := publisher.RegisterMetric(job.metric)
		if err != nil {
			return err
		}
	}
	return nil
}

func (job *metricJob) run(handler ErrorHandler) {
	name := job.metric.Name()
	log.Printf("Collecting data for metric %s\n", name)
	newData, err := job.metric.Collect()
	if err != nil {
		handler(name, fmt.Errorf("Collection failed': %v", err))
		return
	}
	for _, p := range job.publishers {
		log.Printf("Publishing data for metric %s to %s\n", name, p.Name())
		err = p.Publish(job.metric, newData)
		if err != nil {
			handler(name, fmt.Errorf("Publishing to %s failed': %v", p.Name(), err))
		}
	}
}

func (job *metricJob) stop() {
	job.ticker.Stop()
}

func createJob(metric metrics.Metric, updateInterval time.Duration, publisherInstances []publishers.Publisher) metricJob {
	return metricJob{metric: metric, ticker: time.NewTicker(updateInterval), publishers: publisherInstances}
}

type ErrorHandler func(string, error)

type MetricService struct {
	jobs    map[string]metricJob
	handler ErrorHandler
}

func CreateService(handler ErrorHandler) *MetricService {
	return &MetricService{jobs: make(map[string]metricJob), handler: handler}
}

func (srv *MetricService) AddMetric(metric metrics.Metric, updateInterval time.Duration, publisherInstances ...publishers.Publisher) error {
	name := metric.Name()
	if _, ok := srv.jobs[name]; ok {
		return fmt.Errorf("There is already a job for metric '%s'", name)
	}
	srv.jobs[name] = createJob(metric, updateInterval, publisherInstances)
	return nil
}

func (srv *MetricService) RunJobsOnce() {
	for _, j := range srv.jobs {
		err := j.initialize()
		if err != nil {
			srv.handler(j.metric.Name(), err)
			return
		}
		j.run(srv.handler)
	}
}

func (srv *MetricService) Start() {
	for _, j := range srv.jobs {
		j.start(srv.handler)
	}
}

func (srv *MetricService) Stop() {
	for _, j := range srv.jobs {
		j.stop()
	}
}
