package ru.kirillius.pf.sdn.service;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.kirillius.java.utils.events.ConcurrentEventHandler;
import ru.kirillius.java.utils.events.EventHandler;
import ru.kirillius.pf.sdn.api.flow.Flow;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Запускает и выполняет Flow в порядке очереди
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class FlowExecutorService {
    private final ExecutorService executor;
    private final Queue<Flow> executionQueue = new ConcurrentLinkedQueue<>();

    public Flow getCurrentFlow() {
        return currentFlow.get();
    }

    private final AtomicReference<Flow> currentFlow = new AtomicReference<>();
    @Getter
    private final EventHandler<Flow> onExecuted = new ConcurrentEventHandler<>();

    public void triggerExecute(Flow pipeline) {
        if (executionQueue.contains(pipeline)) {
            return;
        }
        executionQueue.add(pipeline);
    }

    public void cancel(Flow pipeline) {
        pipeline.interrupt();
        executionQueue.remove(pipeline);
    }

    @PostConstruct
    private void initialize() {
        worker = executor.submit(new FlowWorker());
    }

    @PreDestroy
    private void destroy() {
        if (worker != null) {
            worker.cancel(true);
        }
    }

    private Future<?> worker;

    private class FlowWorker implements Runnable {
        @SuppressWarnings("BusyWait")
        @SneakyThrows
        @Override
        public void run() {//TODO заменить на worker
            while (!Thread.currentThread().isInterrupted()) {
                while (!executionQueue.isEmpty()) {
                    var pipeline = executionQueue.poll();
                    currentFlow.set(pipeline);
                    try {
                        pipeline.execute();
                        onExecuted.invoke(pipeline);
                    } catch (Exception e) {
                        log.error("Exception while executing pipeline", e);
                    }
                    currentFlow.set(null);
                }
                Thread.sleep(100L);
                Thread.yield();
            }
        }
    }
}
