package main

import (
	"errors"
	"sync"

	"github.com/barakmich/tinkerbell/ray_rpc"
	"go.uber.org/zap"
)

type WorkerPool interface {
	Schedule(*ray_rpc.Work) error
	Close() error
	Finish(*ray_rpc.WorkStatus) error
	Register(worker Worker) error
	Deregister(worker Worker) error
}

type SimpleRRWorkerPool struct {
	sync.Mutex
	workers []Worker
	store   ObjectStore
	offset  int
}

func NewRoundRobinWorkerPool(obj ObjectStore) *SimpleRRWorkerPool {
	return &SimpleRRWorkerPool{
		store: obj,
	}
}

func (wp *SimpleRRWorkerPool) Register(worker Worker) error {
	wp.Lock()
	defer wp.Unlock()
	wp.workers = append(wp.workers, worker)
	return nil
}

func (wp *SimpleRRWorkerPool) Schedule(work *ray_rpc.Work) error {
	wp.Lock()
	defer wp.Unlock()
	if len(wp.workers) == 0 {
		return errors.New("No workers available, try again later")
	}
	origOffset := wp.offset

	done := false
	for !done {
		worker := wp.workers[wp.offset]
		if worker.Schedulable() {
			zap.S().Info("Sending work to worker", wp.offset)
			wp.workers[wp.offset].AssignWork(work)
			done = true
		}
		wp.offset++
		if wp.offset == len(wp.workers) {
			wp.offset = 0
		}
		if wp.offset == origOffset && !done {
			return errors.New("No workers schedulable")
		}
	}
	return nil
}

func (wp *SimpleRRWorkerPool) Finish(status *ray_rpc.WorkStatus) error {
	if status.Status != ray_rpc.COMPLETE {
		panic("todo: Only call Finish on successfully completed work")
	}
	id := deserializeObjectID(status.FinishedTicket.ReturnId)
	return wp.store.PutObject(&Object{id, status.CompleteData})
}

func (wp *SimpleRRWorkerPool) Close() error {
	wp.Lock()
	defer wp.Unlock()
	for _, w := range wp.workers {
		w.Close()
	}
	return nil
}

func (wp *SimpleRRWorkerPool) Deregister(worker Worker) error {
	wp.Lock()
	defer wp.Unlock()
	found := false
	for i, w := range wp.workers {
		if w == worker {
			wp.workers = append(wp.workers[:i], wp.workers[i+1:]...)
			if wp.offset == len(wp.workers) {
				wp.offset = 0
			}
			worker.Close()
			found = true
			zap.S().Info("Deregistering worker", i)
		}
	}
	if !found {
		panic("Trying to deregister a worker that was never created")
	}
	return nil
}
