package showcase;

import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

import static java.nio.file.StandardOpenOption.WRITE;

public class Test {
    public static final CountDownLatch syncPoint1 = new CountDownLatch(1);
    public static final CountDownLatch syncPoint2 = new CountDownLatch(1);
    public static final CountDownLatch syncPoint3 = new CountDownLatch(1);
    public static final CountDownLatch syncPoint4 = new CountDownLatch(1);
    public static final CountDownLatch syncPoint5 = new CountDownLatch(1);
    public static final CountDownLatch syncPoint6 = new CountDownLatch(1);
    public static final CountDownLatch syncPoint7 = new CountDownLatch(1);

    public static void main(String[] args) throws IOException, InterruptedException {
        Path file = Files.createTempFile("", "");
        file.toFile().deleteOnExit();
        AtomicInteger count = new AtomicInteger(0);
        for (int i = 1; i <= 4; i++) {
            int finalI = i;
            new Thread("Thread " + i) {
                @Override
                public void run() {
                    try {
                        switch (finalI) {
                            case 2:
                                syncPoint1.await();
                                break;

                            case 3:
                                syncPoint1.await();
                                syncPoint2.await();
                                syncPoint3.await();
                                break;

                            case 4:
                                syncPoint1.await();
                                syncPoint2.await();
                                syncPoint3.await();
                                syncPoint4.await();
                                syncPoint5.await();
                                break;
                        }

                        FileChannel channel = FileChannel.open(file, WRITE);
                        boolean locked = false;
                        try {
                            FileLock lock = channel.tryLock();
                            if (lock != null) {
                                try {
                                    locked = true;
                                    System.out.println(Thread.currentThread() + ": increment counter");
                                    if (count.incrementAndGet() > 1) {
                                        System.out.println("FATAL: Acquired overlapping lock!");
                                    }
                                    switch (finalI) {
                                        case 1:
                                            syncPoint1.countDown();
                                            syncPoint2.await();
                                            break;

                                        case 3:
                                            syncPoint4.countDown();
                                            syncPoint6.await();
                                            break;
                                    }
                                } finally {
                                    lock.release();
                                }
                            }
                        } catch (OverlappingFileLockException e) {
                            // this is good, just end the thread
                        } finally {
                            channel.close();
                            if (locked) {
                                System.out.println(Thread.currentThread() + ": decrement counter");
                                count.decrementAndGet();
                            }
                            switch (finalI) {
                                case 1:
                                    syncPoint3.countDown();
                                    break;

                                case 2:
                                    syncPoint5.countDown();
                                    break;

                                case 3:
                                    syncPoint7.countDown();
                                    break;

                                case 4:
                                    syncPoint6.countDown();
                                    break;
                            }
                        }
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            }.start();
        }
        syncPoint7.await();
    }
}