package org.test.pingpong;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {

    public static void main(String... args) throws InterruptedException {
        // Choose one:

        // countDownLatchSolution();
        // cyclicBarrierSolution();
        // queueMessagePassingSolution();
    }

    static volatile CountDownLatch pingLatch = new CountDownLatch(1);
    static volatile CountDownLatch pongLatch = new CountDownLatch(1);

    static class PingPongActivityZ extends Thread {

        private final IAction action;
        private final boolean isPing;

        public PingPongActivityZ(IAction act) {
            action = act;
            isPing = act instanceof Ping;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    (isPing ? pingLatch : pongLatch).await();
                    action.doAction();
                    resetLatch();
                    (isPing ? pongLatch : pingLatch).countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        private void resetLatch() {
            if (isPing) {
                pingLatch = new CountDownLatch(1);
            } else {
                pongLatch = new CountDownLatch(1);
            }
        }
    }

    static void countDownLatchSolution() throws InterruptedException {
        Ping ping = new Ping();
        Pong pong = new Pong();

        Thread tPing = new PingPongActivityZ(ping);
        Thread tPong = new PingPongActivityZ(pong);

        tPing.start();
        tPong.start();

        // progress processing
        pingLatch.countDown();

        tPing.join();
        tPong.join();
    }

    static class PingPongActivity extends Thread {
        private final IAction action;
        private final CyclicBarrier actBarrier;
        private final CyclicBarrier oppActBarrier;

        public PingPongActivity(IAction act, CyclicBarrier actBarrier, CyclicBarrier oppActBarrier) {
            action = act;
            this.actBarrier = actBarrier;
            this.oppActBarrier = oppActBarrier;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    actBarrier.await();
                    action.doAction();
                    oppActBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static void cyclicBarrierSolution() throws InterruptedException {
        Ping ping = new Ping();
        Pong pong = new Pong();

        CyclicBarrier pingBarrier = new CyclicBarrier(2);
        CyclicBarrier pongBarrier = new CyclicBarrier(2);

        Thread tPing = new PingPongActivity(ping, pingBarrier, pongBarrier);
        Thread tPong = new PingPongActivity(pong, pongBarrier, pingBarrier);

        tPing.start();
        tPong.start();

        // progress processing
        try {
            pingBarrier.await();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }

        tPing.join();
        tPong.join();
    }

    static class PingPongActivityM implements Runnable {

        private final IAction action;
        private final ExecutorService oppActionActivity;
        private final IAction oppositeAction;
        private final ExecutorService actionActivity;

        public PingPongActivityM(IAction act, ExecutorService actActivity, IAction oppAction, ExecutorService oppActActivity) {
            action = act;
            actionActivity = actActivity;
            oppositeAction = oppAction;
            oppActionActivity = oppActActivity;
        }

        @Override
        public void run() {
            action.doAction();
            oppActionActivity.execute(new PingPongActivityM(oppositeAction, oppActionActivity, action, actionActivity));
        }

    }

    static void queueMessagePassingSolution() {
        ExecutorService pingActivity = Executors.newFixedThreadPool(1);
        ExecutorService pongActivity = Executors.newFixedThreadPool(1);

        Ping ping = new Ping();
        Pong pong = new Pong();
        pingActivity.execute(new PingPongActivityM(ping, pingActivity, pong, pongActivity));
    }

}
