package com.baeldung.java9.streams.reactive.flowvsrx;

import java.util.concurrent.Executors;
import java.util.concurrent.Flow;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.SubmissionPublisher;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

public class FlowApiLiveVideo {

    static class VideoPlayer implements Flow.Subscriber<VideoFrame> {
        Flow.Subscription subscription = null;
        private long consumerDelay = 30;

        public VideoPlayer(long consumerDelay) {
            this.consumerDelay = consumerDelay;
        }

        @Override
        public void onSubscribe(Flow.Subscription subscription) {
            this.subscription = subscription;
            subscription.request(1);
        }

        @Override
        public void onNext(VideoFrame item) {
            try {
                Thread.sleep(consumerDelay);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            subscription.request(1);
        }

        @Override
        public void onError(Throwable throwable) {
        }

        @Override
        public void onComplete() {
        }
    }

    static class VideoStreamServer extends SubmissionPublisher<VideoFrame> {
        ScheduledExecutorService executor = null;

        public VideoStreamServer(int bufferSize) {
            super(Executors.newSingleThreadExecutor(), bufferSize);
            executor = Executors.newScheduledThreadPool(1);
        }

        void startStreaming(long produceDelay, Runnable onDrop) {
            AtomicLong frameNumber = new AtomicLong();
            executor.scheduleWithFixedDelay(() -> {
                offer(new VideoFrame(frameNumber.getAndIncrement()), (subscriber, videoFrame) -> {
                    subscriber.onError(new RuntimeException("Frame#" + videoFrame.getNumber() + " dropped because of back pressure"));
                    onDrop.run();
                    return true;
                });
            }, 0, produceDelay, TimeUnit.MILLISECONDS);
        }
    }

    public static void streamLiveVideo(long produceDelay, long consumeDelay, int bufferSize, Runnable onError){
        FlowApiLiveVideo.VideoStreamServer streamServer = new FlowApiLiveVideo.VideoStreamServer(bufferSize);
        streamServer.subscribe(new FlowApiLiveVideo.VideoPlayer(consumeDelay));
        streamServer.startStreaming(produceDelay, onError);
    }

}