package com.qmth.paper.library.common.lock.impl;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntUnaryOperator;

/**
 * 简单读写锁实现<br>
 * 0表示空闲状态，-1表示正在写，>0表示正在读
 *
 * @author luoshi
 */
public class ReadWriteLock {

    private static ReadOperator readOperator = new ReadOperator();

    private static UnReadOperator unreadOperator = new UnReadOperator();

    private AtomicInteger value;

    public ReadWriteLock() {
        this.value = new AtomicInteger(0);
    }

    public int read() {
        int result = 0;
        while ((result = value.updateAndGet(readOperator)) < 1) {
            ;
        }
        return result;
    }

    public int unRead() {
        if (value.get() < 1) {
            throw new RuntimeException("unread error");
        }
        return value.updateAndGet(unreadOperator);
    }

    public int readCount() {
        return Math.max(0, value.get());
    }

    public int write() {
        while (!value.compareAndSet(0, -1)) {
            ;
        }
        return -1;
    }

    public boolean tryWrite() {
        return value.compareAndSet(0, -1);
    }

    public int unWrite() {
        while (!value.compareAndSet(-1, 0)) {
            ;
        }
        return 0;
    }

    public boolean writing() {
        return value.get() == -1;
    }

    static class ReadOperator implements IntUnaryOperator {

        @Override
        public int applyAsInt(int operand) {
            return operand >= 0 ? operand + 1 : operand;
        }

    }

    static class UnReadOperator implements IntUnaryOperator {

        @Override
        public int applyAsInt(int operand) {
            return operand >= 1 ? operand - 1 : operand;
        }

    }

    static class LockTestThread implements Runnable {

        private ReadWriteLock lock;

        private int number;

        public LockTestThread(int number, ReadWriteLock lock) {
            this.lock = lock;
            this.number = number;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                if (Math.random() < 0.7) {
                    System.out.println(
                            "[thread-" + number + "]   read: " + lock.read() + "[" + System.currentTimeMillis() + "]");
                    try {
                        Thread.sleep(100 * ((i % 10) + 1));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("[thread-" + number + "] unread: " + lock.unRead() + "["
                            + System.currentTimeMillis() + "]");
                } else {
                    System.out.println(
                            "[thread-" + number + "]  write:" + lock.write() + "[" + System.currentTimeMillis() + "]");
                    try {
                        Thread.sleep(100 * ((i % 10) + 1));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("[thread-" + number + "]unwrite: " + lock.unWrite() + "["
                            + System.currentTimeMillis() + "]");
                }
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(5);
        final ReadWriteLock lock = new ReadWriteLock();
        for (int i = 0; i < 5; i++) {
            executor.submit(new LockTestThread(i + 1, lock));
        }

        executor.shutdown();
    }

}
