package edu.nyu.cs9053.concurrency;

import edu.nyu.cs9053.concurrency.deadlock.Walker;
import edu.nyu.cs9053.concurrency.monitor.ConsumerLogic;
import edu.nyu.cs9053.concurrency.monitor.Elevator;
import edu.nyu.cs9053.concurrency.monitor.UnprotectedElevator;
import edu.nyu.cs9053.concurrency.monitor.ProducerLogic;

import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * User: blangel
 * Date: 11/9/14
 * Time: 11:01 AM
 */
public class Concurrent {

    public static <T> void run(final Callable<T> callable, int times) throws InterruptedException {
        run(callable, times, null);
    }

    public static <T> void run(final Callable<T> callable, int times, final Runnable observer) throws InterruptedException {
        final Set<T> encountered = Collections.synchronizedSet(new HashSet<T>());
        final AtomicBoolean unique = new AtomicBoolean(true);
        Runnable logic = new Runnable() {
            @Override public void run() {
                while (unique.get()) {
                    try {
                        T value = callable.call();
                        if (!unique.compareAndSet(true, encountered.add(value))) {
                            break;
                        }
                        System.out.printf("%s%n", String.valueOf(value));
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        if (observer != null) {
            run(new Runnable() {
                @Override public void run() {
                    while (unique.get()) {
                        observer.run();
                    }
                }
            }, 1, false);
        }
        run(logic, times);
    }

    public static void run(Runnable runnable, int times) throws InterruptedException {
        run(runnable, times, true);
    }

    public static void run(Runnable runnable, int times, boolean join) throws InterruptedException {
        List<Thread> threads = new ArrayList<Thread>(times);
        for (int i = 0; i < times; i++) {
            threads.add(new Thread(runnable));
        }
        for (Thread thread : threads) {
            thread.start();
        }
        if (join) {
            for (Thread thread : threads) {
                thread.join();
            }
        }
    }

    public static void startElevator(final Elevator elevator, final ProducerLogic producer, final ConsumerLogic consumer)
            throws Exception {
        Thread producerThread = new Thread(new Runnable() {
            @Override public void run() {
                while (true) {
                    if (producer.hasPassenger()) {
                        producer.produce(elevator);
                    }
                }
            }
        });
        Thread consumerThread = new Thread(new Runnable() {
            @Override public void run() {
                while (true) {
                    if (consumer.passengerWantsOff()) {
                        consumer.consume(elevator);
                    }
                }
            }
        });
        consumerThread.start();
        producerThread.start();
        producerThread.join();
    }

    public static void walk(final Walker a, final Walker b) throws InterruptedException {
        Thread aThread = new Thread(new Runnable() {
            @Override public void run() {
                while (a.attemptPass()) {
                    System.out.printf("Passed%n");
                }
            }
        });
        Thread bThread = new Thread(new Runnable() {
            @Override public void run() {
                while (b.attemptPass()) {
                    System.out.printf("Passed%n");
                }
            }
        });
        aThread.start();
        bThread.start();
        aThread.join();
        bThread.join();
    }

    public static void walk(final edu.nyu.cs9053.concurrency.livelock.Walker a, final edu.nyu.cs9053.concurrency.livelock.Walker b)
            throws InterruptedException {
        Thread aThread = new Thread(new Runnable() {
            @Override public void run() {
                try {
                    while (a.attemptPass()) {
                        System.out.printf("Passed");
                    }
                } catch (InterruptedException ie) {
                    throw new RuntimeException(ie);
                }
            }
        });
        Thread bThread = new Thread(new Runnable() {
            @Override public void run() {
                try {
                    while (b.attemptPass()) {
                        System.out.printf("Passed");
                    }
                } catch (InterruptedException ie) {
                    throw new RuntimeException(ie);
                }
            }
        });
        aThread.start();
        bThread.start();
        aThread.join();
        bThread.join();
    }

}
