import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.ArrayList;
import java.util.concurrent.SynchronousQueue;

public class Test{
    public static void main(String [] args) {
        int initialNumberOfResources = 10;
        int numberOfThreads = 300;
        var finishedPolling = new AtomicInteger(0);
        var executorService = Executors.newVirtualThreadPerTaskExecutor();
        // var platformThreadExecutor = Executors.newFixedThreadPool(numberOfThreads);
        var tasks = new ArrayList<Future>();
        var resources = new SynchronousQueue<Object>(true);

        // Try to make 10 resources available
        for (int i = 0; i < initialNumberOfResources; i++) {
            executorService.submit(() -> {
                try {
                    resources.offer(new Object(), 1, TimeUnit.HOURS);
                } catch (InterruptedException e) {
                    System.exit(1);
                }
            });
        }

        // main logic: tries to get a resource from SynchronousQueue,
        // simulates some work on the resource by calling Thread.sleep and then releases the resource to other threads through the SynchronousQueue
        for (int i = 0; i < numberOfThreads; i++) {
            tasks.add(executorService.submit(() -> {
                try {
                    var resource = resources.poll(1, TimeUnit.HOURS);
                    finishedPolling.incrementAndGet();

                    long sleepStart = System.nanoTime();
                    Thread.sleep(200);
                    // platformThreadExecutor.submit(() -> {
                    // try {
                    // Thread.sleep(200);
                    // } catch (InterruptedException e) {
                    // e.printStackTrace();
                    // System.exit(1);
                    // }
                    // }).get();
                    long sleepEnd = System.nanoTime();
                    long sleepDurationInMs = (sleepEnd - sleepStart) / 1_000_000;
                    if (sleepDurationInMs > 300) { // if sleep duration is unusually long
                        System.out.println(sleepDurationInMs);
                    }

                    // try to return resource to the queue as long as there are other working threads
                    while (finishedPolling.get() < numberOfThreads && !resources.offer(resource)) {
                        Thread.sleep(10);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.exit(1);
                }
            }));
        }

        // clean up
        tasks.forEach(task -> {
            try {
                task.get();
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
                System.exit(1);
            }
        });
        // platformThreadExecutor.shutdownNow();
        executorService.shutdownNow();
        return;
    }
}